diff --git a/.travis.yml b/.travis.yml index 63005f5bf..829f10df3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,11 @@ android: - tools - tools #Running this twice get's the latest build tools (https://github.com/codepath/android_guides/wiki/Setting-up-Travis-CI) - platform-tools - - android-25 - - build-tools-25.0.2 + - android-27 + - build-tools-27.0.3 - extra + licenses: + - 'android-sdk-license-.+' jdk: oraclejdk8 @@ -15,5 +17,8 @@ notifications: sudo: required #The build runs out of memory and is killed if we use the container system +before_install: + - yes | sdkmanager "platforms;android-27" + script: - ./gradlew clean build diff --git a/app/build.gradle b/app/build.gradle index fbfb6e759..45cc9480d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,4 @@ apply plugin: 'com.android.application' -apply plugin: 'me.tatarka.retrolambda' apply plugin: 'com.github.ben-manes.versions' // ./gradlew dependencyUpdates -Drevision=release apply from: 'quality.gradle' @@ -21,13 +20,13 @@ def clientIdIsNotSet = true; def clientSecretIsNotSet = true; android { - compileSdkVersion 25 - buildToolsVersion '25.0.2' + compileSdkVersion 27 + buildToolsVersion '27.0.3' defaultConfig { applicationId 'com.github.pockethub.android' minSdkVersion 15 - targetSdkVersion 25 + targetSdkVersion 27 versionCode versionMajor * 10000 + versionMinor * 1000 + versionPatch * 100 + versionBuild versionName "${versionMajor}.${versionMinor}.${versionPatch}" vectorDrawables.useSupportLibrary = true @@ -80,6 +79,12 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } + testOptions { + unitTests { + includeAndroidResources = true + } + } + packagingOptions { exclude 'META-INF/DEPENDENCIES' exclude 'META-INF/LICENSE' @@ -109,7 +114,7 @@ android { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) - def supportVersion = '25.3.1' + def supportVersion = '27.0.2' compile "com.android.support:appcompat-v7:$supportVersion" compile "com.android.support:support-v4:$supportVersion" compile "com.android.support:design:$supportVersion" @@ -118,27 +123,31 @@ dependencies { compile 'com.squareup.okhttp3:okhttp:3.8.1' compile 'io.reactivex.rxjava2:rxandroid:2.0.1' - compile 'io.reactivex.rxjava2:rxjava:2.0.7' - compile 'com.trello.rxlifecycle2:rxlifecycle:2.0.1' - compile 'com.trello.rxlifecycle2:rxlifecycle-android:2.0.1' - compile 'com.bugsnag:bugsnag-android:3.2.7' - compile 'org.roboguice:roboguice:2.0' + compile 'io.reactivex.rxjava2:rxjava:2.1.7' + compile 'com.uber.autodispose:autodispose:0.5.1' + compile 'com.uber.autodispose:autodispose-android-archcomponents:0.5.1' - compile 'com.github.meisolsson:githubsdk:0.4.5' + compile 'com.bugsnag:bugsnag-android:3.6.0' - compile ('com.google.inject.extensions:guice-assistedinject:3.0'){ - exclude group: 'com.google.inject' - } - compile 'com.afollestad.material-dialogs:core:0.9.4.4' + compile 'com.google.dagger:dagger:2.13' + compile 'com.google.dagger:dagger-android:2.13' + compile 'com.google.dagger:dagger-android-support:2.13' - //Self compiled .aar version of wishlist - compile (name:'lib', ext:'aar') + annotationProcessor 'com.google.dagger:dagger-compiler:2.13' + annotationProcessor 'com.google.dagger:dagger-android-processor:2.13' + + provided 'com.episode6.hackit.auto.factory:auto-factory-annotations:1.0-beta5' + annotationProcessor 'com.google.auto.factory:auto-factory:1.0-beta5' + + compile 'com.jakewharton:butterknife:8.8.1' + annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' + + compile 'com.github.meisolsson:githubsdk:0.4.5' + compile 'com.afollestad.material-dialogs:core:0.9.6.0' + compile 'com.xwray:groupie:2.0.3' //Libs for testing - testCompile 'junit:junit:4.12' - testCompile "org.mockito:mockito-core:1.9.5" - testCompile('org.robolectric:robolectric:3.3.2') { - exclude group: 'commons-logging', module: 'commons-logging' - exclude group: 'org.apache.httpcomponents', module: 'httpclient' - } + testImplementation 'junit:junit:4.12' + testImplementation "org.mockito:mockito-core:1.9.5" + testImplementation "org.robolectric:robolectric:3.6.1" } diff --git a/app/libs/lib.aar b/app/libs/lib.aar deleted file mode 100644 index ee5205a15..000000000 Binary files a/app/libs/lib.aar and /dev/null differ diff --git a/app/src/androidTest/java/com/github/pockethub/android/tests/NewsEventTextTest.java b/app/src/androidTest/java/com/github/pockethub/android/tests/NewsEventTextTest.java index 0d10feb70..17dd1385e 100644 --- a/app/src/androidTest/java/com/github/pockethub/android/tests/NewsEventTextTest.java +++ b/app/src/androidTest/java/com/github/pockethub/android/tests/NewsEventTextTest.java @@ -22,6 +22,8 @@ import android.view.View; import android.widget.TextView; +import com.github.pockethub.android.ui.item.news.GistEventItem; +import com.github.pockethub.android.ui.item.news.NewsItem; import com.meisolsson.githubsdk.model.Gist; import com.meisolsson.githubsdk.model.GitHubEvent; import com.meisolsson.githubsdk.model.GitHubEventType; @@ -30,7 +32,6 @@ import com.meisolsson.githubsdk.model.Team; import com.meisolsson.githubsdk.model.User; import com.github.pockethub.android.R.id; -import com.github.pockethub.android.ui.user.NewsListAdapter; import com.github.pockethub.android.util.AvatarLoader; import com.meisolsson.githubsdk.model.payload.CommitCommentPayload; import com.meisolsson.githubsdk.model.payload.CreatePayload; @@ -44,6 +45,9 @@ import com.meisolsson.githubsdk.model.payload.PullRequestPayload; import com.meisolsson.githubsdk.model.payload.PushPayload; import com.meisolsson.githubsdk.model.payload.TeamAddPayload; +import com.xwray.groupie.GroupAdapter; + +import java.util.Collections; import java.util.Date; /** @@ -51,28 +55,30 @@ */ public class NewsEventTextTest extends InstrumentationTestCase { - private NewsListAdapter adapter; - private TextView text; private User actor; private Repository repo; + private AvatarLoader avatarLoader; + + private LayoutInflater layoutInflater; + @Override protected void setUp() throws Exception { super.setUp(); actor = User.builder().login("user").build(); repo = Repository.builder().name("user/repo").build(); - Context context = getInstrumentation().getTargetContext(); - adapter = new NewsListAdapter(LayoutInflater.from(context), - new AvatarLoader(context)); + avatarLoader = new AvatarLoader(context); + layoutInflater = LayoutInflater.from(context); } private GitHubEvent createEvent(GitHubEventType type, GitHubPayload payload) { return GitHubEvent.builder() + .id("test") .createdAt(new Date()) .type(type) .payload(payload) @@ -88,10 +94,13 @@ private void verify(String expected) { } private void updateView(GitHubEvent event) { - adapter.setItems(new Object[] { event }); - View view = adapter.getView(0, null, null); - assertNotNull(view); - text = (TextView) view.findViewById(id.tv_event); + NewsItem item = NewsItem.createNewsItem(avatarLoader, event); + + View itemView = layoutInflater.inflate(item.getLayout(), null); + NewsItem.ViewHolder viewHolder = item.createViewHolder(itemView); + item.bind(viewHolder, 0); + + text = viewHolder.event; assertNotNull(text); } @@ -307,6 +316,7 @@ public void testPullRequest() { public void testPush() { PushPayload payload = PushPayload.builder() .ref("refs/heads/master") + .commits(Collections.emptyList()) .build(); GitHubEvent event = createEvent(GitHubEventType.PushEvent, payload); diff --git a/app/src/androidTest/java/com/github/pockethub/android/tests/gist/CreateGistActivityTest.java b/app/src/androidTest/java/com/github/pockethub/android/tests/gist/CreateGistActivityTest.java index 6d20d3407..33351f3c5 100644 --- a/app/src/androidTest/java/com/github/pockethub/android/tests/gist/CreateGistActivityTest.java +++ b/app/src/androidTest/java/com/github/pockethub/android/tests/gist/CreateGistActivityTest.java @@ -43,7 +43,7 @@ public CreateGistActivityTest() { public void testCreateWithInitialText() { setActivityIntent(new Intent().putExtra(EXTRA_TEXT, "gist content")); - View createMenu = view(id.m_apply); + View createMenu = view(id.create_gist); assertTrue(createMenu.isEnabled()); EditText content = editText(id.et_gist_content); assertEquals("gist content", content.getText().toString()); @@ -55,7 +55,7 @@ public void testCreateWithInitialText() { * @throws Throwable */ public void testCreateWithNoInitialText() throws Throwable { - View createMenu = view(id.m_apply); + View createMenu = view(id.create_gist); assertFalse(createMenu.isEnabled()); EditText content = editText(id.et_gist_content); focus(content); diff --git a/app/src/androidTest/java/com/github/pockethub/android/tests/gist/GistFilesViewActivityTest.java b/app/src/androidTest/java/com/github/pockethub/android/tests/gist/GistFilesViewActivityTest.java index 40c1b0bb2..8b6a71c88 100644 --- a/app/src/androidTest/java/com/github/pockethub/android/tests/gist/GistFilesViewActivityTest.java +++ b/app/src/androidTest/java/com/github/pockethub/android/tests/gist/GistFilesViewActivityTest.java @@ -15,29 +15,34 @@ */ package com.github.pockethub.android.tests.gist; +import android.app.Application; +import android.app.Instrumentation; +import android.content.Context; import android.support.v4.util.ArrayMap; import android.support.v4.view.ViewPager; +import com.github.pockethub.android.GitHubModule; +import com.github.pockethub.android.PocketHub; +import com.github.pockethub.android.PocketHubModule; import com.github.pockethub.android.R.id; import com.github.pockethub.android.core.gist.GistStore; import com.github.pockethub.android.tests.ActivityTest; import com.github.pockethub.android.ui.gist.GistFilesViewActivity; import com.meisolsson.githubsdk.model.Gist; import com.meisolsson.githubsdk.model.GistFile; -import com.google.inject.Inject; +import javax.inject.Inject; +import javax.inject.Singleton; import java.util.Map; -import roboguice.RoboGuice; +import dagger.Component; /** * Tests of {@link GistFilesViewActivity} */ -public class GistFilesViewActivityTest extends - ActivityTest { +public class GistFilesViewActivityTest extends ActivityTest { - @Inject - private GistStore store; + protected GistStore store; private Gist gist; @@ -52,8 +57,9 @@ public GistFilesViewActivityTest() { protected void setUp() throws Exception { super.setUp(); - RoboGuice.injectMembers(getInstrumentation().getTargetContext() - .getApplicationContext(), this); + Context context = getInstrumentation().getTargetContext(); + PocketHub pocketHub = (PocketHub) context.getApplicationContext(); + store = pocketHub.applicationComponent().gistStore(); Map files = new ArrayMap<>(); diff --git a/app/src/androidTest/java/com/github/pockethub/android/tests/gist/GistStoreTest.java b/app/src/androidTest/java/com/github/pockethub/android/tests/gist/GistStoreTest.java index 72a776974..3f790cb18 100644 --- a/app/src/androidTest/java/com/github/pockethub/android/tests/gist/GistStoreTest.java +++ b/app/src/androidTest/java/com/github/pockethub/android/tests/gist/GistStoreTest.java @@ -31,7 +31,7 @@ public class GistStoreTest extends AndroidTestCase { * Verify issue is updated when re-added */ public void testReuseIssue() { - GistStore store = new GistStore(mContext); + GistStore store = new GistStore(); assertNull(store.getGist("abcd")); Gist gist = Gist.builder() diff --git a/app/src/androidTest/java/com/github/pockethub/android/tests/issue/IssueStoreTest.java b/app/src/androidTest/java/com/github/pockethub/android/tests/issue/IssueStoreTest.java index c6ef0ce29..6e13c477b 100644 --- a/app/src/androidTest/java/com/github/pockethub/android/tests/issue/IssueStoreTest.java +++ b/app/src/androidTest/java/com/github/pockethub/android/tests/issue/IssueStoreTest.java @@ -33,7 +33,7 @@ public class IssueStoreTest extends AndroidTestCase { * Verify issue is updated when re-added */ public void testReuseIssue() { - IssueStore store = new IssueStore(mContext); + IssueStore store = new IssueStore(); Repository repo = InfoUtils.createRepoFromData("owner", "name"); assertNull(store.getIssue(repo, 1)); diff --git a/app/src/main/java/com/github/pockethub/android/ApplicationComponent.java b/app/src/main/java/com/github/pockethub/android/ApplicationComponent.java new file mode 100644 index 000000000..bc5532665 --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/ApplicationComponent.java @@ -0,0 +1,40 @@ +package com.github.pockethub.android; + +import android.app.Application; + +import com.github.pockethub.android.core.gist.GistStore; +import com.github.pockethub.android.dagger.ActivityBuilder; +import com.github.pockethub.android.dagger.DialogFragmentBuilder; +import com.github.pockethub.android.dagger.ServiceBuilder; + +import javax.inject.Singleton; + +import dagger.BindsInstance; +import dagger.Component; +import dagger.android.AndroidInjector; +import dagger.android.support.AndroidSupportInjectionModule; + +@Singleton +@Component(modules = { + AndroidSupportInjectionModule.class, + ApplicationModule.class, + PocketHubModule.class, + ActivityBuilder.class, + ServiceBuilder.class, + DialogFragmentBuilder.class, + GitHubModule.class +}) +public interface ApplicationComponent extends AndroidInjector { + + GistStore gistStore(); + + @Component.Builder + abstract class Builder extends AndroidInjector.Builder { + + @BindsInstance + abstract Builder application(Application application); + + @Override + public abstract ApplicationComponent build(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/github/pockethub/android/ApplicationModule.java b/app/src/main/java/com/github/pockethub/android/ApplicationModule.java new file mode 100644 index 000000000..678e25dfb --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/ApplicationModule.java @@ -0,0 +1,18 @@ +package com.github.pockethub.android; + +import android.app.Application; +import android.content.Context; + +import javax.inject.Singleton; + +import dagger.Binds; +import dagger.BindsInstance; +import dagger.Module; + +@Module +abstract class ApplicationModule { + + @Binds + @Singleton + abstract Context provideApplicationContext(Application application); +} diff --git a/app/src/main/java/com/github/pockethub/android/GitHubModule.java b/app/src/main/java/com/github/pockethub/android/GitHubModule.java new file mode 100644 index 000000000..cfbbf5b43 --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/GitHubModule.java @@ -0,0 +1,49 @@ +package com.github.pockethub.android; + +import android.content.Context; + +import com.meisolsson.githubsdk.core.ServiceGenerator; +import com.meisolsson.githubsdk.service.gists.GistService; +import com.meisolsson.githubsdk.service.issues.IssueService; +import com.meisolsson.githubsdk.service.repositories.RepositoryCommentService; +import com.meisolsson.githubsdk.service.repositories.RepositoryCommitService; +import com.meisolsson.githubsdk.service.users.UserService; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +@Module +public class GitHubModule { + + @Provides + @Singleton + RepositoryCommitService providesRepositoryCommitService(Context context) { + return ServiceGenerator.createService(context, RepositoryCommitService.class); + } + + @Provides + @Singleton + UserService providesUserService(Context context) { + return ServiceGenerator.createService(context, UserService.class); + } + + @Provides + @Singleton + RepositoryCommentService providesRepositoryCommentService(Context context) { + return ServiceGenerator.createService(context, RepositoryCommentService.class); + } + + @Provides + @Singleton + GistService providesGistService(Context context) { + return ServiceGenerator.createService(context, GistService.class); + } + + @Provides + @Singleton + IssueService providesIssueService(Context context) { + return ServiceGenerator.createService(context, IssueService.class); + } +} diff --git a/app/src/main/java/com/github/pockethub/android/PocketHub.java b/app/src/main/java/com/github/pockethub/android/PocketHub.java index 70911643c..f60deaa57 100644 --- a/app/src/main/java/com/github/pockethub/android/PocketHub.java +++ b/app/src/main/java/com/github/pockethub/android/PocketHub.java @@ -16,13 +16,19 @@ package com.github.pockethub.android; -import android.app.Application; +import android.util.Log; import com.bugsnag.android.Bugsnag; import net.danlew.android.joda.JodaTimeAndroid; -public class PocketHub extends Application { +import javax.inject.Inject; +import dagger.android.AndroidInjector; +import dagger.android.DaggerApplication; + +public class PocketHub extends DaggerApplication { + + private ApplicationComponent applicationComponent; @Override public void onCreate() { @@ -31,4 +37,22 @@ public void onCreate() { Bugsnag.init(this); Bugsnag.setNotifyReleaseStages("production"); } + + @Inject + void logInjection() { + Log.i("Test", "Injecting " + PocketHub.class.getSimpleName()); + } + + public ApplicationComponent applicationComponent() { + return applicationComponent; + } + + @Override + protected AndroidInjector applicationInjector() { + applicationComponent = (ApplicationComponent) DaggerApplicationComponent.builder() + .application(this) + .create(this); + + return applicationComponent; + } } diff --git a/app/src/main/java/com/github/pockethub/android/PocketHubModule.java b/app/src/main/java/com/github/pockethub/android/PocketHubModule.java index 2388e0597..b60073d95 100644 --- a/app/src/main/java/com/github/pockethub/android/PocketHubModule.java +++ b/app/src/main/java/com/github/pockethub/android/PocketHubModule.java @@ -22,38 +22,27 @@ import com.github.pockethub.android.core.commit.CommitStore; import com.github.pockethub.android.core.gist.GistStore; import com.github.pockethub.android.core.issue.IssueStore; -import com.github.pockethub.android.persistence.OrganizationRepositories; -import com.github.pockethub.android.sync.SyncCampaign; -import com.google.inject.AbstractModule; -import com.google.inject.Provides; -import com.google.inject.assistedinject.FactoryModuleBuilder; -import com.google.inject.name.Named; +import com.github.pockethub.android.persistence.AccountDataManager; +import com.github.pockethub.android.persistence.CacheHelper; +import com.github.pockethub.android.persistence.DatabaseCache; import java.io.File; import java.lang.ref.WeakReference; +import javax.inject.Named; + +import dagger.Module; +import dagger.Provides; + /** * Main module provide services and clients */ -public class PocketHubModule extends AbstractModule { - - private WeakReference issues; - - private WeakReference gists; - - private WeakReference commits; - - @Override - protected void configure() { - install(new ServicesModule()); - install(new FactoryModuleBuilder().build(SyncCampaign.Factory.class)); - install(new FactoryModuleBuilder() - .build(OrganizationRepositories.Factory.class)); - } +@Module +public class PocketHubModule { @Provides - Account account(Context context){ + Account account(Context context) { AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE); Account[] accounts = accountManager.getAccountsByType(context.getString(R.string.account_type)); return accounts[0]; @@ -64,34 +53,4 @@ Account account(Context context){ File cacheDir(Context context) { return new File(context.getFilesDir(), "cache"); } - - @Provides - IssueStore issueStore(Context context) { - IssueStore store = issues != null ? issues.get() : null; - if (store == null) { - store = new IssueStore(context); - issues = new WeakReference<>(store); - } - return store; - } - - @Provides - GistStore gistStore(Context context) { - GistStore store = gists != null ? gists.get() : null; - if (store == null) { - store = new GistStore(context); - gists = new WeakReference<>(store); - } - return store; - } - - @Provides - CommitStore commitStore(Context context) { - CommitStore store = commits != null ? commits.get() : null; - if (store == null) { - store = new CommitStore(context); - commits = new WeakReference<>(store); - } - return store; - } } diff --git a/app/src/main/java/com/github/pockethub/android/ServicesModule.java b/app/src/main/java/com/github/pockethub/android/ServicesModule.java deleted file mode 100644 index ec99069f9..000000000 --- a/app/src/main/java/com/github/pockethub/android/ServicesModule.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2015 PocketHub - * - * 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.pockethub.android; - -import android.content.Context; - -import com.meisolsson.githubsdk.core.ServiceGenerator; -import com.meisolsson.githubsdk.model.User; -import com.meisolsson.githubsdk.service.users.UserService; -import com.google.inject.AbstractModule; -import com.google.inject.Provides; - -import java.io.IOException; - -/** - * Provide GitHub-API related services - */ -public class ServicesModule extends AbstractModule { - - @Override - protected void configure() { - } - - @Provides - User currentUser(Context context) throws IOException { - return ServiceGenerator.createService(context, UserService.class) - .getUser() - .blockingGet() - .body(); - } -} diff --git a/app/src/main/java/com/github/pockethub/android/ThrowableLoader.java b/app/src/main/java/com/github/pockethub/android/ThrowableLoader.java deleted file mode 100644 index 2174d8cd7..000000000 --- a/app/src/main/java/com/github/pockethub/android/ThrowableLoader.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2015 PocketHub - * - * 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.pockethub.android; - -import android.accounts.Account; -import android.app.Activity; -import android.util.Log; - -import com.github.pockethub.android.accounts.AccountUtils; -import com.github.pockethub.android.accounts.AuthenticatedUserLoader; - -/** - * Loader that support throwing an exception when loading in the background - * - * @param - */ -public abstract class ThrowableLoader extends AuthenticatedUserLoader { - - private static final String TAG = "ThrowableLoader"; - - private final D data; - - private Exception exception; - - /** - * Create loader for context and seeded with initial data - * - * @param activity - * @param data - */ - public ThrowableLoader(Activity activity, D data) { - super(activity); - - this.data = data; - } - - @Override - protected D getAccountFailureData() { - return data; - } - - @Override - public D load(final Account account) { - exception = null; - try { - return loadData(); - } catch (Exception e) { - if (AccountUtils.isUnauthorized(e) - && AccountUtils.updateAccount(account, activity)) { - try { - return loadData(); - } catch (Exception e2) { - e = e2; - } - } - Log.d(TAG, "Exception loading data", e); - exception = e; - return data; - } - } - - /** - * @return exception - */ - public Exception getException() { - return exception; - } - - /** - * Clear the stored exception and return it - * - * @return exception - */ - public Exception clearException() { - final Exception throwable = exception; - exception = null; - return throwable; - } - - /** - * Load data - * - * @return data - * @throws Exception - */ - public abstract D loadData() throws Exception; -} diff --git a/app/src/main/java/com/github/pockethub/android/accounts/AuthenticatedUserLoader.java b/app/src/main/java/com/github/pockethub/android/accounts/AuthenticatedUserLoader.java deleted file mode 100644 index 2ea85565d..000000000 --- a/app/src/main/java/com/github/pockethub/android/accounts/AuthenticatedUserLoader.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2015 PocketHub - * - * 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.pockethub.android.accounts; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.accounts.AccountsException; -import android.app.Activity; - -import com.github.kevinsawicki.wishlist.AsyncLoader; -import com.google.inject.Inject; - -import java.io.IOException; - -import roboguice.RoboGuice; -import roboguice.inject.ContextScope; - -/** - * Base loader class that ensures an authenticated account exists before - * {@link #load(Account)} is called - * - * @param - */ -public abstract class AuthenticatedUserLoader extends AsyncLoader { - - /** - * Activity using this loader - */ - protected Activity activity; - - @Inject - private ContextScope contextScope; - - /** - * Create loader for context - * - * @param activity - */ - public AuthenticatedUserLoader(final Activity activity) { - super(activity); - this.activity = activity; - RoboGuice.injectMembers(activity, this); - } - - /** - * Get data to display when obtaining an account fails - * - * @return data - */ - protected abstract D getAccountFailureData(); - - @Override - public final D loadInBackground() { - final AccountManager manager = AccountManager.get(activity); - final Account account; - try { - account = AccountUtils.getAccount(manager, activity); - } catch (IOException e) { - return getAccountFailureData(); - } catch (AccountsException e) { - return getAccountFailureData(); - } - - contextScope.enter(getContext()); - try { - return load(account); - } finally { - contextScope.exit(getContext()); - } - } - - /** - * Load data - * - * @param account - * @return data - */ - public abstract D load(Account account); -} diff --git a/app/src/main/java/com/github/pockethub/android/accounts/LoginActivity.java b/app/src/main/java/com/github/pockethub/android/accounts/LoginActivity.java index ceb6305d5..29ae327c2 100644 --- a/app/src/main/java/com/github/pockethub/android/accounts/LoginActivity.java +++ b/app/src/main/java/com/github/pockethub/android/accounts/LoginActivity.java @@ -29,8 +29,9 @@ import com.afollestad.materialdialogs.MaterialDialog; import com.github.pockethub.android.R; +import com.github.pockethub.android.rx.AutoDisposeUtils; import com.github.pockethub.android.ui.MainActivity; -import com.github.pockethub.android.ui.roboactivities.RoboAccountAuthenticatorAppCompatActivity; +import com.github.pockethub.android.ui.base.AccountAuthenticatorAppCompatActivity; import com.meisolsson.githubsdk.core.ServiceGenerator; import com.meisolsson.githubsdk.core.TokenStore; import com.meisolsson.githubsdk.model.GitHubToken; @@ -40,6 +41,8 @@ import java.util.concurrent.TimeUnit; +import javax.inject.Inject; + import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; import okhttp3.HttpUrl; @@ -49,7 +52,7 @@ /** * Activity to login */ -public class LoginActivity extends RoboAccountAuthenticatorAppCompatActivity { +public class LoginActivity extends AccountAuthenticatorAppCompatActivity { /** * Auth token type parameter @@ -85,25 +88,19 @@ public static void configureSyncFor(Account account) { private AccountManager accountManager; - private String accessToken; - - private String scope; - private MaterialDialog progressDialog; + @Inject + protected UserService userService; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.login); - clientId = getString(R.string.github_client); secret = getString(R.string.github_secret); redirectUri = getString(R.string.github_oauth); - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - accountManager = AccountManager.get(this); Account[] accounts = accountManager.getAccountsByType(getString(R.string.account_type)); @@ -114,6 +111,11 @@ public void onCreate(Bundle savedInstanceState) { checkOauthConfig(); } + @Override + protected int getContentView() { + return R.layout.login; + } + private void checkOauthConfig() { if (clientId.equals("dummy_client") || secret.equals("dummy_secret")) { Toast.makeText(this, R.string.error_oauth_not_configured, Toast.LENGTH_LONG).show(); @@ -142,7 +144,7 @@ private void onUserLoggedIn(Uri uri) { .getToken(request) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .compose(this.bindToLifecycle()) + .as(AutoDisposeUtils.bindToLifecycle(this)) .subscribe(response -> { GitHubToken token = response.body(); if (token.accessToken() != null) { @@ -156,10 +158,6 @@ private void onUserLoggedIn(Uri uri) { } private void openMain() { - if (progressDialog != null) { - progressDialog.dismiss(); - } - Intent intent = new Intent(this, MainActivity.class); startActivity(intent); finish(); @@ -195,7 +193,7 @@ private void openLoginInBrowser() { @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if(requestCode == WEBVIEW_REQUEST_CODE && resultCode == RESULT_OK) { + if (requestCode == WEBVIEW_REQUEST_CODE && resultCode == RESULT_OK) { onUserLoggedIn(data.getData()); } } @@ -212,17 +210,14 @@ public boolean onOptionsItemSelected(MenuItem item) { } private void endAuth(final String accessToken, final String scope) { - this.accessToken = accessToken; - this.scope = scope; - progressDialog.setContent(getString(R.string.loading_user)); TokenStore.getInstance(this).saveToken(accessToken); - ServiceGenerator.createService(this, UserService.class) + userService .getUser() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .compose(this.bindToLifecycle()) + .as(AutoDisposeUtils.bindToLifecycle(this)) .subscribe(response -> { User user = response.body(); Account account = new Account(user.login(), getString(R.string.account_type)); @@ -240,10 +235,18 @@ private void endAuth(final String accessToken, final String scope) { setAccountAuthenticatorResult(result); - openMain(); + finish(); }, Throwable::printStackTrace); } + @Override + public void finish() { + if (progressDialog != null) { + progressDialog.dismiss(); + } + super.finish(); + } + @Override public boolean onCreateOptionsMenu(Menu optionMenu) { getMenuInflater().inflate(R.menu.activity_login, optionMenu); diff --git a/app/src/main/java/com/github/pockethub/android/core/ResourcePager.java b/app/src/main/java/com/github/pockethub/android/core/ResourcePager.java deleted file mode 100644 index bcf6cf689..000000000 --- a/app/src/main/java/com/github/pockethub/android/core/ResourcePager.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (c) 2015 PocketHub - * - * 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.pockethub.android.core; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; - -/** - * Generic resource pager for elements with an id that can be paged - * - * @param - */ -public abstract class ResourcePager { - - /** - * Next page to request - */ - protected int page = 1; - - /** - * Number of pages to request - */ - protected int count = 1; - - /** - * All resources retrieved - */ - protected final Map resources = new LinkedHashMap<>(); - - /** - * Are more pages available? - */ - protected boolean hasMore; - private PageIterator iterator; - - /** - * Reset the number of the next page to be requested from {@link #next()} - * and clear all stored state - * - * @return this pager - */ - public ResourcePager reset() { - page = 1; - return clear(); - } - - /** - * Clear all stored resources and have the next call to {@link #next()} load - * all previously loaded pages - * - * @return this pager - */ - public ResourcePager clear() { - count = Math.max(1, page - 1); - iterator = null; - page = 1; - resources.clear(); - hasMore = true; - return this; - } - - /** - * Get number of resources loaded into this pager - * - * @return number of resources - */ - public int size() { - return resources.size(); - } - - /** - * Get resources - * - * @return resources - */ - public List getResources() { - return new ArrayList<>(resources.values()); - } - - /** - * Get the next page of issues - * - * @return true if more pages - * @throws IOException - */ - public boolean next() throws IOException { - boolean emptyPage = false; - if(iterator == null) { - iterator = createIterator(page, -1); - } - - try { - for (int i = 0; i < count && iterator.hasNext(); i++) { - Collection resourcePage = iterator.next(); - emptyPage = resourcePage.isEmpty(); - if (emptyPage) { - break; - } - for (E resource : resourcePage) { - resource = register(resource); - if (resource == null) { - continue; - } - resources.put(getId(resource), resource); - } - } - // Set page to count value if first call after call to reset() - if (count > 1) { - page = count; - count = 1; - } - - page++; - } catch (NoSuchElementException e) { - hasMore = false; - e.printStackTrace(); - } - hasMore = iterator.hasNext() && !emptyPage; - return hasMore; - } - - /** - * Are more pages available to request? - * - * @return true if the last call to {@link #next()} returned true, false - * otherwise - */ - public boolean hasMore() { - return hasMore; - } - - /** - * Callback to register a fetched resource before it is stored in this pager - *

- * Sub-classes may override - * - * @param resource - * @return resource - */ - protected E register(final E resource) { - return resource; - } - - /** - * Get id for resource - * - * @param resource - * @return id - */ - protected abstract Object getId(E resource); - - /** - * Create iterator to return given page and size - * - * @param page - * @param size - * @return iterator - */ - public abstract PageIterator createIterator(final int page, - final int size); -} diff --git a/app/src/main/java/com/github/pockethub/android/core/commit/CommitPager.java b/app/src/main/java/com/github/pockethub/android/core/commit/CommitPager.java deleted file mode 100644 index 2b58c7a4c..000000000 --- a/app/src/main/java/com/github/pockethub/android/core/commit/CommitPager.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2015 PocketHub - * - * 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.pockethub.android.core.commit; - -import com.github.pockethub.android.core.ResourcePager; -import com.meisolsson.githubsdk.model.Commit; -import com.meisolsson.githubsdk.model.Repository; - -/** - * Pager over commits - */ -public abstract class CommitPager extends ResourcePager { - - private final Repository repository; - - private final CommitStore store; - - /** - * Create pager - * - * @param repository - * @param store - */ - public CommitPager(final Repository repository, final CommitStore store) { - this.repository = repository; - this.store = store; - } - - @Override - protected Object getId(final Commit resource) { - return resource.sha(); - } - - @Override - protected Commit register(final Commit resource) { - return store.addCommit(repository, resource); - } -} diff --git a/app/src/main/java/com/github/pockethub/android/core/commit/CommitStore.java b/app/src/main/java/com/github/pockethub/android/core/commit/CommitStore.java index 47613ed54..af8196be4 100644 --- a/app/src/main/java/com/github/pockethub/android/core/commit/CommitStore.java +++ b/app/src/main/java/com/github/pockethub/android/core/commit/CommitStore.java @@ -24,29 +24,31 @@ import com.meisolsson.githubsdk.model.Repository; import com.meisolsson.githubsdk.service.repositories.RepositoryCommitService; -import java.io.IOException; import java.util.HashMap; import java.util.Map; +import javax.inject.Inject; +import javax.inject.Singleton; + import io.reactivex.Single; /** * Store of commits */ +@Singleton public class CommitStore extends ItemStore { private final Map> commits = new HashMap<>(); - private final Context context; + @Inject + protected RepositoryCommitService service; /** - * Create commit store - * - * @param context + * Create commit store. */ - public CommitStore(final Context context) { - this.context = context; + @Inject + public CommitStore() { } /** @@ -92,7 +94,7 @@ public Commit addCommit(Repository repo, Commit commit) { * @return refreshed commit */ public Single refreshCommit(final Repository repo, final String id) { - return ServiceGenerator.createService(context, RepositoryCommitService.class) + return service .getCommit(repo.owner().login(), repo.name(), id) .map(response -> addCommit(repo, response.body())); } diff --git a/app/src/main/java/com/github/pockethub/android/core/commit/RefreshCommitTask.java b/app/src/main/java/com/github/pockethub/android/core/commit/RefreshCommitTask.java index c3198a961..6c3f7c214 100644 --- a/app/src/main/java/com/github/pockethub/android/core/commit/RefreshCommitTask.java +++ b/app/src/main/java/com/github/pockethub/android/core/commit/RefreshCommitTask.java @@ -20,24 +20,28 @@ import com.github.pockethub.android.util.HttpImageGetter; import com.github.pockethub.android.util.RxPageUtil; +import com.google.auto.factory.AutoFactory; +import com.google.auto.factory.Provided; import com.meisolsson.githubsdk.core.ServiceGenerator; import com.meisolsson.githubsdk.model.Repository; import com.meisolsson.githubsdk.service.repositories.RepositoryCommentService; -import com.google.inject.Inject; +import com.meisolsson.githubsdk.service.repositories.RepositoryCommitService; +import javax.inject.Inject; + +import dagger.Provides; import io.reactivex.Observable; import io.reactivex.Single; -import roboguice.RoboGuice; /** * Task to load a commit by SHA-1 id */ +@AutoFactory public class RefreshCommitTask { private final Context context; - @Inject - private CommitStore store; + private final CommitStore store; private final Repository repository; @@ -45,19 +49,22 @@ public class RefreshCommitTask { private final HttpImageGetter imageGetter; + private final RepositoryCommentService service; + /** * @param repository * @param id * @param imageGetter */ - public RefreshCommitTask(Activity activity, Repository repository, - String id, HttpImageGetter imageGetter) { - + public RefreshCommitTask(@Provided CommitStore store, @Provided HttpImageGetter imageGetter, + @Provided RepositoryCommentService service, + Activity activity, Repository repository, String id) { + this.service = service; + this.store = store; this.repository = repository; this.id = id; this.imageGetter = imageGetter; this.context = activity; - RoboGuice.injectMembers(activity, this); } /** @@ -66,12 +73,9 @@ public RefreshCommitTask(Activity activity, Repository repository, * @return Single for a FullCommit */ public Single refresh() { - RepositoryCommentService commentService = - ServiceGenerator.createService(context, RepositoryCommentService.class); - return store.refreshCommit(repository, id) .flatMap(commit -> RxPageUtil.getAllPages((page) -> - commentService.getCommitComments(repository.owner().login(), + service.getCommitComments(repository.owner().login(), repository.name(), commit.sha(), page), 1) .flatMap(page -> Observable.fromIterable(page.items())) .map(comment -> { diff --git a/app/src/main/java/com/github/pockethub/android/core/gist/GistEventMatcher.java b/app/src/main/java/com/github/pockethub/android/core/gist/GistEventMatcher.java index 8b5ab3fb7..d957e5e00 100644 --- a/app/src/main/java/com/github/pockethub/android/core/gist/GistEventMatcher.java +++ b/app/src/main/java/com/github/pockethub/android/core/gist/GistEventMatcher.java @@ -15,7 +15,6 @@ */ package com.github.pockethub.android.core.gist; -import com.github.pockethub.android.ui.user.EventType; import com.meisolsson.githubsdk.model.Gist; import com.meisolsson.githubsdk.model.GitHubEvent; import com.meisolsson.githubsdk.model.GitHubEventType; @@ -39,7 +38,7 @@ public Gist getGist(final GitHubEvent event) { } GitHubEventType type = event.type(); - if (EventType.GistEvent.equals(type)) { + if (GitHubEventType.GistEvent.equals(type)) { return ((GistPayload) event.payload()).gist(); } else { return null; diff --git a/app/src/main/java/com/github/pockethub/android/core/gist/GistPager.java b/app/src/main/java/com/github/pockethub/android/core/gist/GistPager.java deleted file mode 100644 index aa41ba0cd..000000000 --- a/app/src/main/java/com/github/pockethub/android/core/gist/GistPager.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2015 PocketHub - * - * 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.pockethub.android.core.gist; - -import com.github.pockethub.android.core.ResourcePager; -import com.meisolsson.githubsdk.model.Gist; - -/** - * Pager over Gists - */ -public abstract class GistPager extends ResourcePager { - - private final GistStore store; - - /** - * Create pager - * - * @param store - */ - public GistPager(final GistStore store) { - this.store = store; - } - - @Override - protected Object getId(Gist resource) { - return resource.id(); - } - - @Override - protected Gist register(Gist resource) { - return store.addGist(resource); - } -} diff --git a/app/src/main/java/com/github/pockethub/android/core/gist/GistStore.java b/app/src/main/java/com/github/pockethub/android/core/gist/GistStore.java index cdcb87673..ff0911bc6 100644 --- a/app/src/main/java/com/github/pockethub/android/core/gist/GistStore.java +++ b/app/src/main/java/com/github/pockethub/android/core/gist/GistStore.java @@ -28,6 +28,9 @@ import java.util.Map; import java.util.TreeMap; +import javax.inject.Inject; +import javax.inject.Singleton; + import io.reactivex.Single; import static java.lang.String.CASE_INSENSITIVE_ORDER; @@ -35,19 +38,19 @@ /** * Store of Gists */ +@Singleton public class GistStore extends ItemStore { private final ItemReferences gists = new ItemReferences<>(); - private Context context; + @Inject + protected GistService gistService; /** - * Create gist store - * - * @param context + * Create gist store. */ - public GistStore(final Context context) { - this.context = context; + @Inject + public GistStore() { } /** @@ -103,7 +106,7 @@ public Gist addGist(Gist gist) { * @return refreshed gist */ public Single refreshGist(String id) { - return ServiceGenerator.createService(context, GistService.class).getGist(id) + return gistService.getGist(id) .map(response -> addGist(response.body())); } @@ -120,7 +123,7 @@ public Single editGist(Gist gist) { .isPublic(gist.isPublic()) .build(); - return ServiceGenerator.createService(context, GistService.class).editGist(edit) + return gistService.editGist(edit) .map(response -> addGist(response.body())); } } diff --git a/app/src/main/java/com/github/pockethub/android/core/gist/RefreshGistTask.java b/app/src/main/java/com/github/pockethub/android/core/gist/RefreshGistTask.java index 7755c4dbb..4caca1090 100644 --- a/app/src/main/java/com/github/pockethub/android/core/gist/RefreshGistTask.java +++ b/app/src/main/java/com/github/pockethub/android/core/gist/RefreshGistTask.java @@ -19,12 +19,14 @@ import android.content.Context; import com.github.pockethub.android.util.HttpImageGetter; +import com.google.auto.factory.AutoFactory; +import com.google.auto.factory.Provided; import com.meisolsson.githubsdk.core.ServiceGenerator; import com.meisolsson.githubsdk.model.Gist; import com.meisolsson.githubsdk.model.GitHubComment; import com.meisolsson.githubsdk.service.gists.GistCommentService; import com.meisolsson.githubsdk.service.gists.GistService; -import com.google.inject.Inject; +import javax.inject.Inject; import java.io.IOException; import java.util.Collections; @@ -35,19 +37,18 @@ import io.reactivex.SingleEmitter; import io.reactivex.SingleOnSubscribe; import retrofit2.Response; -import retrofit2.http.Body; -import roboguice.RoboGuice; /** * Task to load and store a {@link Gist}. */ +@AutoFactory public class RefreshGistTask { private final Context context; + private final GistService service; - @Inject - private GistStore store; + private final GistStore store; private final String id; @@ -59,13 +60,13 @@ public class RefreshGistTask { * @param gistId * @param imageGetter */ - public RefreshGistTask(Activity activity, String gistId, - HttpImageGetter imageGetter) { - id = gistId; + public RefreshGistTask(@Provided GistStore store, @Provided HttpImageGetter imageGetter, + Activity activity, String gistId) { + this.store = store; + this.id = gistId; this.imageGetter = imageGetter; this.context = activity; this.service = ServiceGenerator.createService(context, GistService.class); - RoboGuice.injectMembers(activity, this); } public Single refresh() { diff --git a/app/src/main/java/com/github/pockethub/android/core/issue/IssuePager.java b/app/src/main/java/com/github/pockethub/android/core/issue/IssuePager.java deleted file mode 100644 index cd1f9f31b..000000000 --- a/app/src/main/java/com/github/pockethub/android/core/issue/IssuePager.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2015 PocketHub - * - * 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.pockethub.android.core.issue; - -import com.github.pockethub.android.core.ResourcePager; -import com.meisolsson.githubsdk.model.Issue; - -/** - * Helper class for showing more and more pages of issues - */ -public abstract class IssuePager extends ResourcePager { - - /** - * Store to add loaded issues to - */ - protected final IssueStore store; - - /** - * Create issue pager - * - * @param store - */ - public IssuePager(final IssueStore store) { - this.store = store; - - } - - @Override - protected Issue register(Issue resource) { - return store.addIssue(resource); - } - - @Override - protected Object getId(Issue resource) { - return resource.id(); - } -} diff --git a/app/src/main/java/com/github/pockethub/android/core/issue/IssueStore.java b/app/src/main/java/com/github/pockethub/android/core/issue/IssueStore.java index 4b5cf2ccb..9953a1845 100644 --- a/app/src/main/java/com/github/pockethub/android/core/issue/IssueStore.java +++ b/app/src/main/java/com/github/pockethub/android/core/issue/IssueStore.java @@ -35,28 +35,31 @@ import java.util.HashMap; import java.util.Map; +import javax.inject.Inject; +import javax.inject.Singleton; + import io.reactivex.Single; import retrofit2.Response; /** * Store of loaded issues */ +@Singleton public class IssueStore extends ItemStore { private final Map> repos = new HashMap<>(); - private final Context context; + @Inject + protected Context context; - private IssueService service; + @Inject + protected IssueService service; /** - * Create issue store - * - * @param context + * Create issue store. */ - public IssueStore(final Context context) { - this.context = context; - service = ServiceGenerator.createService(context, IssueService.class); + @Inject + public IssueStore() { } /** diff --git a/app/src/main/java/com/github/pockethub/android/core/issue/RefreshIssueTask.java b/app/src/main/java/com/github/pockethub/android/core/issue/RefreshIssueTask.java index afa6410e1..d2528fe02 100644 --- a/app/src/main/java/com/github/pockethub/android/core/issue/RefreshIssueTask.java +++ b/app/src/main/java/com/github/pockethub/android/core/issue/RefreshIssueTask.java @@ -17,9 +17,10 @@ import android.content.Context; -import com.github.pockethub.android.core.PageIterator; import com.github.pockethub.android.util.HttpImageGetter; import com.github.pockethub.android.util.RxPageUtil; +import com.google.auto.factory.AutoFactory; +import com.google.auto.factory.Provided; import com.meisolsson.githubsdk.core.ServiceGenerator; import com.meisolsson.githubsdk.model.GitHubComment; import com.meisolsson.githubsdk.model.Issue; @@ -29,7 +30,7 @@ import com.meisolsson.githubsdk.model.Repository; import com.meisolsson.githubsdk.service.issues.IssueCommentService; import com.meisolsson.githubsdk.service.issues.IssueEventService; -import com.google.inject.Inject; +import javax.inject.Inject; import com.meisolsson.githubsdk.service.pull_request.PullRequestService; import java.util.Collections; @@ -37,22 +38,19 @@ import io.reactivex.Observable; import io.reactivex.Single; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; import retrofit2.Response; -import roboguice.RoboGuice; /** * Task to load and store an {@link Issue} */ +@AutoFactory public class RefreshIssueTask { private static final String TAG = "RefreshIssueTask"; private final Context context; - @Inject - private IssueStore store; + private final IssueStore store; private final Repository repo; @@ -70,14 +68,17 @@ public class RefreshIssueTask { * @param issueNumber The issue's number * @param bodyImageGetter {@link HttpImageGetter} to fetch images for the bodies */ - public RefreshIssueTask(Context context, Repository repo, int issueNumber, - HttpImageGetter bodyImageGetter, HttpImageGetter commentImageGetter) { + public RefreshIssueTask(@Provided Context context, + Repository repo, int issueNumber, + @Provided HttpImageGetter bodyImageGetter, + @Provided HttpImageGetter commentImageGetter, + @Provided IssueStore store) { this.repo = repo; this.issueNumber = issueNumber; this.bodyImageGetter = bodyImageGetter; this.context = context; this.commentImageGetter = commentImageGetter; - RoboGuice.getInjector(context).injectMembers(this); + this.store = store; } /** diff --git a/app/src/main/java/com/github/pockethub/android/core/user/UserComparator.java b/app/src/main/java/com/github/pockethub/android/core/user/UserComparator.java index a8db2dbf8..66afe44e1 100644 --- a/app/src/main/java/com/github/pockethub/android/core/user/UserComparator.java +++ b/app/src/main/java/com/github/pockethub/android/core/user/UserComparator.java @@ -18,7 +18,7 @@ import android.accounts.Account; import com.meisolsson.githubsdk.model.User; -import com.google.inject.Inject; +import javax.inject.Inject; import java.util.Comparator; diff --git a/app/src/main/java/com/github/pockethub/android/core/user/UserPager.java b/app/src/main/java/com/github/pockethub/android/core/user/UserPager.java deleted file mode 100644 index 7c6716725..000000000 --- a/app/src/main/java/com/github/pockethub/android/core/user/UserPager.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2015 PocketHub - * - * 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.pockethub.android.core.user; - -import com.github.pockethub.android.core.ResourcePager; -import com.meisolsson.githubsdk.model.User; - -/** - * Pager over users - */ -public abstract class UserPager extends ResourcePager { - - @Override - protected Object getId(User resource) { - return resource.id(); - } -} diff --git a/app/src/main/java/com/github/pockethub/android/dagger/ActivityBuilder.java b/app/src/main/java/com/github/pockethub/android/dagger/ActivityBuilder.java new file mode 100644 index 000000000..780941a08 --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/dagger/ActivityBuilder.java @@ -0,0 +1,114 @@ +package com.github.pockethub.android.dagger; + +import com.github.pockethub.android.accounts.LoginActivity; +import com.github.pockethub.android.accounts.LoginWebViewActivity; +import com.github.pockethub.android.dagger.MainFragmentProvider; +import com.github.pockethub.android.ui.MainActivity; +import com.github.pockethub.android.ui.commit.CommitCompareViewActivity; +import com.github.pockethub.android.ui.commit.CommitFileViewActivity; +import com.github.pockethub.android.ui.commit.CommitViewActivity; +import com.github.pockethub.android.ui.gist.CreateGistActivity; +import com.github.pockethub.android.ui.gist.GistFilesViewActivity; +import com.github.pockethub.android.ui.gist.GistsPagerFragment; +import com.github.pockethub.android.ui.gist.GistsViewActivity; +import com.github.pockethub.android.ui.issue.EditIssueActivity; +import com.github.pockethub.android.ui.issue.EditIssuesFilterActivity; +import com.github.pockethub.android.ui.issue.FiltersViewActivity; +import com.github.pockethub.android.ui.issue.IssueBrowseActivity; +import com.github.pockethub.android.ui.issue.IssueSearchActivity; +import com.github.pockethub.android.ui.issue.IssuesViewActivity; +import com.github.pockethub.android.ui.notification.NotificationActivity; +import com.github.pockethub.android.ui.ref.BranchFileViewActivity; +import com.github.pockethub.android.ui.repo.RepositoryContributorsActivity; +import com.github.pockethub.android.ui.repo.RepositoryViewActivity; +import com.github.pockethub.android.ui.search.SearchActivity; +import com.github.pockethub.android.ui.user.UriLauncherActivity; +import com.github.pockethub.android.ui.user.UserViewActivity; + +import dagger.Module; +import dagger.android.ContributesAndroidInjector; + +@Module +public interface ActivityBuilder { + + @ContributesAndroidInjector(modules = MainFragmentProvider.class) + MainActivity mainActivity(); + + @ContributesAndroidInjector(modules = RepositoryViewFragmentProvider.class) + RepositoryViewActivity repositoryViewActivity(); + + @ContributesAndroidInjector(modules = IssuesViewFragmentProvider.class) + IssuesViewActivity issuesViewActivity(); + + @ContributesAndroidInjector(modules = NotificationFragmentProvider.class) + NotificationActivity notificationActivity(); + + @ContributesAndroidInjector + CreateGistActivity createGistActivity(); + + @ContributesAndroidInjector(modules = IssueBrowseFragmentProvider.class) + IssueBrowseActivity issueBrowseActivity(); + + @ContributesAndroidInjector + EditIssuesFilterActivity editIssuesFilterActivity(); + + @ContributesAndroidInjector + EditIssueActivity editIssueActivity(); + + @ContributesAndroidInjector(modules = SearchActivityFragmentProvider.class) + SearchActivity searchActivity(); + + @ContributesAndroidInjector(modules = FiltersViewFragmentProvider.class) + FiltersViewActivity filtersViewActivity(); + + @ContributesAndroidInjector(modules = GistsViewFragmentProvider.class) + GistsViewActivity gistsViewActivity(); + + @ContributesAndroidInjector(modules = GistFilesViewFragmentProvider.class) + GistFilesViewActivity gistFilesViewActivity(); + + @ContributesAndroidInjector(modules = CreateCommentFragmentProvider.class) + com.github.pockethub.android.ui.gist.CreateCommentActivity createGistCommentActivity(); + + @ContributesAndroidInjector(modules = CreateCommentFragmentProvider.class) + com.github.pockethub.android.ui.issue.CreateCommentActivity createIssueCommentActivity(); + + @ContributesAndroidInjector(modules = CreateCommentFragmentProvider.class) + com.github.pockethub.android.ui.commit.CreateCommentActivity createCommitCommentActivity(); + + @ContributesAndroidInjector(modules = CreateCommentFragmentProvider.class) + com.github.pockethub.android.ui.gist.EditCommentActivity editGistCommentActivity(); + + @ContributesAndroidInjector(modules = CreateCommentFragmentProvider.class) + com.github.pockethub.android.ui.issue.EditCommentActivity editIssueCommentActivity(); + + @ContributesAndroidInjector(modules = RepositoryContributorsFragmentProvider.class) + RepositoryContributorsActivity repositoryContributorsActivity(); + + @ContributesAndroidInjector(modules = UserViewFragmentProvider.class) + UserViewActivity userViewActivity(); + + @ContributesAndroidInjector + LoginActivity loginActivity(); + + @ContributesAndroidInjector + UriLauncherActivity uriLauncherActivity(); + + @ContributesAndroidInjector(modules = IssueSearchFragmentProvider.class) + IssueSearchActivity issueSearchActivity(); + + @ContributesAndroidInjector(modules = CommitCompareViewFragmentProvider.class) + CommitCompareViewActivity commitCompareViewActivity(); + + @ContributesAndroidInjector(modules = CommitViewFragmentProvider.class) + CommitViewActivity commitViewActivity(); + + @ContributesAndroidInjector + CommitFileViewActivity commitFileViewActivity(); + + @ContributesAndroidInjector + BranchFileViewActivity branchFileViewActivity(); + + @ContributesAndroidInjector + LoginWebViewActivity loginWebViewActivity(); +} \ No newline at end of file diff --git a/app/src/main/java/com/github/pockethub/android/dagger/ActivityScope.java b/app/src/main/java/com/github/pockethub/android/dagger/ActivityScope.java new file mode 100644 index 000000000..e1e8f3f9f --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/dagger/ActivityScope.java @@ -0,0 +1,11 @@ +package com.github.pockethub.android.dagger; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Scope; + +@Scope +@Retention(RetentionPolicy.RUNTIME) +public @interface ActivityScope { +} diff --git a/app/src/main/java/com/github/pockethub/android/dagger/CommitCompareViewFragmentProvider.java b/app/src/main/java/com/github/pockethub/android/dagger/CommitCompareViewFragmentProvider.java new file mode 100644 index 000000000..e2de68d4a --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/dagger/CommitCompareViewFragmentProvider.java @@ -0,0 +1,13 @@ +package com.github.pockethub.android.dagger; + +import com.github.pockethub.android.ui.commit.CommitCompareListFragment; + +import dagger.Module; +import dagger.android.ContributesAndroidInjector; + +@Module +interface CommitCompareViewFragmentProvider { + + @ContributesAndroidInjector + CommitCompareListFragment commitCompareListFragment(); +} diff --git a/app/src/main/java/com/github/pockethub/android/dagger/CommitViewFragmentProvider.java b/app/src/main/java/com/github/pockethub/android/dagger/CommitViewFragmentProvider.java new file mode 100644 index 000000000..d43ffbd48 --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/dagger/CommitViewFragmentProvider.java @@ -0,0 +1,13 @@ +package com.github.pockethub.android.dagger; + +import com.github.pockethub.android.ui.commit.CommitDiffListFragment; + +import dagger.Module; +import dagger.android.ContributesAndroidInjector; + +@Module +interface CommitViewFragmentProvider { + + @ContributesAndroidInjector + CommitDiffListFragment commitDiffListFragment(); +} diff --git a/app/src/main/java/com/github/pockethub/android/dagger/CreateCommentFragmentProvider.java b/app/src/main/java/com/github/pockethub/android/dagger/CreateCommentFragmentProvider.java new file mode 100644 index 000000000..7a2f169a8 --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/dagger/CreateCommentFragmentProvider.java @@ -0,0 +1,17 @@ +package com.github.pockethub.android.dagger; + +import com.github.pockethub.android.ui.comment.RawCommentFragment; +import com.github.pockethub.android.ui.comment.RenderedCommentFragment; + +import dagger.Module; +import dagger.android.ContributesAndroidInjector; + +@Module +interface CreateCommentFragmentProvider { + + @ContributesAndroidInjector + RawCommentFragment rawCommentFragment(); + + @ContributesAndroidInjector + RenderedCommentFragment renderedCommentFragment(); +} diff --git a/app/src/main/java/com/github/pockethub/android/dagger/DialogFragmentBuilder.java b/app/src/main/java/com/github/pockethub/android/dagger/DialogFragmentBuilder.java new file mode 100644 index 000000000..44517ed1c --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/dagger/DialogFragmentBuilder.java @@ -0,0 +1,29 @@ +package com.github.pockethub.android.dagger; + +import com.github.pockethub.android.ui.ConfirmDialogFragment; +import com.github.pockethub.android.ui.issue.AssigneeDialogFragment; +import com.github.pockethub.android.ui.issue.LabelsDialogFragment; +import com.github.pockethub.android.ui.issue.MilestoneDialogFragment; +import com.github.pockethub.android.ui.ref.RefDialogFragment; + +import dagger.Module; +import dagger.android.ContributesAndroidInjector; + +@Module +public interface DialogFragmentBuilder { + + @ContributesAndroidInjector + LabelsDialogFragment labelsDialogFragment(); + + @ContributesAndroidInjector + AssigneeDialogFragment assigneeDialogFragment(); + + @ContributesAndroidInjector + MilestoneDialogFragment milestoneDialogFragment(); + + @ContributesAndroidInjector + RefDialogFragment refDialogFragment(); + + @ContributesAndroidInjector + ConfirmDialogFragment confirmDialogFragment(); +} diff --git a/app/src/main/java/com/github/pockethub/android/dagger/FiltersViewFragmentProvider.java b/app/src/main/java/com/github/pockethub/android/dagger/FiltersViewFragmentProvider.java new file mode 100644 index 000000000..583dcc8c4 --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/dagger/FiltersViewFragmentProvider.java @@ -0,0 +1,13 @@ +package com.github.pockethub.android.dagger; + +import com.github.pockethub.android.ui.issue.FilterListFragment; + +import dagger.Module; +import dagger.android.ContributesAndroidInjector; + +@Module +interface FiltersViewFragmentProvider { + + @ContributesAndroidInjector + FilterListFragment filterListFragment(); +} diff --git a/app/src/main/java/com/github/pockethub/android/dagger/GistFilesViewFragmentProvider.java b/app/src/main/java/com/github/pockethub/android/dagger/GistFilesViewFragmentProvider.java new file mode 100644 index 000000000..5f0055387 --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/dagger/GistFilesViewFragmentProvider.java @@ -0,0 +1,13 @@ +package com.github.pockethub.android.dagger; + +import com.github.pockethub.android.ui.gist.GistFileFragment; + +import dagger.Module; +import dagger.android.ContributesAndroidInjector; + +@Module +interface GistFilesViewFragmentProvider { + + @ContributesAndroidInjector + GistFileFragment gistFileFragment(); +} diff --git a/app/src/main/java/com/github/pockethub/android/dagger/GistsPagerFragmentProvider.java b/app/src/main/java/com/github/pockethub/android/dagger/GistsPagerFragmentProvider.java new file mode 100644 index 000000000..9e2b50abf --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/dagger/GistsPagerFragmentProvider.java @@ -0,0 +1,21 @@ +package com.github.pockethub.android.dagger; + +import com.github.pockethub.android.ui.gist.MyGistsFragment; +import com.github.pockethub.android.ui.gist.PublicGistsFragment; +import com.github.pockethub.android.ui.gist.StarredGistsFragment; + +import dagger.Module; +import dagger.android.ContributesAndroidInjector; + +@Module +interface GistsPagerFragmentProvider { + + @ContributesAndroidInjector + MyGistsFragment myGistsFragment(); + + @ContributesAndroidInjector + PublicGistsFragment publicGistsFragment(); + + @ContributesAndroidInjector + StarredGistsFragment starredGistsFragment(); +} diff --git a/app/src/main/java/com/github/pockethub/android/dagger/GistsViewFragmentProvider.java b/app/src/main/java/com/github/pockethub/android/dagger/GistsViewFragmentProvider.java new file mode 100644 index 000000000..9111a5c57 --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/dagger/GistsViewFragmentProvider.java @@ -0,0 +1,13 @@ +package com.github.pockethub.android.dagger; + +import com.github.pockethub.android.ui.gist.GistFragment; + +import dagger.Module; +import dagger.android.ContributesAndroidInjector; + +@Module +interface GistsViewFragmentProvider { + + @ContributesAndroidInjector + GistFragment gistFragment(); +} diff --git a/app/src/main/java/com/github/pockethub/android/dagger/HomePagerFragmentProvider.java b/app/src/main/java/com/github/pockethub/android/dagger/HomePagerFragmentProvider.java new file mode 100644 index 000000000..5d4717d79 --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/dagger/HomePagerFragmentProvider.java @@ -0,0 +1,33 @@ +package com.github.pockethub.android.dagger; + +import com.github.pockethub.android.ui.repo.RepositoryListFragment; +import com.github.pockethub.android.ui.user.MembersFragment; +import com.github.pockethub.android.ui.user.MyFollowersFragment; +import com.github.pockethub.android.ui.user.MyFollowingFragment; +import com.github.pockethub.android.ui.user.OrganizationNewsFragment; +import com.github.pockethub.android.ui.user.UserReceivedNewsFragment; + +import dagger.Module; +import dagger.android.ContributesAndroidInjector; + +@Module +interface HomePagerFragmentProvider { + + @ContributesAndroidInjector + UserReceivedNewsFragment userReceivedNewsFragment(); + + @ContributesAndroidInjector + OrganizationNewsFragment organizationNewsFragment(); + + @ContributesAndroidInjector + RepositoryListFragment repositoryListFragment(); + + @ContributesAndroidInjector + MyFollowersFragment myFollowersFragment(); + + @ContributesAndroidInjector + MyFollowingFragment myFollowingFragment(); + + @ContributesAndroidInjector + MembersFragment membersFragment(); +} diff --git a/app/src/main/java/com/github/pockethub/android/dagger/IssueBrowseFragmentProvider.java b/app/src/main/java/com/github/pockethub/android/dagger/IssueBrowseFragmentProvider.java new file mode 100644 index 000000000..b5be53d2f --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/dagger/IssueBrowseFragmentProvider.java @@ -0,0 +1,13 @@ +package com.github.pockethub.android.dagger; + +import com.github.pockethub.android.ui.issue.IssuesFragment; + +import dagger.Module; +import dagger.android.ContributesAndroidInjector; + +@Module +interface IssueBrowseFragmentProvider { + + @ContributesAndroidInjector + IssuesFragment issuesFragment(); +} diff --git a/app/src/main/java/com/github/pockethub/android/dagger/IssueDashboardPagerFragmentProvider.java b/app/src/main/java/com/github/pockethub/android/dagger/IssueDashboardPagerFragmentProvider.java new file mode 100644 index 000000000..ea0a38d5c --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/dagger/IssueDashboardPagerFragmentProvider.java @@ -0,0 +1,13 @@ +package com.github.pockethub.android.dagger; + +import com.github.pockethub.android.ui.issue.DashboardIssueFragment; + +import dagger.Module; +import dagger.android.ContributesAndroidInjector; + +@Module +interface IssueDashboardPagerFragmentProvider { + + @ContributesAndroidInjector + DashboardIssueFragment dashboardIssueFragment(); +} diff --git a/app/src/main/java/com/github/pockethub/android/dagger/IssueSearchFragmentProvider.java b/app/src/main/java/com/github/pockethub/android/dagger/IssueSearchFragmentProvider.java new file mode 100644 index 000000000..b4f0049a9 --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/dagger/IssueSearchFragmentProvider.java @@ -0,0 +1,13 @@ +package com.github.pockethub.android.dagger; + +import com.github.pockethub.android.ui.issue.SearchIssueListFragment; + +import dagger.Module; +import dagger.android.ContributesAndroidInjector; + +@Module +interface IssueSearchFragmentProvider { + + @ContributesAndroidInjector + SearchIssueListFragment searchIssueListFragment(); +} diff --git a/app/src/main/java/com/github/pockethub/android/dagger/IssuesViewFragmentProvider.java b/app/src/main/java/com/github/pockethub/android/dagger/IssuesViewFragmentProvider.java new file mode 100644 index 000000000..a9d79d971 --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/dagger/IssuesViewFragmentProvider.java @@ -0,0 +1,13 @@ +package com.github.pockethub.android.dagger; + +import com.github.pockethub.android.ui.issue.IssueFragment; + +import dagger.Module; +import dagger.android.ContributesAndroidInjector; + +@Module +interface IssuesViewFragmentProvider { + + @ContributesAndroidInjector + IssueFragment issueFragment(); +} diff --git a/app/src/main/java/com/github/pockethub/android/dagger/MainFragmentProvider.java b/app/src/main/java/com/github/pockethub/android/dagger/MainFragmentProvider.java new file mode 100644 index 000000000..11fc4aec2 --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/dagger/MainFragmentProvider.java @@ -0,0 +1,30 @@ +package com.github.pockethub.android.dagger; + +import com.github.pockethub.android.ui.gist.GistsPagerFragment; +import com.github.pockethub.android.ui.issue.FilterListFragment; +import com.github.pockethub.android.ui.issue.IssueDashboardPagerFragment; +import com.github.pockethub.android.ui.repo.RepositoryListFragment; +import com.github.pockethub.android.ui.user.HomePagerFragment; +import com.github.pockethub.android.ui.user.MyFollowersFragment; +import com.github.pockethub.android.ui.user.MyFollowingFragment; +import com.github.pockethub.android.ui.user.OrganizationNewsFragment; +import com.github.pockethub.android.ui.user.UserReceivedNewsFragment; + +import dagger.Module; +import dagger.android.ContributesAndroidInjector; + +@Module +public interface MainFragmentProvider { + + @ContributesAndroidInjector(modules = HomePagerFragmentProvider.class) + HomePagerFragment homePagerFragment(); + + @ContributesAndroidInjector(modules = GistsPagerFragmentProvider.class) + GistsPagerFragment gistsPagerFragment(); + + @ContributesAndroidInjector(modules = IssueDashboardPagerFragmentProvider.class) + IssueDashboardPagerFragment issueDashboardPagerFragment(); + + @ContributesAndroidInjector + FilterListFragment filterListFragment(); +} diff --git a/app/src/main/java/com/github/pockethub/android/dagger/NotificationFragmentProvider.java b/app/src/main/java/com/github/pockethub/android/dagger/NotificationFragmentProvider.java new file mode 100644 index 000000000..1725bc1d5 --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/dagger/NotificationFragmentProvider.java @@ -0,0 +1,14 @@ +package com.github.pockethub.android.dagger; + +import com.github.pockethub.android.ui.notification.NotificationListFragment; + +import dagger.Module; +import dagger.android.ContributesAndroidInjector; + +@Module +interface NotificationFragmentProvider { + + @ContributesAndroidInjector + NotificationListFragment notificationListFragment(); + +} diff --git a/app/src/main/java/com/github/pockethub/android/dagger/RepositoryContributorsFragmentProvider.java b/app/src/main/java/com/github/pockethub/android/dagger/RepositoryContributorsFragmentProvider.java new file mode 100644 index 000000000..a63133de0 --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/dagger/RepositoryContributorsFragmentProvider.java @@ -0,0 +1,13 @@ +package com.github.pockethub.android.dagger; + +import com.github.pockethub.android.ui.repo.RepositoryContributorsFragment; + +import dagger.Module; +import dagger.android.ContributesAndroidInjector; + +@Module +interface RepositoryContributorsFragmentProvider { + + @ContributesAndroidInjector + RepositoryContributorsFragment repositoryContributorsFragment(); +} diff --git a/app/src/main/java/com/github/pockethub/android/dagger/RepositoryViewFragmentProvider.java b/app/src/main/java/com/github/pockethub/android/dagger/RepositoryViewFragmentProvider.java new file mode 100644 index 000000000..6ed933199 --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/dagger/RepositoryViewFragmentProvider.java @@ -0,0 +1,29 @@ +package com.github.pockethub.android.dagger; + +import com.github.pockethub.android.ui.code.RepositoryCodeFragment; +import com.github.pockethub.android.ui.commit.CommitListFragment; +import com.github.pockethub.android.ui.issue.IssuesFragment; +import com.github.pockethub.android.ui.repo.RepositoryNewsFragment; +import com.github.pockethub.android.ui.repo.RepositoryReadmeFragment; + +import dagger.Module; +import dagger.android.ContributesAndroidInjector; + +@Module +interface RepositoryViewFragmentProvider { + + @ContributesAndroidInjector + RepositoryReadmeFragment repositoryReadmeFragment(); + + @ContributesAndroidInjector + RepositoryNewsFragment repositoryNewsFragment(); + + @ContributesAndroidInjector + RepositoryCodeFragment repositoryCodeFragment(); + + @ContributesAndroidInjector + CommitListFragment commitListFragment(); + + @ContributesAndroidInjector + IssuesFragment issuesFragment(); +} diff --git a/app/src/main/java/com/github/pockethub/android/dagger/SearchActivityFragmentProvider.java b/app/src/main/java/com/github/pockethub/android/dagger/SearchActivityFragmentProvider.java new file mode 100644 index 000000000..fa3763353 --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/dagger/SearchActivityFragmentProvider.java @@ -0,0 +1,18 @@ +package com.github.pockethub.android.dagger; + +import com.github.pockethub.android.ui.search.SearchRepositoryListFragment; +import com.github.pockethub.android.ui.search.SearchUserListFragment; + +import dagger.Module; +import dagger.android.ContributesAndroidInjector; + +@Module +interface SearchActivityFragmentProvider { + + @ContributesAndroidInjector + SearchRepositoryListFragment searchRepositoryListFragment(); + + @ContributesAndroidInjector + SearchUserListFragment searchUserListFragment(); + +} diff --git a/app/src/main/java/com/github/pockethub/android/dagger/ServiceBuilder.java b/app/src/main/java/com/github/pockethub/android/dagger/ServiceBuilder.java new file mode 100644 index 000000000..ae004b072 --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/dagger/ServiceBuilder.java @@ -0,0 +1,13 @@ +package com.github.pockethub.android.dagger; + +import com.github.pockethub.android.sync.SyncAdapterService; + +import dagger.Module; +import dagger.android.ContributesAndroidInjector; + +@Module +public interface ServiceBuilder { + + @ContributesAndroidInjector + SyncAdapterService provideSyncAdapterService(); +} diff --git a/app/src/main/java/com/github/pockethub/android/dagger/UserViewFragmentProvider.java b/app/src/main/java/com/github/pockethub/android/dagger/UserViewFragmentProvider.java new file mode 100644 index 000000000..b95a7e5ef --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/dagger/UserViewFragmentProvider.java @@ -0,0 +1,26 @@ +package com.github.pockethub.android.dagger; + +import com.github.pockethub.android.ui.repo.UserRepositoryListFragment; +import com.github.pockethub.android.ui.user.UserCreatedNewsFragment; +import com.github.pockethub.android.ui.user.UserFollowersFragment; +import com.github.pockethub.android.ui.user.UserFollowingFragment; + +import dagger.Module; +import dagger.android.ContributesAndroidInjector; + +@Module +interface UserViewFragmentProvider { + + @ContributesAndroidInjector + UserCreatedNewsFragment userCreatedNewsFragment(); + + @ContributesAndroidInjector + UserRepositoryListFragment userRepositoryListFragment(); + + @ContributesAndroidInjector + UserFollowersFragment userFollowersFragment(); + + @ContributesAndroidInjector + UserFollowingFragment userFollowingFragment(); + +} diff --git a/app/src/main/java/com/github/pockethub/android/persistence/AccountDataManager.java b/app/src/main/java/com/github/pockethub/android/persistence/AccountDataManager.java index b297c26f6..2bdbaa76f 100644 --- a/app/src/main/java/com/github/pockethub/android/persistence/AccountDataManager.java +++ b/app/src/main/java/com/github/pockethub/android/persistence/AccountDataManager.java @@ -24,11 +24,10 @@ import com.github.pockethub.android.RequestReader; import com.github.pockethub.android.RequestWriter; import com.github.pockethub.android.core.issue.IssueFilter; -import com.github.pockethub.android.persistence.OrganizationRepositories.Factory; import com.meisolsson.githubsdk.model.Repository; import com.meisolsson.githubsdk.model.User; -import com.google.inject.Inject; -import com.google.inject.name.Named; +import javax.inject.Inject; +import javax.inject.Named; import java.io.File; import java.io.IOException; @@ -59,20 +58,23 @@ public class AccountDataManager { private static final int FORMAT_VERSION = 4; @Inject - private Context context; + protected Context context; @Inject - private DatabaseCache dbCache; + protected DatabaseCache dbCache; @Inject - private Factory allRepos; + protected OrganizationRepositoriesFactory allRepos; @Inject - private Organizations userAndOrgsResource; + protected Organizations userAndOrgsResource; @Inject @Named("cacheDir") - private File root; + protected File root; + + @Inject + public AccountDataManager() {} /** * @return context @@ -173,7 +175,7 @@ public List getOrgs(boolean forceReload) throws IOException { */ public List getRepos(final User user, boolean forceReload) throws IOException { - OrganizationRepositories resource = allRepos.under(user); + OrganizationRepositories resource = allRepos.create(user); return forceReload ? dbCache.requestAndStore(resource) : dbCache .loadOrRequest(resource); } diff --git a/app/src/main/java/com/github/pockethub/android/persistence/CacheHelper.java b/app/src/main/java/com/github/pockethub/android/persistence/CacheHelper.java index 891066000..2449d8713 100644 --- a/app/src/main/java/com/github/pockethub/android/persistence/CacheHelper.java +++ b/app/src/main/java/com/github/pockethub/android/persistence/CacheHelper.java @@ -19,7 +19,7 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; -import com.google.inject.Inject; +import javax.inject.Inject; /** * Helper class to create & upgrade database cache tables diff --git a/app/src/main/java/com/github/pockethub/android/persistence/DatabaseCache.java b/app/src/main/java/com/github/pockethub/android/persistence/DatabaseCache.java index 9cbf9e139..bf43d1a24 100644 --- a/app/src/main/java/com/github/pockethub/android/persistence/DatabaseCache.java +++ b/app/src/main/java/com/github/pockethub/android/persistence/DatabaseCache.java @@ -21,8 +21,8 @@ import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; -import com.google.inject.Inject; -import com.google.inject.Provider; +import javax.inject.Inject; +import javax.inject.Provider; import java.io.IOException; import java.util.ArrayList; @@ -37,7 +37,10 @@ public class DatabaseCache { private static final String TAG = "DatabaseCache"; @Inject - private Provider helperProvider; + protected Provider helperProvider; + + @Inject + public DatabaseCache() {} /** * Get writable database diff --git a/app/src/main/java/com/github/pockethub/android/persistence/OrganizationRepositories.java b/app/src/main/java/com/github/pockethub/android/persistence/OrganizationRepositories.java index b18c673bb..f4d0c89af 100644 --- a/app/src/main/java/com/github/pockethub/android/persistence/OrganizationRepositories.java +++ b/app/src/main/java/com/github/pockethub/android/persistence/OrganizationRepositories.java @@ -23,6 +23,8 @@ import android.database.sqlite.SQLiteQueryBuilder; import com.github.pockethub.android.core.PageIterator; +import com.google.auto.factory.AutoFactory; +import com.google.auto.factory.Provided; import com.meisolsson.githubsdk.core.ServiceGenerator; import com.meisolsson.githubsdk.model.Page; import com.meisolsson.githubsdk.model.Permissions; @@ -30,9 +32,8 @@ import com.meisolsson.githubsdk.model.User; import com.meisolsson.githubsdk.service.activity.WatchingService; import com.meisolsson.githubsdk.service.repositories.RepositoryService; -import com.google.inject.Inject; -import com.google.inject.Provider; -import com.google.inject.assistedinject.Assisted; +import javax.inject.Inject; +import javax.inject.Provider; import java.io.IOException; import java.util.ArrayList; @@ -45,22 +46,8 @@ /** * Cache of repositories under a given organization */ -public class OrganizationRepositories implements - PersistableResource { - - /** - * Creation factory - */ - public interface Factory { - - /** - * Get repositories under given organization - * - * @param org - * @return repositories - */ - OrganizationRepositories under(User org); - } +@AutoFactory +public class OrganizationRepositories implements PersistableResource { private final User org; @@ -75,9 +62,9 @@ public interface Factory { * @param context * @param accountProvider */ - @Inject - public OrganizationRepositories(@Assisted User orgs, Context context, - Provider accountProvider) { + public OrganizationRepositories(User orgs, + @Provided Context context, + @Provided Provider accountProvider) { this.org = orgs; this.context = context; this.accountProvider = accountProvider; diff --git a/app/src/main/java/com/github/pockethub/android/persistence/Organizations.java b/app/src/main/java/com/github/pockethub/android/persistence/Organizations.java index 366d2e893..2b3ee036b 100644 --- a/app/src/main/java/com/github/pockethub/android/persistence/Organizations.java +++ b/app/src/main/java/com/github/pockethub/android/persistence/Organizations.java @@ -26,7 +26,7 @@ import com.meisolsson.githubsdk.model.User; import com.meisolsson.githubsdk.service.organizations.OrganizationService; import com.meisolsson.githubsdk.service.users.UserService; -import com.google.inject.Inject; +import javax.inject.Inject; import java.io.IOException; import java.util.ArrayList; diff --git a/app/src/main/java/com/github/pockethub/android/rx/AutoDisposeUtils.java b/app/src/main/java/com/github/pockethub/android/rx/AutoDisposeUtils.java new file mode 100644 index 000000000..408d68e61 --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/rx/AutoDisposeUtils.java @@ -0,0 +1,30 @@ +package com.github.pockethub.android.rx; + +import android.arch.lifecycle.Lifecycle; +import android.arch.lifecycle.LifecycleOwner; + +import com.uber.autodispose.AutoDispose; +import com.uber.autodispose.AutoDisposeConverter; +import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider; + +public class AutoDisposeUtils { + + public static AutoDisposeConverter bindToLifecycle(LifecycleOwner lifecycleOwner) { + return bindToLifecycle(lifecycleOwner.getLifecycle()); + } + + public static AutoDisposeConverter bindToLifecycle(Lifecycle lifecycle) { + return AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(lifecycle)); + } + + public static AutoDisposeConverter bindToLifecycle(LifecycleOwner lifecycleOwner, + Lifecycle.Event untilEvent) { + return bindToLifecycle(lifecycleOwner.getLifecycle(), untilEvent); + } + + public static AutoDisposeConverter bindToLifecycle(Lifecycle lifecycle, + Lifecycle.Event event) { + + return AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(lifecycle, event)); + } +} diff --git a/app/src/main/java/com/github/pockethub/android/sync/SyncAdapter.java b/app/src/main/java/com/github/pockethub/android/sync/SyncAdapter.java index 457defe0b..de456153b 100644 --- a/app/src/main/java/com/github/pockethub/android/sync/SyncAdapter.java +++ b/app/src/main/java/com/github/pockethub/android/sync/SyncAdapter.java @@ -22,23 +22,17 @@ import android.content.SyncResult; import android.os.Bundle; -import com.github.pockethub.android.sync.SyncCampaign.Factory; -import com.google.inject.Inject; - -import roboguice.inject.ContextScope; -import roboguice.inject.ContextSingleton; +import javax.inject.Inject; +import javax.inject.Singleton; /** * Sync adapter */ -@ContextSingleton +@Singleton public class SyncAdapter extends AbstractThreadedSyncAdapter { @Inject - private ContextScope contextScope; - - @Inject - private Factory campaignFactory; + protected SyncCampaignFactory campaignFactory; private SyncCampaign campaign = null; @@ -56,14 +50,9 @@ public SyncAdapter(final Context context) { public void onPerformSync(final Account account, final Bundle extras, final String authority, final ContentProviderClient provider, final SyncResult syncResult) { - contextScope.enter(getContext()); - try { - cancelCampaign(); - campaign = campaignFactory.create(syncResult); - campaign.run(); - } finally { - contextScope.exit(getContext()); - } + cancelCampaign(); + campaign = campaignFactory.create(syncResult); + campaign.run(); } @Override diff --git a/app/src/main/java/com/github/pockethub/android/sync/SyncAdapterService.java b/app/src/main/java/com/github/pockethub/android/sync/SyncAdapterService.java index 3fc69bb74..ee1da755e 100644 --- a/app/src/main/java/com/github/pockethub/android/sync/SyncAdapterService.java +++ b/app/src/main/java/com/github/pockethub/android/sync/SyncAdapterService.java @@ -18,21 +18,21 @@ import android.content.Intent; import android.os.IBinder; -import com.google.inject.Inject; +import javax.inject.Inject; +import javax.inject.Provider; -import roboguice.inject.ContextScopedProvider; -import roboguice.service.RoboService; +import dagger.android.DaggerService; /** * Sync adapter service */ -public class SyncAdapterService extends RoboService { +public class SyncAdapterService extends DaggerService { @Inject - private ContextScopedProvider syncAdapterProvider; + protected Provider syncAdapterProvider; @Override public IBinder onBind(Intent intent) { - return syncAdapterProvider.get(this).getSyncAdapterBinder(); + return syncAdapterProvider.get().getSyncAdapterBinder(); } } diff --git a/app/src/main/java/com/github/pockethub/android/sync/SyncCampaign.java b/app/src/main/java/com/github/pockethub/android/sync/SyncCampaign.java index cafc5c90f..718605b9d 100644 --- a/app/src/main/java/com/github/pockethub/android/sync/SyncCampaign.java +++ b/app/src/main/java/com/github/pockethub/android/sync/SyncCampaign.java @@ -21,10 +21,12 @@ import com.github.pockethub.android.persistence.DatabaseCache; import com.github.pockethub.android.persistence.OrganizationRepositories; +import com.github.pockethub.android.persistence.OrganizationRepositoriesFactory; import com.github.pockethub.android.persistence.Organizations; +import com.google.auto.factory.AutoFactory; +import com.google.auto.factory.Provided; import com.meisolsson.githubsdk.model.User; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import javax.inject.Inject; import java.io.IOException; import java.util.List; @@ -32,32 +34,19 @@ /** * A cancelable sync operation to synchronize data for a given account */ +@AutoFactory public class SyncCampaign implements Runnable { private static final String TAG = "SyncCampaign"; - /** - * Factory to create campaign - */ - public interface Factory { - - /** - * Create campaign for result - * - * @param syncResult - * @return campaign - */ - SyncCampaign create(SyncResult syncResult); - } - @Inject - private DatabaseCache cache; + protected DatabaseCache cache; @Inject - private OrganizationRepositories.Factory repos; + protected OrganizationRepositoriesFactory repos; @Inject - private Organizations persistedOrgs; + protected Organizations persistedOrgs; private final SyncResult syncResult; @@ -68,8 +57,7 @@ public interface Factory { * * @param syncResult */ - @Inject - public SyncCampaign(@Assisted SyncResult syncResult) { + public SyncCampaign(SyncResult syncResult) { this.syncResult = syncResult; } @@ -93,7 +81,7 @@ public void run() { Log.d(TAG, "Syncing repos for " + org.login()); try { - cache.requestAndStore(repos.under(org)); + cache.requestAndStore(repos.create(org)); syncResult.stats.numUpdates++; } catch (IOException | SQLException e) { syncResult.stats.numIoExceptions++; diff --git a/app/src/main/java/com/github/pockethub/android/ui/BaseActivity.java b/app/src/main/java/com/github/pockethub/android/ui/BaseActivity.java index 1ecbf4e70..8cd3ac187 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/BaseActivity.java +++ b/app/src/main/java/com/github/pockethub/android/ui/BaseActivity.java @@ -18,81 +18,40 @@ import android.os.Bundle; import android.os.Parcelable; import android.support.annotation.CallSuper; - -import com.github.pockethub.android.ui.roboactivities.RoboAppCompatActivity; -import com.trello.rxlifecycle2.LifecycleProvider; -import com.trello.rxlifecycle2.LifecycleTransformer; -import com.trello.rxlifecycle2.RxLifecycle; -import com.trello.rxlifecycle2.android.ActivityEvent; -import com.trello.rxlifecycle2.android.RxLifecycleAndroid; - +import android.support.annotation.LayoutRes; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; + +import com.github.pockethub.android.R; +import com.uber.autodispose.AutoDispose; +import com.uber.autodispose.AutoDisposeConverter; +import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider; + +import butterknife.BindView; +import butterknife.ButterKnife; +import dagger.android.support.DaggerAppCompatActivity; import io.reactivex.Observable; import io.reactivex.subjects.BehaviorSubject; /** * Activity that display dialogs */ -public abstract class BaseActivity extends - RoboAppCompatActivity implements DialogResultListener, LifecycleProvider { +public abstract class BaseActivity extends DaggerAppCompatActivity implements DialogResultListener { - private final BehaviorSubject lifecycleSubject = BehaviorSubject.create(); + @BindView(R.id.toolbar) + protected Toolbar toolbar; - @CallSuper @Override - protected void onCreate(Bundle savedInstanceState) { + protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - lifecycleSubject.onNext(ActivityEvent.CREATE); - } - - - @Override - public final Observable lifecycle() { - return lifecycleSubject; + setContentView(getContentView()); + ButterKnife.bind(this); + setSupportActionBar(toolbar); } - @Override - public final LifecycleTransformer bindUntilEvent(ActivityEvent event) { - return RxLifecycle.bindUntilEvent(lifecycleSubject, event); - } - - @Override - public final LifecycleTransformer bindToLifecycle() { - return RxLifecycleAndroid.bindActivity(lifecycleSubject); - } - - @Override - @CallSuper - protected void onStart() { - super.onStart(); - lifecycleSubject.onNext(ActivityEvent.START); - } - - @Override - @CallSuper - protected void onResume() { - super.onResume(); - lifecycleSubject.onNext(ActivityEvent.RESUME); - } - - @Override - @CallSuper - protected void onPause() { - lifecycleSubject.onNext(ActivityEvent.PAUSE); - super.onPause(); - } - - @Override - @CallSuper - protected void onStop() { - lifecycleSubject.onNext(ActivityEvent.STOP); - super.onStop(); - } - - @Override - @CallSuper - protected void onDestroy() { - lifecycleSubject.onNext(ActivityEvent.DESTROY); - super.onDestroy(); + public Toolbar getToolbar() { + return toolbar; } /** @@ -170,4 +129,12 @@ protected CharSequence[] getCharSequenceArrayExtra(final String name) { public void onDialogResult(int requestCode, int resultCode, Bundle arguments) { // Intentionally left blank } + + /** + * Get content view to be used when {@link #onCreate(Bundle)} is called. + * + * @return layout resource id + */ + @LayoutRes + protected abstract int getContentView(); } diff --git a/app/src/main/java/com/github/pockethub/android/ui/DialogFragment.java b/app/src/main/java/com/github/pockethub/android/ui/DialogFragment.java index a48eafd4f..5d0ae31b9 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/DialogFragment.java +++ b/app/src/main/java/com/github/pockethub/android/ui/DialogFragment.java @@ -21,12 +21,12 @@ import android.support.annotation.StringRes; import com.afollestad.materialdialogs.MaterialDialog; -import com.github.pockethub.android.ui.roboactivities.RoboSupportFragment; +import com.github.pockethub.android.ui.base.BaseFragment; /** * Base fragment capable of receiving dialog callbacks */ -public abstract class DialogFragment extends RoboSupportFragment implements +public abstract class DialogFragment extends BaseFragment implements DialogResultListener { private MaterialDialog progressDialog; diff --git a/app/src/main/java/com/github/pockethub/android/ui/DialogFragmentHelper.java b/app/src/main/java/com/github/pockethub/android/ui/DialogFragmentHelper.java index 03320fbfd..ead84863e 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/DialogFragmentHelper.java +++ b/app/src/main/java/com/github/pockethub/android/ui/DialogFragmentHelper.java @@ -16,7 +16,6 @@ package com.github.pockethub.android.ui; import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; @@ -25,15 +24,14 @@ import com.afollestad.materialdialogs.MaterialDialog; -import roboguice.fragment.RoboDialogFragment; +import dagger.android.support.DaggerAppCompatDialogFragment; import static android.app.Activity.RESULT_CANCELED; /** * Base dialog fragment helper */ -public abstract class DialogFragmentHelper extends RoboDialogFragment implements - OnClickListener { +public abstract class DialogFragmentHelper extends DaggerAppCompatDialogFragment { /** * Dialog message @@ -99,8 +97,7 @@ protected void onResult(final int resultCode) { if (activity != null) { final Bundle arguments = getArguments(); if (arguments != null) { - activity.onDialogResult(arguments.getInt(ARG_REQUEST_CODE), - resultCode, arguments); + activity.onDialogResult(arguments.getInt(ARG_REQUEST_CODE), resultCode, arguments); } } } @@ -140,9 +137,4 @@ protected MaterialDialog.Builder createDialogBuilder() { .cancelable(true) .cancelListener(this); } - - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } } diff --git a/app/src/main/java/com/github/pockethub/android/ui/ItemListFragment.java b/app/src/main/java/com/github/pockethub/android/ui/ItemListFragment.java index 6c907295a..98894f246 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/ItemListFragment.java +++ b/app/src/main/java/com/github/pockethub/android/ui/ItemListFragment.java @@ -15,76 +15,107 @@ */ package com.github.pockethub.android.ui; -import android.app.Activity; import android.os.Bundle; -import android.support.v4.app.LoaderManager.LoaderCallbacks; -import android.support.v4.content.Loader; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.animation.AnimationUtils; -import android.widget.ListAdapter; -import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; -import com.github.kevinsawicki.wishlist.SingleTypeAdapter; import com.github.pockethub.android.R; -import com.github.pockethub.android.ThrowableLoader; +import com.github.pockethub.android.rx.AutoDisposeUtils; import com.github.pockethub.android.util.ToastUtils; +import com.xwray.groupie.GroupAdapter; +import com.xwray.groupie.Item; +import com.xwray.groupie.OnItemClickListener; +import com.xwray.groupie.OnItemLongClickListener; +import com.xwray.groupie.Section; -import java.util.Collections; +import java.util.ArrayList; import java.util.List; +import butterknife.BindView; +import io.reactivex.Observable; +import io.reactivex.Single; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; + /** * Base fragment for displaying a list of items that loads with a progress bar - * visible + * visible. * - * @param */ public abstract class ItemListFragment extends DialogFragment implements - LoaderCallbacks>, SwipeRefreshLayout.OnRefreshListener { + SwipeRefreshLayout.OnRefreshListener, OnItemClickListener, OnItemLongClickListener { - private static final String FORCE_REFRESH = "forceRefresh"; + /** + * Swipe to refresh view. + */ + @BindView(R.id.swipe_item) + protected SwipeRefreshLayout swipeLayout; - private SwipeRefreshLayout swipeLayout; + /** + * List view. + */ + @BindView(android.R.id.list) + protected RecyclerView recyclerView; /** - * @param args - * bundle passed to the loader by the LoaderManager - * @return true if the bundle indicates a requested forced refresh of the - * items + * Empty view. */ - protected static boolean isForceRefresh(Bundle args) { - return args != null && args.getBoolean(FORCE_REFRESH, false); - } + @BindView(android.R.id.empty) + protected TextView emptyView; /** - * List items provided to {@link #onLoadFinished(Loader, List)} + * Progress bar. */ - protected List items = Collections.emptyList(); + @BindView(R.id.pb_loading) + protected ProgressBar progressBar; /** - * List view + * List items. */ - protected ListView listView; + protected List items = new ArrayList<>(); /** - * Empty view + * Is the list currently shown?. */ - protected TextView emptyView; + protected boolean listShown; /** - * Progress bar + * Disposable for data load request. */ - protected ProgressBar progressBar; + private Disposable dataLoadDisposable; /** - * Is the list currently shown? + * The adapter used by the {@link RecyclerView} to display {@link com.xwray.groupie.Group}:s + * from Groupie. */ - protected boolean listShown; + private GroupAdapter adapter = new GroupAdapter(); + + /** + * The {@link Section} containing headers, footers and the items. + */ + private Section mainSection = new Section(); + + /** + * Is the fragment currently loading data. + */ + protected boolean isLoading; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + adapter.add(mainSection); + } @Override public void onActivityCreated(Bundle savedInstanceState) { @@ -92,9 +123,9 @@ public void onActivityCreated(Bundle savedInstanceState) { if (!items.isEmpty()) { setListShown(true, false); + } else { + refresh(); } - - getLoaderManager().initLoader(0, null, this); } @Override @@ -116,16 +147,15 @@ public void onDestroyView() { listShown = false; emptyView = null; progressBar = null; - listView = null; + recyclerView = null; super.onDestroyView(); } @Override - public void onViewCreated(View view, Bundle savedInstanceState) { + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - swipeLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipe_item); swipeLayout.setOnRefreshListener(this); swipeLayout.setColorSchemeResources( R.color.pager_title_background_top_start, @@ -133,137 +163,134 @@ public void onViewCreated(View view, Bundle savedInstanceState) { R.color.text_link, R.color.pager_title_background_end); - listView = (ListView) view.findViewById(android.R.id.list); - listView.setOnItemClickListener((parent, view1, position, id) -> - onListItemClick((ListView) parent, view1, position, id)); - listView.setOnItemLongClickListener((parent, view12, position, id) -> - onListItemLongClick((ListView) parent, view12, position, id)); - progressBar = (ProgressBar) view.findViewById(R.id.pb_loading); - - emptyView = (TextView) view.findViewById(android.R.id.empty); - - configureList(getActivity(), getListView()); + configureList(getRecyclerView()); } /** - * Configure list after view has been created + * Configure list after view has been created. * - * @param activity - * @param listView + * @param recyclerView */ - protected void configureList(Activity activity, ListView listView) { - listView.setAdapter(createAdapter()); + protected void configureList(RecyclerView recyclerView) { + + adapter.setOnItemClickListener(this); + adapter.setOnItemLongClickListener(this); + recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); + recyclerView.setAdapter(adapter); } /** - * Force a refresh of the items displayed ignoring any cached items + * Force a refresh of the items displayed ignoring any cached items. */ protected void forceRefresh() { - Bundle bundle = new Bundle(); - bundle.putBoolean(FORCE_REFRESH, true); - refresh(bundle); + items.clear(); + refresh(true); } - /** - * Refresh the fragment's list - */ public void refresh() { - refresh(null); + items.clear(); + refresh(false); } - private void refresh(final Bundle args) { - if (!isUsable()) { + /** + * Refresh the fragment's list. + */ + protected void refresh(boolean force) { + if (!isUsable() || isLoading) { return; } - getLoaderManager().restartLoader(0, args, this); + if (dataLoadDisposable != null && !dataLoadDisposable.isDisposed()) { + dataLoadDisposable.dispose(); + } + + isLoading = true; + + dataLoadDisposable = loadData(force) + .flatMap(items -> Observable.fromIterable(items) + .map(this::createItem) + .toList()) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .as(AutoDisposeUtils.bindToLifecycle(this)) + .subscribe(this::onDataLoaded, this::onDataLoadError); } /** - * Get error message to display for exception + * Get error message to display for exception. * - * @param exception * @return string resource id */ - protected abstract int getErrorMessage(Exception exception); + protected abstract int getErrorMessage(); - @Override - public void onLoadFinished(Loader> loader, List items) { + /** + * Load data async via a Single. + * + * @param forceRefresh If the loading is forced. + * @return Single to subscribe to. + */ + protected abstract Single> loadData(boolean forceRefresh); + + /** + * Create a {@link Item} from the data item. + * + * @param item The data item to create an item with. + * @return A new item. + */ + protected abstract Item createItem(E item); + + /** + * Called when the data has loaded. + * + * @param newItems The items added to the list. + */ + protected void onDataLoaded(List newItems) { if (!isUsable()) { return; } + isLoading = false; swipeLayout.setRefreshing(false); - Exception exception = getException(loader); - if (exception != null) { - showError(exception, getErrorMessage(exception)); - showList(); - return; - } - this.items = items; - getListAdapter().getWrappedAdapter().setItems(items.toArray()); + items.addAll(newItems); + mainSection.update(items); + showList(); } - /** - * Create adapter to display items - * - * @return adapter - */ - protected HeaderFooterListAdapter> createAdapter() { - SingleTypeAdapter wrapped = createAdapter(items); - return new HeaderFooterListAdapter<>(getListView(), - wrapped); - } + protected void onDataLoadError(Throwable throwable) { + if (!isUsable()) { + return; + } - /** - * Create adapter to display items - * - * @param items - * @return adapter - */ - protected abstract SingleTypeAdapter createAdapter(final List items); + isLoading = false; + swipeLayout.setRefreshing(false); + + if (throwable != null) { + showError(throwable, getErrorMessage()); + showList(); + } + } /** - * Set the list to be shown + * Set the list to be shown. */ protected void showList() { setListShown(true, isResumed()); } - @Override - public void onLoaderReset(Loader> loader) { - // Intentionally left blank - } - /** - * Show exception in a {@link Toast} + * Show exception in a {@link Toast}. * * @param e * @param defaultMessage */ - protected void showError(final Exception e, final int defaultMessage) { + protected void showError(final Throwable e, final int defaultMessage) { ToastUtils.show(getActivity(), e, defaultMessage); } /** - * Get exception from loader if it provides one by being a - * {@link ThrowableLoader} - * - * @param loader - * @return exception or null if none provided - */ - protected Exception getException(final Loader> loader) { - if (loader instanceof ThrowableLoader) { - return ((ThrowableLoader>) loader).clearException(); - } else { - return null; - } - } - - /** - * Refresh the list with the progress bar showing + * Refresh the list with the progress bar showing. */ protected void refreshWithProgress() { items.clear(); @@ -272,59 +299,58 @@ protected void refreshWithProgress() { } /** - * Get {@link ListView} + * Get {@link RecyclerView}. * - * @return listView + * @return recyclerView */ - public ListView getListView() { - return listView; + public RecyclerView getRecyclerView() { + return recyclerView; } + /** - * Get list adapter + * Get main {@link Section} for adding footers and headers. * - * @return list adapter + * @return mainSection */ - @SuppressWarnings("unchecked") - protected HeaderFooterListAdapter> getListAdapter() { - if (listView != null) { - return (HeaderFooterListAdapter>) listView - .getAdapter(); + public Section getMainSection() { + return mainSection; + } + + /** + * Get the {@link android.support.v7.widget.RecyclerView.LayoutManager} for + * the {@link RecyclerView} + * + * @return recyclerView + */ + public LinearLayoutManager getLayoutManager() { + if (recyclerView != null) { + return (LinearLayoutManager) recyclerView.getLayoutManager(); } else { return null; } } /** - * Notify the underlying adapter that the data set has changed + * Get list adapter. * - * @return this fragment + * @return list adapter */ - protected ItemListFragment notifyDataSetChanged() { - HeaderFooterListAdapter> root = getListAdapter(); - if (root != null) { - SingleTypeAdapter typeAdapter = root.getWrappedAdapter(); - if (typeAdapter != null) { - typeAdapter.notifyDataSetChanged(); - } - } - return this; + public GroupAdapter getListAdapter() { + return adapter; } /** - * Set list adapter to use on list view + * Notify the underlying adapter that the data set has changed. * - * @param adapter * @return this fragment */ - protected ItemListFragment setListAdapter(final ListAdapter adapter) { - if (listView != null) { - listView.setAdapter(adapter); - } + protected ItemListFragment notifyDataSetChanged() { + getListAdapter().notifyDataSetChanged(); return this; } - private ItemListFragment fadeIn(final View view, final boolean animate) { + private ItemListFragment fadeIn(final View view, final boolean animate) { if (view != null) { if (animate) { view.startAnimation(AnimationUtils.loadAnimation(getActivity(), @@ -336,48 +362,47 @@ private ItemListFragment fadeIn(final View view, final boolean animate) { return this; } - private ItemListFragment show(final View view) { + private ItemListFragment show(final View view) { view.setVisibility(View.VISIBLE); return this; } - private ItemListFragment hide(final View view) { + private ItemListFragment hide(final View view) { view.setVisibility(View.GONE); return this; } /** - * Set list shown or progress bar show + * Set list shown or progress bar show. * * @param shown * @return this fragment */ - public ItemListFragment setListShown(final boolean shown) { + public ItemListFragment setListShown(final boolean shown) { return setListShown(shown, true); } /** - * Set list shown or progress bar show + * Set list shown or progress bar show. * * @param shown * @param animate * @return this fragment */ - public ItemListFragment setListShown(final boolean shown, + public ItemListFragment setListShown(final boolean shown, final boolean animate) { if (!isUsable()) { return this; } if (shown == listShown) { - if (shown) + if (shown) { // List has already been shown so hide/show the empty view with // no fade effect - { if (items.isEmpty()) { - hide(listView).show(emptyView); + hide(recyclerView).show(emptyView); } else { - hide(emptyView).show(listView); + hide(emptyView).show(recyclerView); } } return this; @@ -387,14 +412,14 @@ public ItemListFragment setListShown(final boolean shown, if (shown) { if (!items.isEmpty()) { - hide(progressBar).hide(emptyView).fadeIn(listView, animate) - .show(listView); + hide(progressBar).hide(emptyView).fadeIn(recyclerView, animate) + .show(recyclerView); } else { - hide(progressBar).hide(listView).fadeIn(emptyView, animate) + hide(progressBar).hide(recyclerView).fadeIn(emptyView, animate) .show(emptyView); } } else { - hide(listView).hide(emptyView).fadeIn(progressBar, animate) + hide(recyclerView).hide(emptyView).fadeIn(progressBar, animate) .show(progressBar); } @@ -402,12 +427,12 @@ public ItemListFragment setListShown(final boolean shown, } /** - * Set empty text on list fragment + * Set empty text on list fragment. * * @param message * @return this fragment */ - protected ItemListFragment setEmptyText(final String message) { + protected ItemListFragment setEmptyText(final String message) { if (emptyView != null) { emptyView.setText(message); } @@ -415,12 +440,12 @@ protected ItemListFragment setEmptyText(final String message) { } /** - * Set empty text on list fragment + * Set empty text on list fragment. * * @param resId * @return this fragment */ - protected ItemListFragment setEmptyText(final int resId) { + protected ItemListFragment setEmptyText(final int resId) { if (emptyView != null) { emptyView.setText(resId); } @@ -428,26 +453,25 @@ protected ItemListFragment setEmptyText(final int resId) { } /** - * Callback when a list view item is clicked + * Callback when a list view item is clicked. * - * @param l - * @param v - * @param position - * @param id + * @param item + * @param view */ - public void onListItemClick(ListView l, View v, int position, long id) { + @Override + public void onItemClick(@NonNull Item item, @NonNull View view) { + } /** - * Callback when a list view item is clicked and held + * Callback when a list view item is clicked and held. * - * @param l - * @param v - * @param position - * @param id + * @param item + * @param view * @return true if the callback consumed the long click, false otherwise */ - public boolean onListItemLongClick(ListView l, View v, int position, long id) { + @Override + public boolean onItemLongClick(@NonNull Item item, @NonNull View view) { return false; } } diff --git a/app/src/main/java/com/github/pockethub/android/ui/MainActivity.java b/app/src/main/java/com/github/pockethub/android/ui/MainActivity.java index 548a4c914..827c08e66 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/MainActivity.java +++ b/app/src/main/java/com/github/pockethub/android/ui/MainActivity.java @@ -27,13 +27,12 @@ import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; +import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import android.support.design.widget.NavigationView; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; -import android.support.v4.app.LoaderManager; import android.support.v4.content.IntentCompat; -import android.support.v4.content.Loader; import android.support.v4.view.GravityCompat; import android.support.v4.view.MenuItemCompat; import android.support.v4.widget.DrawerLayout; @@ -57,65 +56,72 @@ import com.github.pockethub.android.core.user.UserComparator; import com.github.pockethub.android.persistence.AccountDataManager; import com.github.pockethub.android.persistence.CacheHelper; +import com.github.pockethub.android.rx.AutoDisposeUtils; import com.github.pockethub.android.ui.gist.GistsPagerFragment; import com.github.pockethub.android.ui.issue.FilterListFragment; import com.github.pockethub.android.ui.issue.IssueDashboardPagerFragment; import com.github.pockethub.android.ui.notification.NotificationActivity; -import com.github.pockethub.android.ui.repo.OrganizationLoader; import com.github.pockethub.android.ui.user.HomePagerFragment; import com.github.pockethub.android.util.AvatarLoader; +import com.github.pockethub.android.util.ToastUtils; import com.meisolsson.githubsdk.core.TokenStore; import com.meisolsson.githubsdk.model.User; -import com.google.inject.Inject; -import com.google.inject.Provider; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.inject.Singleton; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import butterknife.BindView; +import io.reactivex.Single; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; -public class MainActivity extends BaseActivity implements - LoaderManager.LoaderCallbacks>, NavigationView.OnNavigationItemSelectedListener { + +public class MainActivity extends BaseActivity + implements NavigationView.OnNavigationItemSelectedListener { private static final String TAG = "MainActivity"; private static final String PREF_USER_LEARNED_DRAWER = "navigation_drawer_learned"; + @BindView(R.id.drawer_layout) + protected DrawerLayout drawerLayout; + + @BindView(R.id.navigation_view) + protected NavigationView navigationView; + @Inject - private AccountDataManager accountDataManager; + protected AccountDataManager accountDataManager; @Inject - private Provider userComparatorProvider; + protected Provider userComparatorProvider; private List orgs = Collections.emptyList(); private User org; - private NavigationView navigationView; - @Inject - private AvatarLoader avatars; - private DrawerLayout drawerLayout; + @Singleton + protected AvatarLoader avatars; + private boolean userLearnedDrawer; - private Toolbar toolbar; private ActionBarDrawerToggle actionBarDrawerToggle; + @VisibleForTesting + public Fragment currentFragment; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); userLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false); - toolbar = (Toolbar) findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); - - actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close){ + actionBarDrawerToggle = new ActionBarDrawerToggle(this, drawerLayout, getToolbar(), R.string.navigation_drawer_open, R.string.navigation_drawer_close){ @Override public void onDrawerOpened(View drawerView) { super.onDrawerOpened(drawerView); @@ -130,11 +136,9 @@ public void onDrawerOpened(View drawerView) { }; drawerLayout.setDrawerListener(actionBarDrawerToggle); - navigationView = (NavigationView) findViewById(R.id.navigation_view); - navigationView.setNavigationItemSelectedListener(this); - getSupportLoaderManager().initLoader(0, null, this); + reloadOrgs(); TokenStore tokenStore = TokenStore.getInstance(this); @@ -149,6 +153,11 @@ public void onDrawerOpened(View drawerView) { } } + @Override + protected int getContentView() { + return R.layout.activity_main; + } + @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); @@ -157,7 +166,7 @@ public void onConfigurationChanged(Configuration newConfig) { @Override public boolean onOptionsItemSelected(MenuItem item) { - if(actionBarDrawerToggle.onOptionsItemSelected(item)){ + if (actionBarDrawerToggle.onOptionsItemSelected(item)) { return true; } return super.onOptionsItemSelected(item); @@ -170,7 +179,48 @@ protected void onPostCreate(Bundle savedInstanceState) { } private void reloadOrgs() { - getSupportLoaderManager().restartLoader(0, null, this); + Single.fromCallable(() -> AccountUtils.getAccount(getAccountManager(), this)) + .map(account -> accountDataManager.getOrgs(false)) + .map(orgs -> { + Collections.sort(orgs, userComparatorProvider.get()); + return orgs; + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .as(AutoDisposeUtils.bindToLifecycle(this)) + .subscribe(this::onOrgsLoaded, + e -> { + Log.e(TAG, "Exception loading organizations", e); + ToastUtils.show(this, e, R.string.error_orgs_load); + }); + } + + @VisibleForTesting + void onOrgsLoaded(List orgs) { + if (orgs.isEmpty()) { + return; + } + + org = orgs.get(0); + this.orgs = orgs; + + setUpNavigationView(); + + Window window = getWindow(); + if (window == null) { + return; + } + View view = window.getDecorView(); + if (view == null) { + return; + } + + view.post(() -> { + switchFragment(new HomePagerFragment(), org); + if (!userLearnedDrawer) { + drawerLayout.openDrawer(GravityCompat.START); + } + }); } @Override @@ -198,53 +248,18 @@ protected void onResume() { } } - @Override - public Loader> onCreateLoader(int i, Bundle bundle) { - return new OrganizationLoader(this, accountDataManager, - userComparatorProvider); - } - Map menuItemOrganizationMap = new HashMap<>(); - @Override - public void onLoadFinished(Loader> listLoader, final List orgs) { - if (orgs.isEmpty()) { - return; - } - - org = orgs.get(0); - this.orgs = orgs; - - setUpNavigationView(); - - Window window = getWindow(); - if (window == null) { - return; - } - View view = window.getDecorView(); - if (view == null) { - return; - } - - view.post(() -> { - switchFragment(new HomePagerFragment(), org); - if(!userLearnedDrawer) { - drawerLayout.openDrawer(GravityCompat.START); - } - }); - - } - private void setUpHeaderView() { ImageView userImage; TextView userRealName; TextView userName; View headerView = navigationView.getHeaderView(0); - userImage = (ImageView) headerView.findViewById(R.id.user_picture); - ImageView notificationIcon = (ImageView) headerView.findViewById(R.id.iv_notification); - userRealName = (TextView) headerView.findViewById(R.id.user_real_name); - userName = (TextView) headerView.findViewById(R.id.user_name); + userImage = headerView.findViewById(R.id.user_picture); + ImageView notificationIcon = headerView.findViewById(R.id.iv_notification); + userRealName = headerView.findViewById(R.id.user_real_name); + userName = headerView.findViewById(R.id.user_name); notificationIcon.setOnClickListener(v -> startActivity(new Intent(MainActivity.this, NotificationActivity.class))); @@ -261,11 +276,8 @@ private void setUpHeaderView() { } private void setUpNavigationView() { - if (navigationView != null) { - - setUpHeaderView(); - setUpNavigationMenu(); - } + setUpHeaderView(); + setUpNavigationMenu(); } private void setUpNavigationMenu() { @@ -288,12 +300,7 @@ private void setUpNavigationMenu() { } @Override - public void onLoaderReset(Loader> listLoader) { - - } - - @Override - public boolean onNavigationItemSelected(MenuItem menuItem) { + public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) { int itemId = menuItem.getItemId(); if (itemId == R.id.navigation_home) { @@ -325,37 +332,41 @@ public boolean onNavigationItemSelected(MenuItem menuItem) { } private void logout() { - AccountManager accountManager = getAccountManager(); - String accountType = getString(R.string.account_type); - Account[] allGitHubAccounts = accountManager.getAccountsByType(accountType); - - for (Account account : allGitHubAccounts) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { - accountManager.removeAccount(account, this, null, null); - } else { - accountManager.removeAccount(account, null, null); - } - } - + // Remove cookies so that the login is clean if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { CookieManager.getInstance().removeAllCookies(null); } else { CookieManager.getInstance().removeAllCookie(); } + // Clear all of the cached data CacheHelper helper = new CacheHelper(this); helper.getWritableDatabase().delete("orgs", null, null); helper.getWritableDatabase().delete("users", null, null); helper.getWritableDatabase().delete("repos", null, null); + // Remove the account + AccountManager accountManager = getAccountManager(); + String accountType = getString(R.string.account_type); + Account[] allGitHubAccounts = accountManager.getAccountsByType(accountType); + + for (Account account : allGitHubAccounts) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + accountManager.removeAccount(account, this, bool -> startLoginActivity(), null); + } else { + accountManager.removeAccount(account, bundle -> startLoginActivity(), null); + } + } + } + + private void startLoginActivity() { Intent in = new Intent(this, LoginActivity.class); - in.addFlags(IntentCompat.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); + in.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(in); finish(); } - @VisibleForTesting - void switchFragment(Fragment fragment, User organization) { + private void switchFragment(Fragment fragment, User organization) { if (organization != null) { Bundle args = new Bundle(); args.putParcelable("org", organization); @@ -364,10 +375,11 @@ void switchFragment(Fragment fragment, User organization) { FragmentManager manager = getSupportFragmentManager(); manager.beginTransaction().replace(R.id.container, fragment).commit(); drawerLayout.closeDrawer(GravityCompat.START); + + currentFragment = fragment; } - @VisibleForTesting - AccountManager getAccountManager() { + private AccountManager getAccountManager() { return AccountManager.get(this); } } diff --git a/app/src/main/java/com/github/pockethub/android/ui/MarkdownLoader.java b/app/src/main/java/com/github/pockethub/android/ui/MarkdownLoader.java index 9b92c92a9..eff597362 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/MarkdownLoader.java +++ b/app/src/main/java/com/github/pockethub/android/ui/MarkdownLoader.java @@ -15,71 +15,43 @@ */ package com.github.pockethub.android.ui; -import android.accounts.Account; -import android.app.Activity; +import android.content.Context; import android.text.Html.ImageGetter; import com.meisolsson.githubsdk.core.ServiceGenerator; import com.meisolsson.githubsdk.model.Repository; -import com.github.pockethub.android.accounts.AuthenticatedUserLoader; import com.github.pockethub.android.util.HtmlUtils; import com.meisolsson.githubsdk.model.request.RequestMarkdown; import com.meisolsson.githubsdk.service.misc.MarkdownService; +import io.reactivex.Single; +import retrofit2.Response; + /** - * Markdown loader + * Markdown loader. */ -public class MarkdownLoader extends AuthenticatedUserLoader { - - private static final String TAG = "MarkdownLoader"; - - private final ImageGetter imageGetter; - - private final Repository repository; - - private final String raw; - - private boolean encode; +public class MarkdownLoader { /** - * @param activity - * @param repository + * Fetches html + * @param context * @param raw + * @param repository * @param imageGetter * @param encode + * @return */ - public MarkdownLoader(Activity activity, Repository repository, - String raw, ImageGetter imageGetter, boolean encode) { - super(activity); - - this.repository = repository; - this.raw = raw; - this.imageGetter = imageGetter; - this.encode = encode; - } - - @Override - protected CharSequence getAccountFailureData() { - return null; - } - - @Override - public CharSequence load(Account account) { + public static Single load(Context context, String raw, Repository repository, + ImageGetter imageGetter, boolean encode) { RequestMarkdown markdown = RequestMarkdown.builder() .mode(RequestMarkdown.MODE_GFM) .text(raw) .context(repository != null ? String.format("%s/%s", repository.owner().login(), repository.name()) : null) .build(); - String html = ServiceGenerator.createService(activity, MarkdownService.class) + return ServiceGenerator.createService(context, MarkdownService.class) .renderMarkdown(markdown) - .blockingGet() - .body(); - - if (encode) { - return HtmlUtils.encode(html, imageGetter); - } else { - return html; - } + .map(Response::body) + .map(html -> encode ? HtmlUtils.encode(html, imageGetter) : html); } } diff --git a/app/src/main/java/com/github/pockethub/android/ui/NewsFragment.java b/app/src/main/java/com/github/pockethub/android/ui/NewsFragment.java index e54119751..84a4c4247 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/NewsFragment.java +++ b/app/src/main/java/com/github/pockethub/android/ui/NewsFragment.java @@ -18,20 +18,38 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import android.support.annotation.NonNull; import android.text.TextUtils; +import android.util.Log; import android.view.View; import android.widget.ImageView; -import android.widget.ListView; import android.widget.TextView; import com.afollestad.materialdialogs.MaterialDialog; +import com.github.pockethub.android.ui.item.news.CommitCommentEventItem; +import com.github.pockethub.android.ui.item.news.CreateEventItem; +import com.github.pockethub.android.ui.item.news.DeleteEventItem; +import com.github.pockethub.android.ui.item.news.FollowEventItem; +import com.github.pockethub.android.ui.item.news.ForkEventItem; +import com.github.pockethub.android.ui.item.news.GistEventItem; +import com.github.pockethub.android.ui.item.news.GollumEventItem; +import com.github.pockethub.android.ui.item.news.IssueCommentEventItem; +import com.github.pockethub.android.ui.item.news.IssuesEventItem; +import com.github.pockethub.android.ui.item.news.MemberEventItem; +import com.github.pockethub.android.ui.item.news.NewsItem; +import com.github.pockethub.android.ui.item.news.PublicEventItem; +import com.github.pockethub.android.ui.item.news.PullRequestEventItem; +import com.github.pockethub.android.ui.item.news.PullRequestReviewCommentEventItem; +import com.github.pockethub.android.ui.item.news.PushEventItem; +import com.github.pockethub.android.ui.item.news.ReleaseEventItem; +import com.github.pockethub.android.ui.item.news.TeamAddEventItem; +import com.github.pockethub.android.ui.item.news.WatchEventItem; import com.meisolsson.githubsdk.model.Gist; import com.meisolsson.githubsdk.model.GitHubEvent; import com.meisolsson.githubsdk.model.Issue; import com.meisolsson.githubsdk.model.Release; import com.meisolsson.githubsdk.model.Repository; import com.meisolsson.githubsdk.model.User; -import com.github.kevinsawicki.wishlist.SingleTypeAdapter; import com.github.pockethub.android.R; import com.github.pockethub.android.core.gist.GistEventMatcher; import com.github.pockethub.android.core.issue.IssueEventMatcher; @@ -43,7 +61,6 @@ import com.github.pockethub.android.ui.gist.GistsViewActivity; import com.github.pockethub.android.ui.issue.IssuesViewActivity; import com.github.pockethub.android.ui.repo.RepositoryViewActivity; -import com.github.pockethub.android.ui.user.NewsListAdapter; import com.github.pockethub.android.util.AvatarLoader; import com.github.pockethub.android.util.ConvertUtils; import com.github.pockethub.android.util.InfoUtils; @@ -52,7 +69,9 @@ import com.meisolsson.githubsdk.model.payload.CommitCommentPayload; import com.meisolsson.githubsdk.model.payload.PushPayload; import com.meisolsson.githubsdk.model.payload.ReleasePayload; -import com.google.inject.Inject; +import com.xwray.groupie.Item; + +import javax.inject.Inject; import java.util.List; @@ -88,7 +107,7 @@ public abstract class NewsFragment extends PagedItemFragment { protected final UserEventMatcher userMatcher = new UserEventMatcher(); @Inject - private AvatarLoader avatars; + protected AvatarLoader avatars; @Override public void onActivityCreated(Bundle savedInstanceState) { @@ -98,8 +117,12 @@ public void onActivityCreated(Bundle savedInstanceState) { } @Override - public void onListItemClick(ListView l, View v, int position, long id) { - GitHubEvent event = (GitHubEvent) l.getItemAtPosition(position); + public void onItemClick(@NonNull Item item, @NonNull View view) { + if (!(item instanceof NewsItem)) { + return; + } + + GitHubEvent event = ((NewsItem) item).getData(); if (DownloadEvent.equals(event.type())) { openDownload(event); @@ -141,13 +164,17 @@ public void onListItemClick(ListView l, View v, int position, long id) { } @Override - public boolean onListItemLongClick(ListView l, View v, int position, - long itemId) { + public boolean onItemLongClick(@NonNull Item item, @NonNull View view) { if (!isUsable()) { return false; } - final GitHubEvent event = (GitHubEvent) l.getItemAtPosition(position); + if (!(item instanceof NewsItem)) { + return false; + } + + + final GitHubEvent event = ((NewsItem) item).getData(); final Repository repo = ConvertUtils.eventRepoToRepo(event.repo()); final User user = event.actor(); @@ -158,21 +185,21 @@ public boolean onListItemLongClick(ListView l, View v, int position, // Hacky but necessary since material dialogs has a different API final MaterialDialog[] dialogHolder = new MaterialDialog[1]; - View view = getActivity().getLayoutInflater().inflate( + View dialogView = getActivity().getLayoutInflater().inflate( R.layout.nav_dialog, null); - avatars.bind((ImageView) view.findViewById(R.id.iv_user_avatar), user); - avatars.bind((ImageView) view.findViewById(R.id.iv_repo_avatar), repo.owner()); - ((TextView) view.findViewById(R.id.tv_login)).setText(user.login()); - ((TextView) view.findViewById(R.id.tv_repo_name)).setText(InfoUtils.createRepoId(repo)); - view.findViewById(R.id.ll_user_area).setOnClickListener(v1 -> { + avatars.bind((ImageView) dialogView.findViewById(R.id.iv_user_avatar), user); + avatars.bind((ImageView) dialogView.findViewById(R.id.iv_repo_avatar), repo.owner()); + ((TextView) dialogView.findViewById(R.id.tv_login)).setText(user.login()); + ((TextView) dialogView.findViewById(R.id.tv_repo_name)).setText(InfoUtils.createRepoId(repo)); + dialogView.findViewById(R.id.ll_user_area).setOnClickListener(v1 -> { dialogHolder[0].dismiss(); viewUser(user); }); - view.findViewById(R.id.ll_repo_area).setOnClickListener(v1 -> { + dialogView.findViewById(R.id.ll_repo_area).setOnClickListener(v1 -> { dialogHolder[0].dismiss(); viewRepository(repo); }); - builder.customView(view, false); + builder.customView(dialogView, false); MaterialDialog dialog = builder.build(); dialogHolder[0] = dialog; @@ -296,9 +323,8 @@ protected void viewIssue(Issue issue, Repository repository) { } @Override - protected SingleTypeAdapter createAdapter(List items) { - return new NewsListAdapter(getActivity().getLayoutInflater(), - items.toArray(new GitHubEvent[items.size()]), avatars); + protected Item createItem(GitHubEvent item) { + return NewsItem.createNewsItem(avatars, item); } @Override @@ -307,7 +333,7 @@ protected int getLoadingMessage() { } @Override - protected int getErrorMessage(Exception exception) { + protected int getErrorMessage() { return R.string.error_news_load; } } diff --git a/app/src/main/java/com/github/pockethub/android/ui/PagedItemFragment.java b/app/src/main/java/com/github/pockethub/android/ui/PagedItemFragment.java index 8df43b099..61f566b78 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/PagedItemFragment.java +++ b/app/src/main/java/com/github/pockethub/android/ui/PagedItemFragment.java @@ -13,143 +13,143 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.github.pockethub.android.ui; -import android.app.Activity; import android.os.Bundle; -import android.support.v4.content.Loader; -import android.widget.AbsListView; -import android.widget.AbsListView.OnScrollListener; -import android.widget.ListView; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; -import com.github.pockethub.android.ThrowableLoader; -import com.github.pockethub.android.core.ResourcePager; +import com.meisolsson.githubsdk.model.Page; +import com.xwray.groupie.Item; -import java.io.IOException; import java.util.List; +import io.reactivex.Single; +import retrofit2.Response; + /** * List fragment that adds more elements when the bottom of the list is scrolled - * to - * - * @param + * to. */ -public abstract class PagedItemFragment extends ItemListFragment - implements OnScrollListener { +public abstract class PagedItemFragment extends ItemListFragment { + + private final RecyclerView.OnScrollListener scrollListener = + new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + } + + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + if (!isUsable()) { + return; + } + if (!hasMore) { + return; + } + + // The item count minus the footer + int count = getMainSection().getItemCount() - 1; + LinearLayoutManager layoutManager = getLayoutManager(); + + if (layoutManager != null) { + if (layoutManager.findLastVisibleItemPosition() >= count) { + showMore(); + } + } + } + }; + + private ResourceLoadingIndicator loadingIndicator; /** - * Resource pager + * The current page. */ - protected ResourcePager pager; - - private ResourceLoadingIndicator loadingIndicator; + private int page = 1; /** - * Create pager that provides resources - * - * @return pager + * Is there more items to fetch. */ - protected abstract ResourcePager createPager(); + private boolean hasMore = true; /** - * Get resource id of {@link String} to display when loading + * Get resource id of {@link String} to display when loading. * * @return string resource id */ protected abstract int getLoadingMessage(); @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - pager = createPager(); + protected void configureList(RecyclerView recyclerView) { + super.configureList(recyclerView); + loadingIndicator = new ResourceLoadingIndicator(getLoadingMessage()); + loadingIndicator.setSection(getMainSection()); } - /** - * Configure list after view has been created - * - * @param activity - * @param listView - */ @Override - protected void configureList(Activity activity, ListView listView) { - super.configureList(activity, listView); - - loadingIndicator = new ResourceLoadingIndicator(activity, - getLoadingMessage()); - loadingIndicator.setList(getListAdapter()); + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + getRecyclerView().addOnScrollListener(scrollListener); } @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); + public void onDestroyView() { + getRecyclerView().removeOnScrollListener(scrollListener); + super.onDestroyView(); + } - getListView().setOnScrollListener(this); + protected abstract Single>> loadData(int page); + + @Override + protected Single> loadData(boolean forceRefresh) { + return loadData(page) + .map(Response::body) + .map(page -> { + hasMore = page.next() != null; + return page; + }) + .map(Page::items); - getListView().setFastScrollEnabled(true); } - @Override - public Loader> onCreateLoader(int id, Bundle bundle) { - return new ThrowableLoader>(getActivity(), items) { - - @Override - public List loadData() throws IOException { - pager.next(); - return pager.getResources(); - } - }; + private void resetPagingData() { + page = 1; + hasMore = true; } @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - // Intentionally left blank + protected void forceRefresh() { + resetPagingData(); + loadingIndicator.setVisible(false); + super.forceRefresh(); } @Override - public void onScroll(AbsListView view, int firstVisibleItem, - int visibleItemCount, int totalItemCount) { - if (!isUsable()) { - return; - } - if (!pager.hasMore()) { - return; - } - if (getLoaderManager().hasRunningLoaders()) { - return; - } - if (listView != null - && listView.getLastVisiblePosition() >= pager.size()) { - showMore(); - } + public void refresh() { + refresh(false); } @Override - protected void forceRefresh() { - pager.clear(); - - super.forceRefresh(); + protected void refreshWithProgress() { + resetPagingData(); + loadingIndicator.setVisible(false); + super.refreshWithProgress(); } /** - * Show more events while retaining the current pager state + * Show more events while retaining the current pager state. */ private void showMore() { + page++; refresh(); } @Override - public void onLoadFinished(Loader> loader, List items) { - loadingIndicator.setVisible(pager.hasMore()); - - super.onLoadFinished(loader, items); - } - - @Override - protected void refreshWithProgress() { - pager.reset(); - pager = createPager(); - - super.refreshWithProgress(); + protected void onDataLoaded(List items) { + super.onDataLoaded(items); + loadingIndicator.setVisible(hasMore); } } diff --git a/app/src/main/java/com/github/pockethub/android/ui/ResourceLoadingIndicator.java b/app/src/main/java/com/github/pockethub/android/ui/ResourceLoadingIndicator.java index 955e9a2a2..268cf3ad4 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/ResourceLoadingIndicator.java +++ b/app/src/main/java/com/github/pockethub/android/ui/ResourceLoadingIndicator.java @@ -15,12 +15,8 @@ */ package com.github.pockethub.android.ui; -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.TextView; - -import com.github.pockethub.android.R; +import com.github.pockethub.android.ui.item.LoadingItem; +import com.xwray.groupie.Section; /** * Helper for showing more items are being loaded at the bottom of a list via a @@ -28,54 +24,45 @@ */ public class ResourceLoadingIndicator { - private HeaderFooterListAdapter adapter; + private Section section; private boolean showing; - private final View view; - - private final TextView textView; + private final LoadingItem loadingItem; /** - * Create indicator using given inflater + * Create indicator using given inflater. * - * @param context * @param loadingResId * string resource id to show when loading */ - public ResourceLoadingIndicator(final Context context, - final int loadingResId) { - view = LayoutInflater.from(context).inflate(R.layout.loading_item, null); - textView = (TextView) view.findViewById(R.id.tv_loading); - textView.setText(loadingResId); + public ResourceLoadingIndicator(final int loadingResId) { + loadingItem = new LoadingItem(loadingResId); } /** - * Set the adapter that this indicator should be added as a footer to + * Set the adapter that this indicator should be added as a footer to. * - * @param adapter + * @param section * @return this indicator */ - public ResourceLoadingIndicator setList( - final HeaderFooterListAdapter adapter) { - this.adapter = adapter; - adapter.addFooter(view); - showing = true; + public ResourceLoadingIndicator setSection(final Section section) { + this.section = section; return this; } /** - * Set visibility of entire indicator view + * Set visibility of entire indicator view. * * @param visible * @return this indicator */ public ResourceLoadingIndicator setVisible(final boolean visible) { - if (showing != visible && adapter != null) { + if (showing != visible && section != null) { if (visible) { - adapter.addFooter(view); + section.setFooter(loadingItem); } else { - adapter.removeFooter(view); + section.removeFooter(); } } showing = visible; diff --git a/app/src/main/java/com/github/pockethub/android/ui/SingleChoiceDialogFragment.java b/app/src/main/java/com/github/pockethub/android/ui/SingleChoiceDialogFragment.java index 72752aaf6..77bed657f 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/SingleChoiceDialogFragment.java +++ b/app/src/main/java/com/github/pockethub/android/ui/SingleChoiceDialogFragment.java @@ -15,17 +15,21 @@ */ package com.github.pockethub.android.ui; -import android.content.DialogInterface.OnClickListener; import android.os.Bundle; import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.view.View; + +import com.xwray.groupie.Item; +import com.xwray.groupie.OnItemClickListener; import java.util.ArrayList; /** * Helper to display a single choice dialog */ -public class SingleChoiceDialogFragment extends DialogFragmentHelper implements - OnClickListener { +public class SingleChoiceDialogFragment extends DialogFragmentHelper + implements OnItemClickListener { /** * Arguments key for the selected item @@ -67,4 +71,9 @@ protected static void show(final BaseActivity activity, arguments.putInt(ARG_SELECTED_CHOICE, selectedChoice); show(activity, helper, arguments, TAG); } + + @Override + public void onItemClick(@NonNull Item item, @NonNull View view) { + dismiss(); + } } diff --git a/app/src/main/java/com/github/pockethub/android/ui/StyledText.java b/app/src/main/java/com/github/pockethub/android/ui/StyledText.java index f736fcf3b..cc56a10d7 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/StyledText.java +++ b/app/src/main/java/com/github/pockethub/android/ui/StyledText.java @@ -19,9 +19,11 @@ import android.text.TextUtils; import android.text.style.BackgroundColorSpan; import android.text.style.ForegroundColorSpan; +import android.text.style.StrikethroughSpan; import android.text.style.StyleSpan; import android.text.style.TypefaceSpan; import android.text.style.URLSpan; +import android.text.style.UnderlineSpan; import android.view.View; import android.view.View.OnClickListener; @@ -139,6 +141,17 @@ public StyledText monospace(final CharSequence text) { return append(text, new TypefaceSpan("monospace")); } + public StyledText underlineAll() { + setSpan(new UnderlineSpan(), 0, length(), SPAN_EXCLUSIVE_EXCLUSIVE); + return this; + } + + + public StyledText strikethroughAll() { + setSpan(new StrikethroughSpan(), 0, length(), SPAN_EXCLUSIVE_EXCLUSIVE); + return this; + } + /** * Append text as URL * diff --git a/app/src/main/java/com/github/pockethub/android/ui/TabPagerActivity.java b/app/src/main/java/com/github/pockethub/android/ui/TabPagerActivity.java index 7954eb384..3d1362193 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/TabPagerActivity.java +++ b/app/src/main/java/com/github/pockethub/android/ui/TabPagerActivity.java @@ -25,22 +25,26 @@ import com.github.pockethub.android.R; +import butterknife.BindView; + /** * Activity with tabbed pages * * @param */ public abstract class TabPagerActivity - extends PagerActivity implements OnTabChangeListener, TabContentFactory { + extends PagerActivity implements OnTabChangeListener, TabContentFactory { /** * View pager */ + @BindView(R.id.vp_pages) protected ViewPager pager; /** * Tab host */ + @BindView(R.id.sliding_tabs_layout) protected TabLayout slidingTabsLayout; /** @@ -117,11 +121,7 @@ protected void setCurrentItem(final int position) { // Intentionally left blank } - /** - * Get content view to be used when {@link #onCreate(Bundle)} is called - * - * @return layout resource id - */ + @Override protected int getContentView() { return R.layout.pager_with_tabs; } @@ -157,16 +157,9 @@ protected void configureTabPager() { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(getContentView()); - - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); - // On Lollipop, the action bar shadow is provided by default, so have to remove it explicitly getSupportActionBar().setElevation(0); - - pager = (ViewPager) findViewById(R.id.vp_pages); pager.addOnPageChangeListener(this); - slidingTabsLayout = (TabLayout) findViewById(R.id.sliding_tabs_layout); } @Override diff --git a/app/src/main/java/com/github/pockethub/android/ui/TabPagerFragment.java b/app/src/main/java/com/github/pockethub/android/ui/TabPagerFragment.java index 6070cf537..74b878b99 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/TabPagerFragment.java +++ b/app/src/main/java/com/github/pockethub/android/ui/TabPagerFragment.java @@ -17,6 +17,7 @@ package com.github.pockethub.android.ui; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.design.widget.TabLayout; import android.support.v4.view.PagerAdapter; @@ -27,6 +28,8 @@ import com.github.pockethub.android.R; +import butterknife.BindView; + import static android.widget.TabHost.OnTabChangeListener; import static android.widget.TabHost.TabContentFactory; @@ -37,11 +40,13 @@ public abstract class TabPagerFragment@mccrajs's implementation here. */ -public class AccountAuthenticatorAppCompatActivity extends AppCompatActivity implements LifecycleProvider { - private AccountAuthenticatorResponse mAccountAuthenticatorResponse = null; - private Bundle mResultBundle = null; - - private final BehaviorSubject lifecycleSubject = BehaviorSubject.create(); - - @Override - public final Observable lifecycle() { - return lifecycleSubject; - } - - @Override - public final LifecycleTransformer bindUntilEvent(ActivityEvent event) { - return RxLifecycle.bindUntilEvent(lifecycleSubject, event); - } - - @Override - public final LifecycleTransformer bindToLifecycle() { - return RxLifecycleAndroid.bindActivity(lifecycleSubject); - } - - @Override - @CallSuper - protected void onStart() { - super.onStart(); - lifecycleSubject.onNext(ActivityEvent.START); - } - - @Override - @CallSuper - protected void onResume() { - super.onResume(); - lifecycleSubject.onNext(ActivityEvent.RESUME); - } - - @Override - @CallSuper - protected void onPause() { - lifecycleSubject.onNext(ActivityEvent.PAUSE); - super.onPause(); - } - - @Override - @CallSuper - protected void onStop() { - lifecycleSubject.onNext(ActivityEvent.STOP); - super.onStop(); - } - - @Override - @CallSuper - protected void onDestroy() { - lifecycleSubject.onNext(ActivityEvent.DESTROY); - super.onDestroy(); - } +public abstract class AccountAuthenticatorAppCompatActivity extends BaseActivity { + private AccountAuthenticatorResponse accountAuthenticatorResponse = null; + private Bundle resultBundle = null; /** * Set the result that is to be sent as the result of the request that caused this * Activity to be launched. If result is null or this method is never called then @@ -110,7 +51,7 @@ protected void onDestroy() { * @param result this is returned as the result of the AbstractAccountAuthenticator request */ public final void setAccountAuthenticatorResult(Bundle result) { - mResultBundle = result; + resultBundle = result; } /** @@ -121,13 +62,12 @@ public final void setAccountAuthenticatorResult(Bundle result) { @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); - lifecycleSubject.onNext(ActivityEvent.CREATE); - mAccountAuthenticatorResponse = + accountAuthenticatorResponse = getIntent().getParcelableExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE); - if (mAccountAuthenticatorResponse != null) { - mAccountAuthenticatorResponse.onRequestContinued(); + if (accountAuthenticatorResponse != null) { + accountAuthenticatorResponse.onRequestContinued(); } } @@ -136,15 +76,15 @@ protected void onCreate(Bundle icicle) { */ @Override public void finish() { - if (mAccountAuthenticatorResponse != null) { + if (accountAuthenticatorResponse != null) { // send the result bundle back if set, otherwise send an error. - if (mResultBundle != null) { - mAccountAuthenticatorResponse.onResult(mResultBundle); + if (resultBundle != null) { + accountAuthenticatorResponse.onResult(resultBundle); } else { - mAccountAuthenticatorResponse.onError(AccountManager.ERROR_CODE_CANCELED, + accountAuthenticatorResponse.onError(AccountManager.ERROR_CODE_CANCELED, "canceled"); } - mAccountAuthenticatorResponse = null; + accountAuthenticatorResponse = null; } super.finish(); } diff --git a/app/src/main/java/com/github/pockethub/android/ui/base/BaseFragment.java b/app/src/main/java/com/github/pockethub/android/ui/base/BaseFragment.java new file mode 100644 index 000000000..a520b11e9 --- /dev/null +++ b/app/src/main/java/com/github/pockethub/android/ui/base/BaseFragment.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2015 PocketHub + * + * 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.pockethub.android.ui.base; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.CallSuper; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.view.View; + +import butterknife.ButterKnife; +import dagger.android.support.AndroidSupportInjection; +import dagger.android.support.DaggerFragment; +import io.reactivex.Observable; +import io.reactivex.subjects.BehaviorSubject; + +public abstract class BaseFragment extends DaggerFragment { + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + ButterKnife.bind(this, view); + } +} diff --git a/app/src/main/java/com/github/pockethub/android/ui/code/RepositoryCodeFragment.java b/app/src/main/java/com/github/pockethub/android/ui/code/RepositoryCodeFragment.java index b3100cb63..8b2750e93 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/code/RepositoryCodeFragment.java +++ b/app/src/main/java/com/github/pockethub/android/ui/code/RepositoryCodeFragment.java @@ -18,7 +18,9 @@ import android.app.Activity; import android.content.Context; import android.os.Bundle; -import android.text.method.LinkMovementMethod; +import android.support.annotation.NonNull; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -26,9 +28,6 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; @@ -38,21 +37,30 @@ import com.github.pockethub.android.core.code.FullTree.Folder; import com.github.pockethub.android.core.code.RefreshTreeTask; import com.github.pockethub.android.core.ref.RefUtils; +import com.github.pockethub.android.rx.AutoDisposeUtils; import com.github.pockethub.android.ui.DialogFragment; import com.github.pockethub.android.ui.BaseActivity; -import com.github.pockethub.android.ui.HeaderFooterListAdapter; import com.github.pockethub.android.ui.StyledText; +import com.github.pockethub.android.ui.item.code.BlobItem; +import com.github.pockethub.android.ui.item.code.FolderItem; +import com.github.pockethub.android.ui.item.code.PathHeaderItem; import com.github.pockethub.android.ui.ref.BranchFileViewActivity; -import com.github.pockethub.android.ui.ref.CodeTreeAdapter; import com.github.pockethub.android.ui.ref.RefDialog; import com.github.pockethub.android.ui.ref.RefDialogFragment; import com.github.pockethub.android.util.ToastUtils; import com.meisolsson.githubsdk.model.Repository; import com.meisolsson.githubsdk.model.git.GitReference; +import com.xwray.groupie.GroupAdapter; +import com.xwray.groupie.Item; +import com.xwray.groupie.OnItemClickListener; +import com.xwray.groupie.Section; +import java.util.ArrayList; import java.util.LinkedList; +import java.util.List; -import io.reactivex.Single; +import butterknife.BindView; +import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; @@ -63,28 +71,30 @@ /** * Fragment to display a repository's source code tree */ -public class RepositoryCodeFragment extends DialogFragment implements - OnItemClickListener { +public class RepositoryCodeFragment extends DialogFragment implements OnItemClickListener { private static final String TAG = "RepositoryCodeFragment"; - private FullTree tree; - - private ListView listView; + @BindView(android.R.id.list) + protected RecyclerView recyclerView; - private ProgressBar progressView; + @BindView(R.id.pb_loading) + protected ProgressBar progressView; - private TextView branchIconView; + @BindView(R.id.tv_branch_icon) + protected TextView branchIconView; - private TextView branchView; + @BindView(R.id.tv_branch) + protected TextView branchView; - private TextView pathView; + @BindView(R.id.rl_branch) + protected View branchFooterView; - private View pathHeaderView; + private GroupAdapter adapter = new GroupAdapter(); - private View branchFooterView; + private Section mainSection = new Section(); - private HeaderFooterListAdapter adapter; + private FullTree tree; private boolean pathShowing; @@ -105,6 +115,9 @@ public void onAttach(Context context) { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); + adapter.add(mainSection); + adapter.setOnItemClickListener(this); + if (tree == null || folder == null) { refreshTree(null); } else { @@ -120,29 +133,29 @@ public void onCreateOptionsMenu(Menu optionsMenu, MenuInflater inflater) { @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case R.id.m_refresh: - if (tree != null) { - GitReference ref = GitReference.builder() - .ref(tree.reference.ref()) - .build(); - refreshTree(ref); - }else { - refreshTree(null); - } - return true; - default: - return super.onOptionsItemSelected(item); + case R.id.m_refresh: + if (tree != null) { + GitReference ref = GitReference.builder() + .ref(tree.reference.ref()) + .build(); + refreshTree(ref); + } else { + refreshTree(null); + } + return true; + default: + return super.onOptionsItemSelected(item); } } private void showLoading(final boolean loading) { if (loading) { progressView.setVisibility(View.VISIBLE); - listView.setVisibility(View.GONE); + recyclerView.setVisibility(View.GONE); branchFooterView.setVisibility(View.GONE); } else { progressView.setVisibility(View.GONE); - listView.setVisibility(View.VISIBLE); + recyclerView.setVisibility(View.VISIBLE); branchFooterView.setVisibility(View.VISIBLE); } } @@ -153,7 +166,7 @@ private void refreshTree(final GitReference reference) { .refresh() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .compose(this.bindToLifecycle()) + .as(AutoDisposeUtils.bindToLifecycle(this)) .subscribe(fullTree -> { if (folder == null || folder.isRoot()) { setFolder(fullTree, fullTree.root); @@ -219,31 +232,17 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, } @Override - public void onViewCreated(View view, Bundle savedInstanceState) { + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - progressView = (ProgressBar) view.findViewById(R.id.pb_loading); - listView = (ListView) view.findViewById(android.R.id.list); - listView.setOnItemClickListener(this); - - Activity activity = getActivity(); - adapter = new HeaderFooterListAdapter<>(listView, - new CodeTreeAdapter(activity)); + recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); + recyclerView.setAdapter(adapter); - branchFooterView = view.findViewById(R.id.rl_branch); - branchView = (TextView) view.findViewById(R.id.tv_branch); - branchIconView = (TextView) view.findViewById(R.id.tv_branch_icon); branchFooterView.setOnClickListener(v -> switchBranches()); - pathHeaderView = activity.getLayoutInflater().inflate(R.layout.path_item, - null); - pathView = (TextView) pathHeaderView.findViewById(R.id.tv_path); - pathView.setMovementMethod(LinkMovementMethod.getInstance()); if (pathShowing) { - adapter.addHeader(pathHeaderView); + mainSection.setHeader(new PathHeaderItem("")); } - - listView.setAdapter(adapter); } /** @@ -273,8 +272,6 @@ private void setFolder(final FullTree tree, final Folder folder) { branchIconView.setText(R.string.icon_fork); } - adapter.getWrappedAdapter().setIndented(folder.entry != null); - if (folder.entry != null) { int textLightColor = getResources().getColor(R.color.text_light); final String[] segments = folder.entry.path().split("/"); @@ -293,33 +290,45 @@ private void setFolder(final FullTree tree, final Folder folder) { }).append(' ').foreground('/', textLightColor).append(' '); } text.bold(segments[segments.length - 1]); - pathView.setText(text); + if (!pathShowing) { - adapter.addHeader(pathHeaderView); + mainSection.setHeader(new PathHeaderItem(text)); pathShowing = true; } } else if (pathShowing) { - adapter.removeHeader(pathHeaderView); + mainSection.removeHeader(); pathShowing = false; } - adapter.getWrappedAdapter().setItems(folder); - listView.setSelection(0); + + boolean indented = folder.entry != null; + + List items = new ArrayList(); + + for (Folder folder1 : folder.folders.values()) { + items.add(new FolderItem(getActivity(), folder1, indented)); + } + + for (Entry blob : folder.files.values()) { + items.add(new BlobItem(getActivity(), blob, indented)); + } + + mainSection.update(items); } @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - Entry entry = (Entry) parent.getItemAtPosition(position); - if (tree == null || entry == null) { + public void onItemClick(@NonNull Item item, @NonNull View view) { + if (tree == null) { return; } - if (entry instanceof Folder) { - setFolder(tree, (Folder) entry); - } else { + if (item instanceof BlobItem) { + Entry entry = ((BlobItem) item).getData(); startActivity(BranchFileViewActivity.createIntent(repository, tree.branch, entry.entry.path(), entry.entry.sha())); + } else if (item instanceof FolderItem) { + Folder folder = ((FolderItem) item).getData(); + setFolder(tree, folder); } } } diff --git a/app/src/main/java/com/github/pockethub/android/ui/comment/CommentListAdapter.java b/app/src/main/java/com/github/pockethub/android/ui/comment/CommentListAdapter.java deleted file mode 100644 index 853af11bd..000000000 --- a/app/src/main/java/com/github/pockethub/android/ui/comment/CommentListAdapter.java +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright (c) 2015 PocketHub - * - * 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.pockethub.android.ui.comment; - -import android.content.Context; -import android.support.v7.widget.PopupMenu; -import android.text.Html; -import android.text.method.LinkMovementMethod; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.ImageView; - -import com.github.kevinsawicki.wishlist.MultiTypeAdapter; -import com.github.pockethub.android.R; -import com.github.pockethub.android.ui.view.OcticonTextView; -import com.github.pockethub.android.util.AvatarLoader; -import com.github.pockethub.android.util.HttpImageGetter; -import com.github.pockethub.android.util.TimeUtils; -import com.meisolsson.githubsdk.model.GitHubComment; -import com.meisolsson.githubsdk.model.GitHubEvent; -import com.meisolsson.githubsdk.model.Issue; -import com.meisolsson.githubsdk.model.IssueEvent; -import com.meisolsson.githubsdk.model.IssueEventType; - -import java.util.Collection; - -/** - * Adapter for a list of {@link GitHubComment} objects - */ -public class CommentListAdapter extends MultiTypeAdapter { - - private final AvatarLoader avatars; - - private final HttpImageGetter imageGetter; - - /** - * Callback listener to be invoked when user tries to edit a comment. - */ - private final EditCommentListener editCommentListener; - - /** - * Callback listener to be invoked when user tries to edit a comment. - */ - private final DeleteCommentListener deleteCommentListener; - - private final boolean canWrite; - - private final String userName; - - private Context context; - - private Issue issue; - - /** - * Create list adapter - * - * @param inflater - * @param elements - * @param avatars - * @param imageGetter - */ - public CommentListAdapter(LayoutInflater inflater, GitHubComment[] elements, - AvatarLoader avatars, HttpImageGetter imageGetter, Issue issue) { - this(inflater, elements, avatars, imageGetter, null, null, null, false, issue); - this.context = inflater.getContext(); - } - - /** - * Create list adapter - * - * @param inflater - * @param avatars - * @param imageGetter - */ - public CommentListAdapter(LayoutInflater inflater, AvatarLoader avatars, - HttpImageGetter imageGetter, Issue issue) { - this(inflater, null, avatars, imageGetter, issue); - this.context = inflater.getContext(); - } - - /** - * Create list adapter - * - * @param inflater - * @param elements - * @param avatars - * @param imageGetter - * @param userName - * @param canWrite - */ - public CommentListAdapter(LayoutInflater inflater, GitHubComment[] elements, - AvatarLoader avatars, HttpImageGetter imageGetter, - EditCommentListener editCommentListener, DeleteCommentListener deleteCommentListener, - String userName, boolean canWrite, Issue issue) { - super(inflater); - - this.issue = issue; - this.userName = userName; - this.canWrite = canWrite; - this.context = inflater.getContext(); - this.avatars = avatars; - this.imageGetter = imageGetter; - this.editCommentListener = editCommentListener; - this.deleteCommentListener = deleteCommentListener; - setItems(elements); - } - - @Override - protected void update(int position, Object obj, int type) { - if(type == 0) { - updateComment((GitHubComment) obj); - } else { - updateEvent((IssueEvent) obj); - } - } - - protected void updateEvent(final IssueEvent event) { - String message = String.format("%s %s", event.actor().login(), event.event()); - avatars.bind(imageView(2), event.actor()); - - IssueEventType eventType = event.event(); - - switch (eventType) { - case assigned: - case unassigned: - setText(0, OcticonTextView.ICON_PERSON); - textView(0).setTextColor( - context.getResources().getColor(R.color.text_description)); - break; - case labeled: - case unlabeled: - setText(0, OcticonTextView.ICON_TAG); - textView(0).setTextColor( - context.getResources().getColor(R.color.text_description)); - break; - case referenced: - setText(0, OcticonTextView.ICON_BOOKMARK); - textView(0).setTextColor( - context.getResources().getColor(R.color.text_description)); - break; - case milestoned: - case demilestoned: - setText(0, OcticonTextView.ICON_MILESTONE); - textView(0).setTextColor( - context.getResources().getColor(R.color.text_description)); - break; - case closed: - setText(0, OcticonTextView.ICON_ISSUE_CLOSE); - textView(0).setTextColor( - context.getResources().getColor(R.color.issue_event_closed)); - break; - case reopened: - setText(0, OcticonTextView.ICON_ISSUE_REOPEN); - textView(0).setTextColor( - context.getResources().getColor(R.color.issue_event_reopened)); - break; - case renamed: - setText(0, OcticonTextView.ICON_EDIT); - textView(0).setTextColor( - context.getResources().getColor(R.color.text_description)); - break; - case merged: - message += String.format(" commit %s into %s from %s", event.commitId().substring(0, 6), issue.pullRequest().base().ref(), - issue.pullRequest().head().ref()); - setText(0, OcticonTextView.ICON_MERGE); - textView(0).setTextColor( - context.getResources().getColor(R.color.issue_event_merged)); - break; - case locked: - setText(0, OcticonTextView.ICON_LOCK); - textView(0).setTextColor( - context.getResources().getColor(R.color.issue_event_lock)); - break; - case unlocked: - setText(0, OcticonTextView.ICON_KEY); - textView(0).setTextColor( - context.getResources().getColor(R.color.issue_event_lock)); - break; - } - - message += " " + TimeUtils.getRelativeTime(event.createdAt()); - setText(1, Html.fromHtml(message)); - } - - protected void updateComment(final GitHubComment comment) { - imageGetter.bind(textView(0), comment.body(), comment.id()); - avatars.bind(imageView(3), comment.user()); - - setText(1, comment.user().login()); - setText(2, TimeUtils.getRelativeTime(comment.updatedAt())); - - final boolean canEdit = (canWrite || comment.user().login().equals(userName)) - && editCommentListener != null; - - final boolean canDelete = (canWrite || comment.user().login().equals(userName)) - && deleteCommentListener != null; - - final ImageView ivMore = view(4); - - if(!canEdit && !canDelete) { - ivMore.setVisibility(View.INVISIBLE); - } else { - ivMore.setOnClickListener(v -> showMorePopup(ivMore, comment, canEdit, canDelete)); - } - } - - private void showMorePopup(View v, final GitHubComment comment, final boolean canEdit, final boolean canDelete ) { - PopupMenu menu = new PopupMenu(context, v); - menu.inflate(R.menu.comment_popup); - - menu.getMenu().findItem(R.id.m_edit).setEnabled(canEdit); - menu.getMenu().findItem(R.id.m_delete).setEnabled(canDelete); - - menu.setOnMenuItemClickListener(menuItem -> { - switch (menuItem.getItemId()) { - case R.id.m_edit: - if (editCommentListener != null) { - editCommentListener.onEditComment(comment); - } - break; - case R.id.m_delete: - if (deleteCommentListener != null) { - deleteCommentListener.onDeleteComment(comment); - } - break; - } - return false; - }); - - menu.show(); - } - - public MultiTypeAdapter setItems(Collection items) { - if (items == null) { - return this; - } - return setItems(items.toArray()); - } - - public MultiTypeAdapter setItems(final Object[] items) { - if (items == null) { - return this; - } - - this.clear(); - - for (Object item : items) { - if(item instanceof GitHubComment) { - this.addItem(0, item); - } else if(item instanceof GitHubEvent) { - this.addItem(1, item); - } else if(item instanceof IssueEvent) { - this.addItem(1, (item)); - } - } - - notifyDataSetChanged(); - return this; - } - - public void setIssue(Issue issue) { - this.issue = issue; - } - - @Override - protected View initialize(int type, View view) { - view = super.initialize(type, view); - - textView(view, 0).setMovementMethod(LinkMovementMethod.getInstance()); - return view; - } - - @Override - public int getViewTypeCount() { - return 2; - } - - @Override - protected int getChildLayoutId(int type) { - if(type == 0) { - return R.layout.comment_item; - } else { - return R.layout.comment_event_item; - } - } - - @Override - protected int[] getChildViewIds(int type) { - if(type == 0) { - return new int[]{R.id.tv_comment_body, R.id.tv_comment_author, - R.id.tv_comment_date, R.id.iv_avatar, R.id.iv_more}; - } else { - return new int[]{R.id.tv_event_icon, R.id.tv_event, R.id.iv_avatar}; - } - } -} diff --git a/app/src/main/java/com/github/pockethub/android/ui/comment/CreateCommentActivity.java b/app/src/main/java/com/github/pockethub/android/ui/comment/CreateCommentActivity.java index 326234e7a..0cb634553 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/comment/CreateCommentActivity.java +++ b/app/src/main/java/com/github/pockethub/android/ui/comment/CreateCommentActivity.java @@ -26,7 +26,8 @@ import com.github.pockethub.android.util.AvatarLoader; import com.meisolsson.githubsdk.model.GitHubComment; import com.meisolsson.githubsdk.model.git.GitComment; -import com.google.inject.Inject; + +import javax.inject.Inject; import static com.github.pockethub.android.Intents.EXTRA_COMMENT; import static com.github.pockethub.android.ui.view.OcticonTextView.ICON_EDIT; diff --git a/app/src/main/java/com/github/pockethub/android/ui/comment/RawCommentFragment.java b/app/src/main/java/com/github/pockethub/android/ui/comment/RawCommentFragment.java index 4675acede..84ee61b62 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/comment/RawCommentFragment.java +++ b/app/src/main/java/com/github/pockethub/android/ui/comment/RawCommentFragment.java @@ -41,6 +41,7 @@ import com.github.pockethub.android.util.PermissionsUtils; import com.github.pockethub.android.util.ToastUtils; +import butterknife.BindView; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Response; @@ -53,9 +54,11 @@ public class RawCommentFragment extends DialogFragment { private static final int REQUEST_CODE_SELECT_PHOTO = 0; private static final int READ_PERMISSION_REQUEST = 1; - private EditText commentText; + @BindView(R.id.et_comment) + protected EditText commentText; - private FloatingActionButton addImageFab; + @BindView(R.id.fab_add_image) + protected FloatingActionButton addImageFab; /** * Text to populate comment window. @@ -63,12 +66,9 @@ public class RawCommentFragment extends DialogFragment { private String initComment; @Override - public void onViewCreated(View view, Bundle savedInstanceState) { + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - commentText = (EditText) view.findViewById(R.id.et_comment); - addImageFab = (FloatingActionButton) view.findViewById(R.id.fab_add_image); - // @TargetApi(…) required to ensure build passes // noinspection Convert2Lambda addImageFab.setOnClickListener(new View.OnClickListener() { diff --git a/app/src/main/java/com/github/pockethub/android/ui/comment/RenderedCommentFragment.java b/app/src/main/java/com/github/pockethub/android/ui/comment/RenderedCommentFragment.java index 255cab23b..07d2e78fa 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/comment/RenderedCommentFragment.java +++ b/app/src/main/java/com/github/pockethub/android/ui/comment/RenderedCommentFragment.java @@ -15,50 +15,42 @@ */ package com.github.pockethub.android.ui.comment; +import android.content.Context; import android.os.Bundle; -import android.support.v4.app.LoaderManager.LoaderCallbacks; -import android.support.v4.content.Loader; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; import android.widget.ProgressBar; import android.widget.TextView; -import com.github.kevinsawicki.wishlist.Keyboard; import com.github.pockethub.android.R; import com.github.pockethub.android.ui.DialogFragment; import com.github.pockethub.android.ui.MarkdownLoader; import com.github.pockethub.android.util.HttpImageGetter; import com.github.pockethub.android.util.ToastUtils; import com.meisolsson.githubsdk.model.Repository; -import com.google.inject.Inject; +import javax.inject.Inject; -import java.io.Serializable; +import butterknife.BindView; /** * Fragment to display rendered comment fragment */ -public class RenderedCommentFragment extends DialogFragment implements - LoaderCallbacks { +public class RenderedCommentFragment extends DialogFragment { private static final String ARG_TEXT = "text"; private static final String ARG_REPO = "repo"; - private ProgressBar progress; + @BindView(R.id.pb_loading) + protected ProgressBar progress; - private TextView bodyText; + @BindView(R.id.tv_comment_body) + protected TextView bodyText; @Inject - private HttpImageGetter imageGetter; - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - progress = (ProgressBar) view.findViewById(R.id.pb_loading); - bodyText = (TextView) view.findViewById(R.id.tv_comment_body); - } + protected HttpImageGetter imageGetter; /** * Set text to render @@ -67,16 +59,23 @@ public void onViewCreated(View view, Bundle savedInstanceState) { * @param repo */ public void setText(final String raw, final Repository repo) { - Bundle args = new Bundle(); - args.putCharSequence(ARG_TEXT, raw); - if (repo instanceof Serializable) { - args.putParcelable(ARG_REPO, repo); - } - getLoaderManager().restartLoader(0, args, this); - Keyboard.hideSoftInput(bodyText); + loadMarkdown(raw, repo); + hideSoftKeyboard(); showLoading(true); } + private void hideSoftKeyboard() { + Context context = getContext(); + if (context != null) { + InputMethodManager imm = + (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + + if (imm != null) { + imm.hideSoftInputFromWindow(bodyText.getWindowToken(), 0); + } + } + } + private void showLoading(final boolean loading) { if (loading) { progress.setVisibility(View.VISIBLE); @@ -93,25 +92,11 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, return inflater.inflate(R.layout.fragment_comment_preview, null); } - @Override - public Loader onCreateLoader(int loader, Bundle args) { - final CharSequence raw = args.getCharSequence(ARG_TEXT); - final Repository repo = args.getParcelable(ARG_REPO); - return new MarkdownLoader(getActivity(), repo, raw.toString(), - imageGetter, true); - } - - @Override - public void onLoadFinished(Loader loader, - CharSequence rendered) { - if (rendered == null) { - ToastUtils.show(getActivity(), R.string.error_rendering_markdown); - } - bodyText.setText(rendered); - showLoading(false); - } - - @Override - public void onLoaderReset(Loader loader) { + private void loadMarkdown(String raw, Repository repo) { + MarkdownLoader.load(getActivity(), raw, repo, imageGetter, true) + .subscribe(rendered -> { + bodyText.setText(rendered); + showLoading(false); + } , e -> ToastUtils.show(getActivity(), R.string.error_rendering_markdown)); } } diff --git a/app/src/main/java/com/github/pockethub/android/ui/commit/CommitCompareListFragment.java b/app/src/main/java/com/github/pockethub/android/ui/commit/CommitCompareListFragment.java index a17f5ccf5..2c33b5319 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/commit/CommitCompareListFragment.java +++ b/app/src/main/java/com/github/pockethub/android/ui/commit/CommitCompareListFragment.java @@ -18,6 +18,10 @@ import android.app.Activity; import android.content.Context; import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.Menu; @@ -25,16 +29,16 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.ListView; import android.widget.ProgressBar; -import android.widget.TextView; import com.github.pockethub.android.R; import com.github.pockethub.android.core.commit.CommitUtils; +import com.github.pockethub.android.rx.AutoDisposeUtils; import com.github.pockethub.android.ui.DialogFragment; -import com.github.pockethub.android.ui.HeaderFooterListAdapter; +import com.github.pockethub.android.ui.item.commit.CommitItem; +import com.github.pockethub.android.ui.item.TextItem; +import com.github.pockethub.android.ui.item.commit.CommitFileHeaderItem; +import com.github.pockethub.android.ui.item.commit.CommitFileLineItem; import com.github.pockethub.android.util.AvatarLoader; import com.github.pockethub.android.util.ToastUtils; import com.meisolsson.githubsdk.core.ServiceGenerator; @@ -43,13 +47,19 @@ import com.meisolsson.githubsdk.model.GitHubFile; import com.meisolsson.githubsdk.model.Repository; import com.meisolsson.githubsdk.service.repositories.RepositoryCommitService; -import com.google.inject.Inject; +import com.xwray.groupie.GroupAdapter; +import com.xwray.groupie.Item; +import com.xwray.groupie.OnItemClickListener; +import com.xwray.groupie.Section; + +import javax.inject.Inject; import java.text.MessageFormat; -import java.util.Collection; +import java.util.ArrayList; import java.util.Collections; import java.util.List; +import butterknife.BindView; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; @@ -60,14 +70,15 @@ /** * Fragment to display a list of commits being compared */ -public class CommitCompareListFragment extends DialogFragment implements - OnItemClickListener { +public class CommitCompareListFragment extends DialogFragment implements OnItemClickListener { - private DiffStyler diffStyler; + @BindView(android.R.id.list) + protected RecyclerView list; - private ListView list; + @BindView(R.id.pb_loading) + protected ProgressBar progress; - private ProgressBar progress; + private DiffStyler diffStyler; private Repository repository; @@ -76,9 +87,15 @@ public class CommitCompareListFragment extends DialogFragment implements private String head; @Inject - private AvatarLoader avatars; + protected AvatarLoader avatars; + + private GroupAdapter adapter = new GroupAdapter(); + + private Section mainSection = new Section(); - private HeaderFooterListAdapter adapter; + private Section commitsSection = new Section(); + + private Section filesSection = new Section(); private CommitCompare compare; @@ -100,6 +117,29 @@ public void onCreate(Bundle savedInstanceState) { compareCommits(); } + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mainSection.add(commitsSection); + mainSection.add(filesSection); + adapter.add(mainSection); + + adapter.setOnItemClickListener(this); + } + + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + list.setLayoutManager(new LinearLayoutManager(getActivity())); + list.setAdapter(adapter); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_commit_diff_list, container); + } + @Override public void onCreateOptionsMenu(final Menu optionsMenu, final MenuInflater inflater) { @@ -126,7 +166,7 @@ private void compareCommits() { .compareCommits(repository.owner().login(), repository.name(), base, head) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .compose(this.bindToLifecycle()) + .as(AutoDisposeUtils.bindToLifecycle(this)) .subscribe(response -> { CommitCompare compareCommit = response.body(); List files = compareCommit.files(); @@ -146,76 +186,52 @@ private void updateList(CommitCompare compare) { progress.setVisibility(View.GONE); list.setVisibility(View.VISIBLE); - LayoutInflater inflater = getActivity().getLayoutInflater(); - adapter.clearHeaders(); - adapter.getWrappedAdapter().clear(); - List commits = compare.commits(); if (!commits.isEmpty()) { - View commitHeader = inflater.inflate(R.layout.commit_details_header, - null); - ((TextView) commitHeader.findViewById(R.id.tv_commit_summary)) - .setText(MessageFormat.format( - getString(R.string.comparing_commits), commits.size())); - adapter.addHeader(commitHeader); - adapter.addHeader(inflater.inflate(R.layout.list_divider, null)); - CommitListAdapter commitAdapter = new CommitListAdapter( - R.layout.commit_item, inflater, commits, avatars); - for (int i = 0; i < commits.size(); i++) { - Commit commit = commits.get(i); - View view = commitAdapter.getView(i, null, null); - adapter.addHeader(view, commit, true); - adapter.addHeader(inflater.inflate(R.layout.list_divider, null)); + String comparingCommits = getString(R.string.comparing_commits); + String text = MessageFormat.format(comparingCommits, commits.size()); + commitsSection.setHeader( + new TextItem(R.layout.commit_details_header, R.id.tv_commit_summary, text) + ); + + List items = new ArrayList<>(); + for (Commit commit : commits) { + items.add(new CommitItem(avatars, commit)); } + + commitsSection.update(items); } - CommitFileListAdapter rootAdapter = adapter.getWrappedAdapter(); - rootAdapter.clear(); List files = compare.files(); if (!files.isEmpty()) { - addFileStatHeader(files, inflater); - for (GitHubFile file : files) { - rootAdapter.addItem(file); - } + filesSection.setHeader( + new TextItem(R.layout.commit_compare_file_details_header, + R.id.tv_commit_file_summary, CommitUtils.formatStats(files)) + ); + filesSection.update(createFileSections(files)); } } - private void addFileStatHeader(List files, - LayoutInflater inflater) { - View fileHeader = inflater.inflate( - R.layout.commit_compare_file_details_header, null); - ((TextView) fileHeader.findViewById(R.id.tv_commit_file_summary)) - .setText(CommitUtils.formatStats(files)); - adapter.addHeader(fileHeader); - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - list = (ListView) view.findViewById(android.R.id.list); - progress = (ProgressBar) view.findViewById(R.id.pb_loading); + private List

createFileSections(List files) { + List
sections = new ArrayList<>(); - LayoutInflater inflater = getActivity().getLayoutInflater(); + for (GitHubFile file : files) { + Section section = new Section(new CommitFileHeaderItem(getActivity(), file)); + List lines = diffStyler.get(file.filename()); + for (CharSequence line : lines) { + section.add(new CommitFileLineItem(diffStyler, line)); + } - list.setOnItemClickListener(this); + sections.add(section); + } - adapter = new HeaderFooterListAdapter<>(list, - new CommitFileListAdapter(inflater, diffStyler, null, null)); - adapter.addFooter(inflater.inflate(R.layout.footer_separator, null)); - list.setAdapter(adapter); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_commit_diff_list, container); + return sections; } private void openCommit(final Commit commit) { if (compare != null) { int commitPosition = 0; - Collection commits = compare.commits(); + List commits = compare.commits(); for (Commit candidate : commits) { if (commit == candidate) { break; @@ -224,7 +240,11 @@ private void openCommit(final Commit commit) { } } if (commitPosition < commits.size()) { - startActivity(CommitViewActivity.createIntent(repository, commitPosition, commits)); + String[] ids = new String[commits.size()]; + for (int i = 0; i < commits.size(); i++) { + ids[i] = commits.get(i).sha(); + } + startActivity(CommitViewActivity.createIntent(repository, commitPosition, ids)); } } else { startActivity(CommitViewActivity.createIntent(repository, @@ -239,27 +259,26 @@ private void openFile(final GitHubFile file) { } } - private void openLine(AdapterView parent, int position) { + private void openLine(GroupAdapter adapter, int position) { Object item; while (--position >= 0) { - item = parent.getItemAtPosition(position); - if (item instanceof GitHubFile) { - openFile((GitHubFile) item); + item = adapter.getItem(position); + if (item instanceof CommitFileHeaderItem) { + openFile(((CommitFileHeaderItem) item).getData()); return; } } } @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - Object item = parent.getItemAtPosition(position); - if (item instanceof Commit) { - openCommit((Commit) item); - } else if (item instanceof GitHubFile) { - openFile((GitHubFile) item); - } else if (item instanceof CharSequence) { - openLine(parent, position); + public void onItemClick(@NonNull Item item, @NonNull View view) { + if (item instanceof CommitItem) { + openCommit(((CommitItem) item).getData()); + } else if (item instanceof CommitFileHeaderItem) { + openFile(((CommitFileHeaderItem) item).getData()); + } else if (item instanceof CommitFileLineItem) { + int position = adapter.getAdapterPosition(item); + openLine(adapter, position); } } } diff --git a/app/src/main/java/com/github/pockethub/android/ui/commit/CommitCompareViewActivity.java b/app/src/main/java/com/github/pockethub/android/ui/commit/CommitCompareViewActivity.java index f3eeab834..3eb265890 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/commit/CommitCompareViewActivity.java +++ b/app/src/main/java/com/github/pockethub/android/ui/commit/CommitCompareViewActivity.java @@ -30,7 +30,7 @@ import com.github.pockethub.android.ui.repo.RepositoryViewActivity; import com.github.pockethub.android.util.AvatarLoader; import com.github.pockethub.android.util.InfoUtils; -import com.google.inject.Inject; +import javax.inject.Inject; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP; @@ -63,7 +63,7 @@ public static Intent createIntent(final Repository repository, private Repository repository; @Inject - private AvatarLoader avatars; + protected AvatarLoader avatars; private Fragment fragment; @@ -73,10 +73,6 @@ protected void onCreate(Bundle savedInstanceState) { repository = getIntent().getParcelableExtra(EXTRA_REPOSITORY); - setContentView(R.layout.commit_compare); - - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); - ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setSubtitle(InfoUtils.createRepoId(repository)); @@ -86,6 +82,11 @@ protected void onCreate(Bundle savedInstanceState) { .findFragmentById(android.R.id.list); } + @Override + protected int getContentView() { + return R.layout.commit_compare; + } + @Override public boolean onCreateOptionsMenu(Menu optionsMenu) { if (fragment != null) { diff --git a/app/src/main/java/com/github/pockethub/android/ui/commit/CommitDiffListFragment.java b/app/src/main/java/com/github/pockethub/android/ui/commit/CommitDiffListFragment.java index dc0830102..1fda3579a 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/commit/CommitDiffListFragment.java +++ b/app/src/main/java/com/github/pockethub/android/ui/commit/CommitDiffListFragment.java @@ -20,6 +20,9 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.Menu; @@ -27,27 +30,28 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.ImageView; -import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import com.afollestad.materialdialogs.MaterialDialog; +import com.github.pockethub.android.core.commit.RefreshCommitTaskFactory; +import com.github.pockethub.android.rx.AutoDisposeUtils; +import com.github.pockethub.android.ui.item.LoadingItem; +import com.github.pockethub.android.ui.item.commit.CommitCommentItem; +import com.github.pockethub.android.ui.item.TextItem; +import com.github.pockethub.android.ui.item.commit.CommitFileHeaderItem; +import com.github.pockethub.android.ui.item.commit.CommitFileLineItem; +import com.github.pockethub.android.ui.item.commit.CommitHeaderItem; +import com.github.pockethub.android.ui.item.commit.CommitParentItem; import com.meisolsson.githubsdk.model.Commit; import com.meisolsson.githubsdk.model.GitHubFile; import com.meisolsson.githubsdk.model.Repository; import com.github.pockethub.android.R; import com.github.pockethub.android.core.commit.CommitStore; import com.github.pockethub.android.core.commit.CommitUtils; -import com.github.pockethub.android.core.commit.FullCommit; import com.github.pockethub.android.core.commit.FullCommitFile; -import com.github.pockethub.android.core.commit.RefreshCommitTask; import com.github.pockethub.android.ui.DialogFragment; -import com.github.pockethub.android.ui.HeaderFooterListAdapter; -import com.github.pockethub.android.ui.StyledText; import com.github.pockethub.android.util.AvatarLoader; import com.github.pockethub.android.util.HttpImageGetter; import com.github.pockethub.android.util.InfoUtils; @@ -55,18 +59,22 @@ import com.github.pockethub.android.util.ToastUtils; import com.meisolsson.githubsdk.model.git.GitComment; import com.meisolsson.githubsdk.model.git.GitCommit; -import com.google.inject.Inject; +import com.xwray.groupie.GroupAdapter; +import com.xwray.groupie.Item; +import com.xwray.groupie.OnItemClickListener; +import com.xwray.groupie.Section; +import javax.inject.Inject; + +import java.util.ArrayList; import java.util.Collections; -import java.util.Date; import java.util.List; -import io.reactivex.Single; +import butterknife.BindView; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; import static android.app.Activity.RESULT_OK; -import static android.graphics.Paint.UNDERLINE_TEXT_FLAG; import static com.github.pockethub.android.Intents.EXTRA_BASE; import static com.github.pockethub.android.Intents.EXTRA_COMMENT; import static com.github.pockethub.android.Intents.EXTRA_REPOSITORY; @@ -75,14 +83,16 @@ /** * Fragment to display commit details with diff output */ -public class CommitDiffListFragment extends DialogFragment implements - OnItemClickListener { +public class CommitDiffListFragment extends DialogFragment implements OnItemClickListener { - private DiffStyler diffStyler; - private ListView list; + @BindView(android.R.id.list) + protected RecyclerView list; - private ProgressBar progress; + @BindView(R.id.pb_loading) + protected ProgressBar progress; + + private DiffStyler diffStyler; private Repository repository; @@ -95,37 +105,26 @@ public class CommitDiffListFragment extends DialogFragment implements private List files; @Inject - private AvatarLoader avatars; + protected AvatarLoader avatars; @Inject - private CommitStore store; - - private View loadingView; - - private View commitHeader; - - private TextView commitMessage; - - private View authorArea; - - private ImageView authorAvatar; + protected CommitStore store; - private TextView authorName; + private GroupAdapter adapter = new GroupAdapter(); - private TextView authorDate; + private Section mainSection = new Section(); - private View committerArea; + private Section commitSection = new Section(); - private ImageView committerAvatar; + private Section filesSection = new Section(); - private TextView committerName; + private Section commentSection = new Section(); - private TextView committerDate; - - private HeaderFooterListAdapter adapter; + @Inject + protected RefreshCommitTaskFactory refreshCommitTaskFactory; @Inject - private HttpImageGetter commentImageGetter; + protected HttpImageGetter commentImageGetter; @Override public void onCreate(Bundle savedInstanceState) { @@ -140,14 +139,18 @@ public void onCreate(Bundle savedInstanceState) { public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - commit = store.getCommit(repository, base); + mainSection.add(commitSection); + mainSection.add(filesSection); + mainSection.add(commentSection); - ((TextView) loadingView.findViewById(R.id.tv_loading)) - .setText(R.string.loading_files_and_comments); + adapter.add(mainSection); + adapter.setOnItemClickListener(this); + + commit = store.getCommit(repository, base); if (files == null || (commit != null && commit.commit().commentCount() > 0 && comments == null)) { - adapter.addFooter(loadingView); + mainSection.setFooter(new LoadingItem(R.string.loading_files_and_comments)); } if (commit != null && comments != null && files != null) { @@ -160,6 +163,20 @@ public void onActivityCreated(Bundle savedInstanceState) { } } + @Override + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + diffStyler = new DiffStyler(getResources()); + + list.setLayoutManager(new LinearLayoutManager(getActivity())); + list.setAdapter(adapter); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_commit_diff_list, container, false); + } + private void addComment(final GitComment comment) { if (comments != null && files != null) { comments.add(comment); @@ -238,11 +255,11 @@ private void shareCommit() { } private void refreshCommit() { - new RefreshCommitTask(getActivity(), repository, base, commentImageGetter) + refreshCommitTaskFactory.create(getActivity(), repository, base) .refresh() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .compose(this.bindToLifecycle()) + .as(AutoDisposeUtils.bindToLifecycle(this)) .subscribe(full -> { List files = full.getCommit().files(); diffStyler.setFiles(files); @@ -257,89 +274,25 @@ private void refreshCommit() { }); } - private boolean isDifferentCommitter(final String author, - final String committer) { - return committer != null && !committer.equals(author); - } - - private void addCommitDetails(Commit commit) { - adapter.addHeader(commitHeader); - - commitMessage.setText(commit.commit().message()); - - String commitAuthor = CommitUtils.getAuthor(commit); - String commitCommitter = CommitUtils.getCommitter(commit); - - if (commitAuthor != null) { - CommitUtils.bindAuthor(commit, avatars, authorAvatar); - authorName.setText(commitAuthor); - StyledText styledAuthor = new StyledText(); - styledAuthor.append(getString(R.string.authored)); - - Date commitAuthorDate = CommitUtils.getAuthorDate(commit); - if (commitAuthorDate != null) { - styledAuthor.append(' ').append(commitAuthorDate); - } - - authorDate.setText(styledAuthor); - authorArea.setVisibility(View.VISIBLE); - } else { - authorArea.setVisibility(View.GONE); - } - - if (isDifferentCommitter(commitAuthor, commitCommitter)) { - CommitUtils.bindCommitter(commit, avatars, committerAvatar); - committerName.setText(commitCommitter); - StyledText styledCommitter = new StyledText(); - styledCommitter.append(getString(R.string.committed)); - - Date commitCommitterDate = CommitUtils.getCommitterDate(commit); - if (commitCommitterDate != null) { - styledCommitter.append(' ').append(commitCommitterDate); - } - - committerDate.setText(styledCommitter); - committerArea.setVisibility(View.VISIBLE); - } else { - committerArea.setVisibility(View.GONE); - } - } - - private void addDiffStats(Commit commit, LayoutInflater inflater) { - View fileHeader = inflater.inflate(R.layout.commit_file_details_header, - null); - ((TextView) fileHeader.findViewById(R.id.tv_commit_file_summary)) - .setText(CommitUtils.formatStats(commit.files())); - adapter.addHeader(fileHeader); - } - - private void addCommitParents(Commit commit, - LayoutInflater inflater) { + private void addCommitParents(Commit commit) { List parents = commit.parents(); if (parents == null || parents.isEmpty()) { return; } + List items = new ArrayList<>(); for (Commit parent : parents) { - View parentView = inflater.inflate(R.layout.commit_parent_item, null); - TextView parentIdText = (TextView) parentView - .findViewById(R.id.tv_commit_id); - parentIdText.setPaintFlags(parentIdText.getPaintFlags() - | UNDERLINE_TEXT_FLAG); - StyledText parentText = new StyledText(); - parentText.append(getString(R.string.parent_prefix)); - parentText.monospace(CommitUtils.abbreviate(parent.sha())); - parentIdText.setText(parentText); - adapter.addHeader(parentView, parent, true); + items.add(new CommitParentItem(getActivity(), parent)); } + commitSection.update(items); } private void updateHeader(Commit commit) { progress.setVisibility(View.GONE); list.setVisibility(View.VISIBLE); - addCommitDetails(commit); - addCommitParents(commit, getActivity().getLayoutInflater()); + mainSection.setHeader(new CommitHeaderItem(avatars, getActivity(), commit)); + addCommitParents(commit); } private void updateList(Commit commit, List comments, List files) { @@ -351,65 +304,44 @@ private void updateList(Commit commit, List comments, List comments, - List files) { - CommitFileListAdapter rootAdapter = adapter.getWrappedAdapter(); - rootAdapter.clear(); - for (FullCommitFile file : files) { - rootAdapter.addItem(file); - } + private void updateItems(List comments, List files) { + filesSection.update(createFileSections(files)); + + List items = new ArrayList<>(); for (GitComment comment : comments) { - rootAdapter.addComment(comment); + items.add(new CommitCommentItem(avatars, commentImageGetter, comment)); } + commentSection.update(items); } - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - list = (ListView) view.findViewById(android.R.id.list); - progress = (ProgressBar) view.findViewById(R.id.pb_loading); - - diffStyler = new DiffStyler(getResources()); - - list.setOnItemClickListener(this); - - LayoutInflater inflater = getActivity().getLayoutInflater(); - - adapter = new HeaderFooterListAdapter<>(list, - new CommitFileListAdapter(inflater, diffStyler, avatars, - commentImageGetter)); - adapter.addFooter(inflater.inflate(R.layout.footer_separator, null)); - list.setAdapter(adapter); - - commitHeader = inflater.inflate(R.layout.commit_header, null); - commitMessage = (TextView) commitHeader - .findViewById(R.id.tv_commit_message); - - authorArea = commitHeader.findViewById(R.id.ll_author); - authorAvatar = (ImageView) commitHeader.findViewById(R.id.iv_author); - authorName = (TextView) commitHeader.findViewById(R.id.tv_author); - authorDate = (TextView) commitHeader.findViewById(R.id.tv_author_date); - - committerArea = commitHeader.findViewById(R.id.ll_committer); - committerAvatar = (ImageView) commitHeader - .findViewById(R.id.iv_committer); - committerName = (TextView) commitHeader.findViewById(R.id.tv_committer); - committerDate = (TextView) commitHeader.findViewById(R.id.tv_commit_date); + private List
createFileSections(List files) { + List
sections = new ArrayList<>(); + for (FullCommitFile file : files) { + Section section = new Section(new CommitFileHeaderItem(getActivity(), file.getFile())); + List lines = diffStyler.get(file.getFile().filename()); + int number = 0; + for (CharSequence line : lines) { + section.add(new CommitFileLineItem(diffStyler, line)); + for (GitComment comment : file.get(number)) { + section.add(new CommitCommentItem(avatars, commentImageGetter, comment, true)); + } + number++; + } - loadingView = inflater.inflate(R.layout.loading_item, null); - } + sections.add(section); + } - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_commit_diff_list, container, false); + return sections; } private void showFileOptions(CharSequence line, final int position, final GitHubFile file) { @@ -465,10 +397,9 @@ private void openFile(GitHubFile file) { * * @param position * @param item - * @param parent + * @param adapter */ - private void selectPreviousFile(int position, Object item, - AdapterView parent) { + private void selectPreviousFile(int position, Object item, GroupAdapter adapter) { CharSequence line; if (item instanceof CharSequence) { line = (CharSequence) item; @@ -478,11 +409,11 @@ private void selectPreviousFile(int position, Object item, int linePosition = 0; while (--position >= 0) { - item = parent.getItemAtPosition(position); + item = adapter.getItem(position); - if (item instanceof GitHubFile) { + if (item instanceof CommitFileHeaderItem) { if (line != null) { - showFileOptions(line, linePosition, (GitHubFile) item); + showFileOptions(line, linePosition, ((CommitFileHeaderItem) item).getData()); } break; } else if (item instanceof CharSequence) { @@ -496,19 +427,19 @@ private void selectPreviousFile(int position, Object item, } @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - Object item = parent.getItemAtPosition(position); - if (item instanceof Commit) { - startActivity(CommitViewActivity.createIntent(repository, - ((Commit) item).sha())); - } else if (item instanceof GitHubFile) { - openFile((GitHubFile) item); + public void onItemClick(@NonNull Item item, @NonNull View view) { + int position = adapter.getAdapterPosition(item); + + if (item instanceof CommitParentItem) { + String sha = ((CommitParentItem) item).getData().sha(); + startActivity(CommitViewActivity.createIntent(repository, sha)); + } else if (item instanceof CommitFileHeaderItem) { + openFile(((CommitFileHeaderItem) item).getData()); } else if (item instanceof CharSequence) { - selectPreviousFile(position, item, parent); - } else if (item instanceof GitComment) { - if (!TextUtils.isEmpty(((GitComment) item).path())) { - selectPreviousFile(position, item, parent); + selectPreviousFile(position, item, adapter); + } else if (item instanceof CommitCommentItem) { + if (!TextUtils.isEmpty(((CommitCommentItem) item).getData().path())) { + selectPreviousFile(position, item, adapter); } } } diff --git a/app/src/main/java/com/github/pockethub/android/ui/commit/CommitFileListAdapter.java b/app/src/main/java/com/github/pockethub/android/ui/commit/CommitFileListAdapter.java deleted file mode 100644 index eb697783a..000000000 --- a/app/src/main/java/com/github/pockethub/android/ui/commit/CommitFileListAdapter.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright (c) 2015 PocketHub - * - * 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.pockethub.android.ui.commit; - -import android.content.res.Resources; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; - -import com.github.kevinsawicki.wishlist.MultiTypeAdapter; -import com.github.pockethub.android.R; -import com.github.pockethub.android.core.commit.FullCommitFile; -import com.github.pockethub.android.ui.StyledText; -import com.github.pockethub.android.util.AvatarLoader; -import com.github.pockethub.android.util.HttpImageGetter; -import com.github.pockethub.android.util.TimeUtils; -import com.meisolsson.githubsdk.model.GitHubFile; -import com.meisolsson.githubsdk.model.git.GitComment; - -import java.util.List; - -import static com.github.kevinsawicki.wishlist.ViewUpdater.FORMAT_INT; - - -/** - * Adapter to display a list of files changed in commits - */ -public class CommitFileListAdapter extends MultiTypeAdapter { - - private static final int TYPE_FILE_HEADER = 0; - - private static final int TYPE_FILE_LINE = 1; - - private static final int TYPE_LINE_COMMENT = 2; - - private static final int TYPE_COMMENT = 3; - - private final DiffStyler diffStyler; - - private final HttpImageGetter imageGetter; - - private final AvatarLoader avatars; - - private final int addTextColor; - - private final int removeTextColor; - - /** - * @param inflater - * @param diffStyler - * @param avatars - * @param imageGetter - */ - public CommitFileListAdapter(final LayoutInflater inflater, - final DiffStyler diffStyler, final AvatarLoader avatars, - final HttpImageGetter imageGetter) { - super(inflater); - - this.diffStyler = diffStyler; - this.avatars = avatars; - this.imageGetter = imageGetter; - - Resources resources = inflater.getContext().getResources(); - addTextColor = resources.getColor(R.color.diff_add_text); - removeTextColor = resources.getColor(R.color.diff_remove_text); - } - - @Override - public int getViewTypeCount() { - return 4; - } - - @Override - public long getItemId(int position) { - switch (getItemViewType(position)) { - case TYPE_FILE_HEADER: - String sha = ((GitHubFile) getItem(position)).sha(); - if (!TextUtils.isEmpty(sha)) { - return sha.hashCode(); - } else { - return super.getItemId(position); - } - case TYPE_COMMENT: - case TYPE_LINE_COMMENT: - return ((GitComment) getItem(position)).id(); - default: - return super.getItemId(position); - } - - } - - /** - * Add file to adapter - * - * @param file - */ - public void addItem(final FullCommitFile file) { - addItem(TYPE_FILE_HEADER, file.getFile()); - List lines = diffStyler.get(file.getFile().filename()); - int number = 0; - for (CharSequence line : lines) { - addItem(TYPE_FILE_LINE, line); - for (GitComment comment : file.get(number)) { - addItem(TYPE_LINE_COMMENT, comment); - } - number++; - } - } - - /** - * Add file to adapter - * - * @param file - */ - public void addItem(final GitHubFile file) { - addItem(TYPE_FILE_HEADER, file); - addItems(TYPE_FILE_LINE, diffStyler.get(file.filename())); - } - - /** - * Add comment to adapter - * - * @param comment - */ - public void addComment(final GitComment comment) { - addItem(TYPE_COMMENT, comment); - } - - @Override - protected int getChildLayoutId(final int type) { - switch (type) { - case TYPE_FILE_HEADER: - return R.layout.commit_diff_file_header; - case TYPE_FILE_LINE: - return R.layout.commit_diff_line; - case TYPE_LINE_COMMENT: - return R.layout.diff_comment_item; - case TYPE_COMMENT: - return R.layout.commit_comment_item; - default: - return -1; - } - } - - @Override - protected int[] getChildViewIds(final int type) { - switch (type) { - case TYPE_FILE_HEADER: - return new int[] { R.id.tv_name, R.id.tv_folder, R.id.tv_stats }; - case TYPE_FILE_LINE: - return new int[] { R.id.tv_diff }; - case TYPE_LINE_COMMENT: - case TYPE_COMMENT: - return new int[] { R.id.tv_comment_body, R.id.iv_avatar, - R.id.tv_comment_author, R.id.tv_comment_date }; - default: - return null; - } - } - - @Override - protected void update(final int position, final Object item, final int type) { - switch (type) { - case TYPE_FILE_HEADER: - GitHubFile file = (GitHubFile) item; - String path = file.filename(); - int lastSlash = path.lastIndexOf('/'); - if (lastSlash != -1) { - setText(0, path.substring(lastSlash + 1)); - setText(1, path.substring(0, lastSlash + 1)).setVisibility(View.VISIBLE); - } else { - setText(0, path); - setGone(1, true); - } - - StyledText stats = new StyledText(); - stats.foreground('+', addTextColor); - stats.foreground(FORMAT_INT.format(file.additions()), - addTextColor); - stats.append(' ').append(' ').append(' '); - stats.foreground('-', removeTextColor); - stats.foreground(FORMAT_INT.format(file.deletions()), - removeTextColor); - setText(2, stats); - return; - case TYPE_FILE_LINE: - CharSequence text = (CharSequence) item; - diffStyler.updateColors((CharSequence) item, setText(0, text)); - return; - case TYPE_LINE_COMMENT: - case TYPE_COMMENT: - GitComment comment = (GitComment) item; - avatars.bind(imageView(1), comment.user()); - setText(2, comment.user().login()); - setText(3, TimeUtils.getRelativeTime(comment.updatedAt())); - imageGetter.bind(textView(0), comment.bodyHtml(), comment.id()); - } - } -} diff --git a/app/src/main/java/com/github/pockethub/android/ui/commit/CommitFileViewActivity.java b/app/src/main/java/com/github/pockethub/android/ui/commit/CommitFileViewActivity.java index 8f2921630..3002f5e84 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/commit/CommitFileViewActivity.java +++ b/app/src/main/java/com/github/pockethub/android/ui/commit/CommitFileViewActivity.java @@ -17,8 +17,6 @@ import android.content.Intent; import android.os.Bundle; -import android.support.v4.app.LoaderManager.LoaderCallbacks; -import android.support.v4.content.Loader; import android.support.v7.app.ActionBar; import android.support.v7.widget.Toolbar; import android.text.TextUtils; @@ -30,6 +28,7 @@ import android.webkit.WebView; import android.widget.ProgressBar; +import com.github.pockethub.android.rx.AutoDisposeUtils; import com.meisolsson.githubsdk.core.ServiceGenerator; import com.meisolsson.githubsdk.model.GitHubFile; import com.meisolsson.githubsdk.model.Repository; @@ -48,8 +47,9 @@ import com.github.pockethub.android.util.ToastUtils; import com.meisolsson.githubsdk.model.git.GitBlob; import com.meisolsson.githubsdk.service.git.GitService; -import com.google.inject.Inject; +import javax.inject.Inject; +import butterknife.BindView; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; @@ -63,8 +63,7 @@ /** * Activity to display the contents of a file in a commit */ -public class CommitFileViewActivity extends BaseActivity implements - LoaderCallbacks { +public class CommitFileViewActivity extends BaseActivity { private static final String TAG = "CommitFileViewActivity"; @@ -89,6 +88,12 @@ public static Intent createIntent(Repository repository, String commit, GitHubFi return builder.toIntent(); } + @BindView(R.id.pb_loading) + protected ProgressBar loadingBar; + + @BindView(R.id.wv_code) + protected WebView codeView; + private Repository repo; private String commit; @@ -105,36 +110,25 @@ public static Intent createIntent(Repository repository, String commit, GitHubFi private GitBlob blob; - private ProgressBar loadingBar; - - private WebView codeView; - private SourceEditor editor; private MenuItem markdownItem; @Inject - private AvatarLoader avatars; + protected AvatarLoader avatars; @Inject - private HttpImageGetter imageGetter; + protected HttpImageGetter imageGetter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_commit_file_view); - - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); - repo = getIntent().getParcelableExtra(EXTRA_REPOSITORY); commit = getStringExtra(EXTRA_HEAD); sha = getStringExtra(EXTRA_BASE); path = getStringExtra(EXTRA_PATH); - loadingBar = (ProgressBar) findViewById(R.id.pb_loading); - codeView = (WebView) findViewById(R.id.wv_code); - file = CommitUtils.getName(path); isMarkdownFile = MarkdownUtils.isMarkdown(file); @@ -156,6 +150,11 @@ protected void onCreate(Bundle savedInstanceState) { loadContent(); } + @Override + protected int getContentView() { + return R.layout.activity_commit_file_view; + } + @Override public boolean onCreateOptionsMenu(final Menu optionsMenu) { getMenuInflater().inflate(R.menu.activity_file_view, optionsMenu); @@ -223,36 +222,6 @@ public boolean onOptionsItemSelected(MenuItem item) { } } - @Override - public Loader onCreateLoader(int loader, Bundle args) { - final String raw = args.getString(ARG_TEXT); - final Repository repo = args.getParcelable(ARG_REPO); - return new MarkdownLoader(this, repo, raw, imageGetter, false); - } - - @Override - public void onLoadFinished(Loader loader, - CharSequence rendered) { - if (rendered == null) { - ToastUtils.show(this, R.string.error_rendering_markdown); - } - - loadingBar.setVisibility(View.GONE); - codeView.setVisibility(View.VISIBLE); - - if (!TextUtils.isEmpty(rendered)) { - renderedMarkdown = rendered.toString(); - if (markdownItem != null) { - markdownItem.setEnabled(true); - } - editor.setMarkdown(true).setSource(file, renderedMarkdown, false); - } - } - - @Override - public void onLoaderReset(Loader loader) { - } - private void shareFile() { String id = InfoUtils.createRepoId(repo); startActivity(ShareUtils.create( @@ -265,10 +234,20 @@ private void loadMarkdown() { codeView.setVisibility(View.GONE); String markdown = new String(Base64.decode(blob.content(), Base64.DEFAULT)); - Bundle args = new Bundle(); - args.putCharSequence(ARG_TEXT, markdown); - args.putParcelable(ARG_REPO, repo); - getSupportLoaderManager().restartLoader(0, args, this); + + MarkdownLoader.load(this, markdown, repo, imageGetter, false) + .subscribe(rendered -> { + loadingBar.setVisibility(View.GONE); + codeView.setVisibility(View.VISIBLE); + + if (!TextUtils.isEmpty(rendered)) { + renderedMarkdown = rendered.toString(); + if (markdownItem != null) { + markdownItem.setEnabled(true); + } + editor.setMarkdown(true).setSource(file, renderedMarkdown, false); + } + } , e -> ToastUtils.show(this, R.string.error_rendering_markdown)); } private void loadContent() { @@ -276,7 +255,7 @@ private void loadContent() { .getGitBlob(repo.owner().login(), repo.name(), sha) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .compose(this.bindToLifecycle()) + .as(AutoDisposeUtils.bindToLifecycle(this)) .subscribe(response -> { GitBlob gitBlob = response.body(); loadingBar.setVisibility(View.GONE); diff --git a/app/src/main/java/com/github/pockethub/android/ui/commit/CommitListAdapter.java b/app/src/main/java/com/github/pockethub/android/ui/commit/CommitListAdapter.java deleted file mode 100644 index 7f3ed47cd..000000000 --- a/app/src/main/java/com/github/pockethub/android/ui/commit/CommitListAdapter.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2015 PocketHub - * - * 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.pockethub.android.ui.commit; - -import android.text.TextUtils; -import android.view.LayoutInflater; - -import com.github.kevinsawicki.wishlist.SingleTypeAdapter; -import com.github.pockethub.android.R; -import com.github.pockethub.android.core.commit.CommitUtils; -import com.github.pockethub.android.ui.StyledText; -import com.github.pockethub.android.util.AvatarLoader; -import com.meisolsson.githubsdk.model.Commit; - -import java.util.Collection; - -/** - * Adapter to display commits - */ -public class CommitListAdapter extends SingleTypeAdapter { - - private final AvatarLoader avatars; - - /** - * @param viewId - * @param inflater - * @param elements - * @param avatars - */ - public CommitListAdapter(int viewId, LayoutInflater inflater, - Collection elements, AvatarLoader avatars) { - super(inflater, viewId); - - this.avatars = avatars; - setItems(elements); - } - - @Override - public long getItemId(int position) { - String sha = getItem(position).sha(); - if (!TextUtils.isEmpty(sha)) { - return sha.hashCode(); - } else { - return super.getItemId(position); - } - } - - @Override - protected int[] getChildViewIds() { - return new int[] { R.id.tv_commit_id, R.id.tv_commit_author, R.id.iv_avatar, - R.id.tv_commit_message, R.id.tv_commit_comments }; - } - - @Override - protected void update(int position, Commit item) { - setText(0, CommitUtils.abbreviate(item.sha())); - - StyledText authorText = new StyledText(); - authorText.bold(CommitUtils.getAuthor(item)); - authorText.append(' '); - authorText.append(CommitUtils.getAuthorDate(item)); - setText(1, authorText); - - CommitUtils.bindAuthor(item, avatars, imageView(2)); - setText(3, item.commit().message()); - setText(4, CommitUtils.getCommentCount(item)); - } -} diff --git a/app/src/main/java/com/github/pockethub/android/ui/commit/CommitListFragment.java b/app/src/main/java/com/github/pockethub/android/ui/commit/CommitListFragment.java index 8becf6424..d08f806d5 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/commit/CommitListFragment.java +++ b/app/src/main/java/com/github/pockethub/android/ui/commit/CommitListFragment.java @@ -19,23 +19,19 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; -import android.support.v4.content.Loader; +import android.support.annotation.NonNull; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ListView; import android.widget.TextView; +import com.github.pockethub.android.ui.item.commit.CommitItem; import com.meisolsson.githubsdk.core.ServiceGenerator; import com.meisolsson.githubsdk.model.Commit; +import com.meisolsson.githubsdk.model.Page; import com.meisolsson.githubsdk.model.Repository; -import com.github.kevinsawicki.wishlist.SingleTypeAdapter; import com.github.pockethub.android.R; -import com.github.pockethub.android.ThrowableLoader; -import com.github.pockethub.android.core.PageIterator; -import com.github.pockethub.android.core.ResourcePager; -import com.github.pockethub.android.core.commit.CommitPager; import com.github.pockethub.android.core.commit.CommitStore; import com.github.pockethub.android.core.ref.RefUtils; import com.github.pockethub.android.ui.BaseActivity; @@ -48,20 +44,29 @@ import com.meisolsson.githubsdk.model.git.GitReference; import com.meisolsson.githubsdk.service.repositories.RepositoryCommitService; import com.meisolsson.githubsdk.service.repositories.RepositoryService; -import com.google.inject.Inject; +import com.xwray.groupie.Item; + +import javax.inject.Inject; import java.util.List; +import butterknife.BindView; +import butterknife.OnClick; +import io.reactivex.Single; +import retrofit2.Response; + import static android.app.Activity.RESULT_OK; import static com.github.pockethub.android.Intents.EXTRA_REPOSITORY; import static com.github.pockethub.android.RequestCodes.COMMIT_VIEW; import static com.github.pockethub.android.RequestCodes.REF_UPDATE; /** - * Fragment to display a list of repository commits + * Fragment to display a list of repo commits */ -public class CommitListFragment extends PagedItemFragment - implements DialogResultListener { +public class CommitListFragment extends PagedItemFragment implements DialogResultListener { + + @Inject + protected RepositoryCommitService service; /** * Avatar loader @@ -70,17 +75,20 @@ public class CommitListFragment extends PagedItemFragment protected AvatarLoader avatars; @Inject - private CommitStore store; + protected CommitStore store; - private Repository repository; + private Repository repo; private RefDialog dialog; - private TextView branchIconView; + @BindView(R.id.tv_branch_icon) + protected TextView branchIconView; - private TextView branchView; + @BindView(R.id.tv_branch) + protected TextView branchView; - private View branchFooterView; + @BindView(R.id.rl_branch) + protected View branchFooterView; private String ref; @@ -89,7 +97,7 @@ public void onAttach(Context context) { super.onAttach(context); Activity activity = (Activity) context; - repository = activity.getIntent().getParcelableExtra(EXTRA_REPOSITORY); + repo = activity.getIntent().getParcelableExtra(EXTRA_REPOSITORY); } @Override @@ -100,109 +108,68 @@ public void onActivityCreated(Bundle savedInstanceState) { } @Override - public Loader> onCreateLoader(int id, Bundle bundle) { - final ThrowableLoader> parentLoader = (ThrowableLoader>) super - .onCreateLoader(id, bundle); - return new ThrowableLoader>(getActivity(), items) { - - @Override - public List loadData() throws Exception { - if (TextUtils.isEmpty(ref)) { - String defaultBranch = repository.defaultBranch(); - if (TextUtils.isEmpty(defaultBranch)) { - defaultBranch = ServiceGenerator.createService(getActivity(), RepositoryService.class) - .getRepository(repository.owner().login(), repository.name()) - .blockingGet() - .body() - .defaultBranch(); - - if (TextUtils.isEmpty(defaultBranch)) { - defaultBranch = "master"; - } - } - ref = defaultBranch; - } - - return parentLoader.loadData(); + protected Single>> loadData(int page) { + Single refSingle; + if (TextUtils.isEmpty(ref)) { + String defaultBranch = repo.defaultBranch(); + if (TextUtils.isEmpty(defaultBranch)) { + refSingle = ServiceGenerator.createService(getActivity(), RepositoryService.class) + .getRepository(repo.owner().login(), repo.name()) + .map(response -> { + String branch = response.body().defaultBranch(); + if (TextUtils.isEmpty(branch)) { + return "master"; + } + return branch; + }); + } else { + refSingle = Single.just(defaultBranch); } - }; + } else { + refSingle = Single.just(ref); + } + + return refSingle + .map(ref -> { + CommitListFragment.this.ref = ref; + return ref; + }) + .flatMap(branch -> + service.getCommits(repo.owner().login(), repo.name(), branch, page)); } @Override - public void onLoadFinished(Loader> loader, List items) { - super.onLoadFinished(loader, items); + protected Item createItem(Commit dataItem) { + return new CommitItem(avatars, dataItem); + } + @Override + protected void onDataLoaded(List items) { + super.onDataLoaded(items); if (ref != null) { updateRefLabel(); } } - @Override - protected ResourcePager createPager() { - return new CommitPager(repository, store) { - - private String last; - - @Override - protected Commit register(Commit resource) { - // Store first parent of last commit registered for next page - // lookup - List parents = resource.parents(); - if (parents != null && !parents.isEmpty()) { - last = parents.get(0).sha(); - } else { - last = null; - } - - return super.register(resource); - } - - @Override - public PageIterator createIterator(int page, int size) { - - return new PageIterator<>(page1 -> { - RepositoryCommitService service = ServiceGenerator.createService(getActivity(), - RepositoryCommitService.class); - - if (page1 > 1 || ref == null) { - return service.getCommits(repository.owner().login(), repository.name(), last, page1); - } else { - return service.getCommits(repository.owner().login(), repository.name(), ref, page1); - } - }, page); - } - - @Override - public ResourcePager clear() { - last = null; - return super.clear(); - } - }; - } - @Override protected int getLoadingMessage() { return R.string.loading_commits; } @Override - protected int getErrorMessage(Exception exception) { + protected int getErrorMessage() { return R.string.error_commits_load; } @Override - protected SingleTypeAdapter createAdapter( - List items) { - return new CommitListAdapter(R.layout.commit_item, getActivity() - .getLayoutInflater(), items, avatars); - } - - @Override - public void onListItemClick(ListView l, View v, int position, long id) { - Object item = l.getItemAtPosition(position); - if (item instanceof Commit) { - startActivityForResult(CommitViewActivity.createIntent(repository, - position, items), COMMIT_VIEW); + public void onItemClick(@NonNull Item item, @NonNull View view) { + super.onItemClick(item, view); + if (item instanceof CommitItem) { + int position = getListAdapter().getAdapterPosition(item); + startActivityForResult( + CommitViewActivity.createIntent(repo, position, items), + COMMIT_VIEW + ); } } @@ -244,14 +211,15 @@ private void setRef(final GitReference ref) { refreshWithProgress(); } - private void switchRefs() { + @OnClick(R.id.rl_branch) + protected void switchRefs() { if (ref == null) { return; } if (dialog == null) { dialog = new RefDialog((BaseActivity) getActivity(), - REF_UPDATE, repository); + REF_UPDATE, repo); } GitReference reference = GitReference.builder() .ref(ref) @@ -260,22 +228,11 @@ private void switchRefs() { } @Override - public ItemListFragment setListShown(boolean shown, - boolean animate) { + public ItemListFragment setListShown(boolean shown, boolean animate) { branchFooterView.setVisibility(shown ? View.VISIBLE : View.GONE); return super.setListShown(shown, animate); } - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - branchFooterView = view.findViewById(R.id.rl_branch); - branchView = (TextView) view.findViewById(R.id.tv_branch); - branchIconView = (TextView) view.findViewById(R.id.tv_branch_icon); - branchFooterView.setOnClickListener(v -> switchRefs()); - } - @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { diff --git a/app/src/main/java/com/github/pockethub/android/ui/commit/CommitViewActivity.java b/app/src/main/java/com/github/pockethub/android/ui/commit/CommitViewActivity.java index 441080baf..7087c6bc7 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/commit/CommitViewActivity.java +++ b/app/src/main/java/com/github/pockethub/android/ui/commit/CommitViewActivity.java @@ -18,9 +18,9 @@ import android.content.Intent; import android.os.Bundle; import android.support.v7.app.ActionBar; -import android.support.v7.widget.Toolbar; import android.view.MenuItem; +import com.github.pockethub.android.ui.item.commit.CommitItem; import com.meisolsson.githubsdk.model.Commit; import com.meisolsson.githubsdk.model.Repository; import com.github.pockethub.android.Intents.Builder; @@ -32,10 +32,14 @@ import com.github.pockethub.android.ui.repo.RepositoryViewActivity; import com.github.pockethub.android.util.AvatarLoader; import com.github.pockethub.android.util.InfoUtils; -import com.google.inject.Inject; +import com.xwray.groupie.Item; + +import javax.inject.Inject; import java.util.Collection; +import butterknife.BindView; + import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP; import static com.github.pockethub.android.Intents.EXTRA_BASES; @@ -68,10 +72,11 @@ public static Intent createIntent(final Repository repository, * @return intent */ public static Intent createIntent(final Repository repository, - final int position, final Collection commits) { + final int position, final Collection commits) { String[] ids = new String[commits.size()]; int index = 0; - for (Commit commit : commits) { + for (Item item : commits) { + Commit commit = ((CommitItem) item).getData(); ids[index++] = commit.sha(); } return createIntent(repository, position, ids); @@ -94,7 +99,8 @@ public static Intent createIntent(final Repository repository, return builder.toIntent(); } - private ViewPager pager; + @BindView(R.id.vp_pages) + protected ViewPager pager; private Repository repository; @@ -103,7 +109,7 @@ public static Intent createIntent(final Repository repository, private int initialPosition; @Inject - private AvatarLoader avatars; + protected AvatarLoader avatars; private CommitPagerAdapter adapter; @@ -111,19 +117,13 @@ public static Intent createIntent(final Repository repository, protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_pager); - - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); - - pager = (ViewPager) findViewById(R.id.vp_pages); - repository = getIntent().getParcelableExtra(EXTRA_REPOSITORY); ids = getCharSequenceArrayExtra(EXTRA_BASES); initialPosition = getIntExtra(EXTRA_POSITION); adapter = new CommitPagerAdapter(this, repository, ids); pager.setAdapter(adapter); - pager.setOnPageChangeListener(this); + pager.addOnPageChangeListener(this); pager.scheduleSetItem(initialPosition, this); onPageSelected(initialPosition); @@ -133,6 +133,17 @@ protected void onCreate(Bundle savedInstanceState) { avatars.bind(actionBar, repository.owner()); } + @Override + protected void onDestroy() { + super.onDestroy(); + pager.removeOnPageChangeListener(this); + } + + @Override + protected int getContentView() { + return R.layout.activity_pager; + } + @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { diff --git a/app/src/main/java/com/github/pockethub/android/ui/commit/CreateCommentActivity.java b/app/src/main/java/com/github/pockethub/android/ui/commit/CreateCommentActivity.java index 317a5138a..bfb2a2204 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/commit/CreateCommentActivity.java +++ b/app/src/main/java/com/github/pockethub/android/ui/commit/CreateCommentActivity.java @@ -20,6 +20,7 @@ import android.support.v7.app.ActionBar; import android.text.TextUtils; +import com.github.pockethub.android.rx.AutoDisposeUtils; import com.github.pockethub.android.rx.RxProgress; import com.meisolsson.githubsdk.core.ServiceGenerator; import com.meisolsson.githubsdk.model.Repository; @@ -119,8 +120,8 @@ protected void createComment(final String comment) { .createCommitComment(repository.owner().login(), repository.name(), commit, commitCommentBuilder.build()) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .compose(this.bindToLifecycle()) .compose(RxProgress.bindToLifecycle(this, R.string.creating_comment)) + .as(AutoDisposeUtils.bindToLifecycle(this)) .subscribe(response -> finish(response.body()), e -> ToastUtils.show(this, e.getMessage())); } diff --git a/app/src/main/java/com/github/pockethub/android/ui/gist/CreateCommentActivity.java b/app/src/main/java/com/github/pockethub/android/ui/gist/CreateCommentActivity.java index 843b06d1b..5a524dea2 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/gist/CreateCommentActivity.java +++ b/app/src/main/java/com/github/pockethub/android/ui/gist/CreateCommentActivity.java @@ -20,6 +20,7 @@ import android.support.v7.app.ActionBar; import android.util.Log; +import com.github.pockethub.android.rx.AutoDisposeUtils; import com.meisolsson.githubsdk.core.ServiceGenerator; import com.meisolsson.githubsdk.model.Gist; import com.meisolsson.githubsdk.model.User; @@ -81,7 +82,7 @@ protected void createComment(final String comment) { .createGistComment(gist.id(), commentRequest) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .compose(this.bindToLifecycle()) + .as(AutoDisposeUtils.bindToLifecycle(this)) .subscribe(response -> finish(response.body()), error -> { Log.e(TAG, "Exception creating comment on gist", error); diff --git a/app/src/main/java/com/github/pockethub/android/ui/gist/CreateGistActivity.java b/app/src/main/java/com/github/pockethub/android/ui/gist/CreateGistActivity.java index 197aa50b1..751aab8a1 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/gist/CreateGistActivity.java +++ b/app/src/main/java/com/github/pockethub/android/ui/gist/CreateGistActivity.java @@ -30,6 +30,7 @@ import android.widget.EditText; import com.github.pockethub.android.R; +import com.github.pockethub.android.rx.AutoDisposeUtils; import com.github.pockethub.android.rx.RxProgress; import com.github.pockethub.android.ui.BaseActivity; import com.github.pockethub.android.ui.TextWatcherAdapter; @@ -43,6 +44,11 @@ import java.util.HashMap; import java.util.Map; +import butterknife.BindView; +import butterknife.BindViews; +import butterknife.OnCheckedChanged; +import butterknife.OnFocusChange; +import butterknife.OnTextChanged; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; @@ -53,64 +59,26 @@ public class CreateGistActivity extends BaseActivity { private static final String TAG = "CreateGistActivity"; - private EditText descriptionText; + @BindView(R.id.et_gist_description) + protected EditText descriptionText; - private EditText nameText; + @BindView(R.id.et_gist_name) + protected EditText nameText; - private EditText contentText; + @BindView(R.id.et_gist_content) + protected EditText contentText; - private CheckBox publicCheckBox; + @BindView(R.id.cb_public) + protected CheckBox publicCheckBox; + + @BindView(R.id.appbar) + protected AppBarLayout appBarLayout; private MenuItem menuItem; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - setContentView(R.layout.activity_gist_create); - - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); - - descriptionText = (EditText) findViewById(R.id.et_gist_description); - nameText = (EditText) findViewById(R.id.et_gist_name); - contentText = (EditText) findViewById(R.id.et_gist_content); - publicCheckBox = (CheckBox) findViewById(R.id.cb_public); - - final AppBarLayout appBarLayout = (AppBarLayout) findViewById(R.id.appbar); - - // Fully expand the AppBar if something in it gets focus - View.OnFocusChangeListener expandAppBarOnFocusChangeListener = (v, hasFocus) -> { - if (hasFocus) { - appBarLayout.setExpanded(true); - } - }; - nameText.setOnFocusChangeListener(expandAppBarOnFocusChangeListener); - descriptionText.setOnFocusChangeListener(expandAppBarOnFocusChangeListener); - publicCheckBox.setOnFocusChangeListener(expandAppBarOnFocusChangeListener); - - // Fully expand the AppBar if something in it changes its value - TextWatcher expandAppBarTextWatcher = new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - appBarLayout.setExpanded(true); - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - appBarLayout.setExpanded(true); - } - - @Override - public void afterTextChanged(Editable s) { - appBarLayout.setExpanded(true); - } - }; - nameText.addTextChangedListener(expandAppBarTextWatcher); - descriptionText.addTextChangedListener(expandAppBarTextWatcher); - publicCheckBox.addTextChangedListener(expandAppBarTextWatcher); - publicCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> - appBarLayout.setExpanded(true)); - ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); @@ -124,16 +92,14 @@ public void afterTextChanged(Editable s) { descriptionText.setText(subject); } - contentText.addTextChangedListener(new TextWatcherAdapter() { - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - updateCreateMenu(s); - } - }); updateCreateMenu(); } + @Override + protected int getContentView() { + return R.layout.activity_gist_create; + } + @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); @@ -157,12 +123,29 @@ public boolean onOptionsItemSelected(MenuItem item) { } } - private void updateCreateMenu() { - if (contentText != null) { - updateCreateMenu(contentText.getText()); + // Fully expand the AppBar if something in it gets focus + @OnFocusChange({R.id.et_gist_description, R.id.et_gist_name, R.id.cb_public}) + protected void expandAppBarOnFocusChangeListener(View v, boolean hasFocus) { + if (hasFocus) { + appBarLayout.setExpanded(true); } } + @OnCheckedChanged(R.id.cb_public) + @OnTextChanged(value = {R.id.et_gist_description, R.id.et_gist_name, R.id.cb_public}) + protected void expandAppBarOnChange() { + appBarLayout.setExpanded(true); + } + + @OnTextChanged(R.id.et_gist_content) + protected void onContentTextChange() { + updateCreateMenu(); + } + + private void updateCreateMenu() { + updateCreateMenu(contentText.getText()); + } + private void updateCreateMenu(CharSequence text) { if (menuItem != null) { menuItem.setEnabled(!TextUtils.isEmpty(text)); @@ -174,11 +157,11 @@ private void createGist() { String enteredDescription = descriptionText.getText().toString().trim(); final String description = enteredDescription.length() > 0 ? enteredDescription - : getString(R.string.gist_description_hint); + : getString(R.string.gist_description_hint); String enteredName = nameText.getText().toString().trim(); final String name = enteredName.length() > 0 ? enteredName - : getString(R.string.gist_file_name_hint); + : getString(R.string.gist_file_name_hint); final String content = contentText.getText().toString(); Map map = new HashMap<>(); @@ -194,8 +177,8 @@ private void createGist() { .createGist(createGist) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .compose(this.bindToLifecycle()) .compose(RxProgress.bindToLifecycle(this, R.string.creating_gist)) + .as(AutoDisposeUtils.bindToLifecycle(this)) .subscribe(response -> { startActivity(GistsViewActivity.createIntent(response.body())); setResult(RESULT_OK); diff --git a/app/src/main/java/com/github/pockethub/android/ui/gist/EditCommentActivity.java b/app/src/main/java/com/github/pockethub/android/ui/gist/EditCommentActivity.java index 86f868f29..495be1d25 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/gist/EditCommentActivity.java +++ b/app/src/main/java/com/github/pockethub/android/ui/gist/EditCommentActivity.java @@ -20,6 +20,7 @@ import android.support.v7.app.ActionBar; import android.util.Log; +import com.github.pockethub.android.rx.AutoDisposeUtils; import com.github.pockethub.android.rx.RxProgress; import com.meisolsson.githubsdk.core.ServiceGenerator; import com.meisolsson.githubsdk.model.Gist; @@ -99,8 +100,8 @@ protected void editComment(CommentRequest commentRequest) { .editGistComment(gist.id(), comment.id(), commentRequest) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .compose(this.bindToLifecycle()) .compose(RxProgress.bindToLifecycle(this, R.string.editing_comment)) + .as(AutoDisposeUtils.bindToLifecycle(this)) .subscribe(response -> finish(response.body()), e -> { Log.d(TAG, "Exception editing comment on gist", e); ToastUtils.show(this, e.getMessage()); diff --git a/app/src/main/java/com/github/pockethub/android/ui/gist/GistFileFragment.java b/app/src/main/java/com/github/pockethub/android/ui/gist/GistFileFragment.java index 7f2ba2584..fef555451 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/gist/GistFileFragment.java +++ b/app/src/main/java/com/github/pockethub/android/ui/gist/GistFileFragment.java @@ -20,6 +20,7 @@ import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.os.Bundle; +import android.support.annotation.NonNull; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -36,11 +37,12 @@ import com.github.pockethub.android.util.ToastUtils; import com.meisolsson.githubsdk.model.Gist; import com.meisolsson.githubsdk.model.GistFile; -import com.google.inject.Inject; +import javax.inject.Inject; import java.io.IOException; import java.util.Map; +import butterknife.BindView; import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; @@ -55,7 +57,8 @@ public class GistFileFragment extends DialogFragment implements OnSharedPreferenceChangeListener { - private WebView webView; + @BindView(R.id.wv_code) + protected WebView webView; private String gistId; @@ -64,7 +67,7 @@ public class GistFileFragment extends DialogFragment implements private Gist gist; @Inject - private GistStore store; + protected GistStore store; private SourceEditor editor; @@ -180,11 +183,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, } @Override - public void onViewCreated(View view, Bundle savedInstanceState) { + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - - webView = (WebView) view.findViewById(R.id.wv_code); - editor = new SourceEditor(webView); editor.setWrap(PreferenceUtils.getCodePreferences(getActivity()) .getBoolean(WRAP, false)); diff --git a/app/src/main/java/com/github/pockethub/android/ui/gist/GistFilesViewActivity.java b/app/src/main/java/com/github/pockethub/android/ui/gist/GistFilesViewActivity.java index c2b6c3941..b443218a9 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/gist/GistFilesViewActivity.java +++ b/app/src/main/java/com/github/pockethub/android/ui/gist/GistFilesViewActivity.java @@ -24,6 +24,8 @@ import android.view.View; import android.widget.ProgressBar; +import com.github.pockethub.android.core.gist.RefreshGistTaskFactory; +import com.github.pockethub.android.rx.AutoDisposeUtils; import com.meisolsson.githubsdk.model.Gist; import com.meisolsson.githubsdk.model.User; import com.github.pockethub.android.Intents.Builder; @@ -35,9 +37,9 @@ import com.github.pockethub.android.ui.PagerActivity; import com.github.pockethub.android.ui.ViewPager; import com.github.pockethub.android.util.AvatarLoader; -import com.github.pockethub.android.util.HttpImageGetter; -import com.google.inject.Inject; +import javax.inject.Inject; +import butterknife.BindView; import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; @@ -68,22 +70,25 @@ public static Intent createIntent(Gist gist, int position) { private int initialPosition; - private ViewPager pager; + @BindView(R.id.vp_pages) + protected ViewPager pager; - private ProgressBar loadingBar; + @BindView(R.id.pb_loading) + protected ProgressBar loadingBar; - private TabLayout tabs; + @BindView(R.id.sliding_tabs_layout) + protected TabLayout tabs; private Gist gist; @Inject - private GistStore store; + protected GistStore store; @Inject - private AvatarLoader avatars; + protected AvatarLoader avatars; @Inject - private HttpImageGetter imageGetter; + protected RefreshGistTaskFactory refreshGistTaskFactory; private GistFilesPagerAdapter adapter; @@ -94,14 +99,6 @@ protected void onCreate(Bundle savedInstanceState) { gistId = getStringExtra(EXTRA_GIST_ID); initialPosition = getIntExtra(EXTRA_POSITION); - setContentView(R.layout.activity_pager_with_title); - - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); - - pager = (ViewPager) findViewById(R.id.vp_pages); - loadingBar = (ProgressBar) findViewById(R.id.pb_loading); - tabs = (TabLayout) findViewById(R.id.sliding_tabs_layout); - if (initialPosition < 0) { initialPosition = 0; } @@ -115,11 +112,11 @@ protected void onCreate(Bundle savedInstanceState) { loadingBar.setVisibility(View.VISIBLE); pager.setVisibility(View.GONE); tabs.setVisibility(View.GONE); - new RefreshGistTask(this, gistId, imageGetter) + refreshGistTaskFactory.create(this, gistId) .refresh() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .compose(this.bindToLifecycle()) + .as(AutoDisposeUtils.bindToLifecycle(this)) .subscribe(gist -> { this.gist = gist.getGist(); configurePager(); @@ -127,6 +124,11 @@ protected void onCreate(Bundle savedInstanceState) { } } + @Override + protected int getContentView() { + return R.layout.activity_pager_with_title; + } + private void configurePager() { ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); diff --git a/app/src/main/java/com/github/pockethub/android/ui/gist/GistFragment.java b/app/src/main/java/com/github/pockethub/android/ui/gist/GistFragment.java index 833f51fdb..6baa53e90 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/gist/GistFragment.java +++ b/app/src/main/java/com/github/pockethub/android/ui/gist/GistFragment.java @@ -18,7 +18,11 @@ import android.app.Activity; import android.content.Intent; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v4.app.FragmentActivity; +import android.support.v7.widget.DividerItemDecoration; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; @@ -27,13 +31,15 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.ListView; import android.widget.ProgressBar; -import android.widget.TextView; +import com.github.pockethub.android.core.gist.RefreshGistTaskFactory; +import com.github.pockethub.android.rx.AutoDisposeUtils; import com.github.pockethub.android.rx.RxProgress; +import com.github.pockethub.android.ui.item.LoadingItem; +import com.github.pockethub.android.ui.item.gist.GistFileItem; +import com.github.pockethub.android.ui.item.GitHubCommentItem; +import com.github.pockethub.android.ui.item.gist.GistHeaderItem; import com.meisolsson.githubsdk.core.ServiceGenerator; import com.meisolsson.githubsdk.model.Gist; import com.meisolsson.githubsdk.model.GistFile; @@ -43,12 +49,8 @@ import com.github.pockethub.android.accounts.AccountUtils; import com.github.pockethub.android.core.OnLoadListener; import com.github.pockethub.android.core.gist.GistStore; -import com.github.pockethub.android.core.gist.RefreshGistTask; import com.github.pockethub.android.ui.ConfirmDialogFragment; import com.github.pockethub.android.ui.DialogFragment; -import com.github.pockethub.android.ui.HeaderFooterListAdapter; -import com.github.pockethub.android.ui.StyledText; -import com.github.pockethub.android.ui.comment.CommentListAdapter; import com.github.pockethub.android.ui.comment.DeleteCommentListener; import com.github.pockethub.android.ui.comment.EditCommentListener; import com.github.pockethub.android.util.AvatarLoader; @@ -57,14 +59,19 @@ import com.github.pockethub.android.util.ToastUtils; import com.meisolsson.githubsdk.service.gists.GistCommentService; import com.meisolsson.githubsdk.service.gists.GistService; -import com.google.inject.Inject; +import com.xwray.groupie.GroupAdapter; +import com.xwray.groupie.Item; +import com.xwray.groupie.OnItemClickListener; +import com.xwray.groupie.Section; + +import javax.inject.Inject; import java.util.ArrayList; import java.util.Collections; -import java.util.Date; import java.util.List; import java.util.Map; +import butterknife.BindView; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; @@ -90,38 +97,35 @@ public class GistFragment extends DialogFragment implements OnItemClickListener private Gist gist; - private ListView list; + @BindView(android.R.id.list) + protected RecyclerView list; - private ProgressBar progress; + @BindView(R.id.pb_loading) + protected ProgressBar progress; @Inject - private GistStore store; + protected GistStore store; @Inject - private HttpImageGetter imageGetter; - - private View headerView; + protected RefreshGistTaskFactory refreshGistTaskFactory; - private View footerView; - - private TextView created; + @Inject + protected HttpImageGetter imageGetter; - private TextView updated; + private GroupAdapter adapter = new GroupAdapter(); - private TextView description; + private Section mainSection = new Section(); - private View loadingView; + private Section filesSection = new Section(); - private HeaderFooterListAdapter adapter; + private Section commentsSection = new Section(); private boolean starred; private boolean loadFinished; @Inject - private AvatarLoader avatars; - - private List fileHeaders = new ArrayList<>(); + protected AvatarLoader avatars; @Override public void onCreate(Bundle savedInstanceState) { @@ -133,50 +137,29 @@ public void onCreate(Bundle savedInstanceState) { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View root = inflater.inflate(R.layout.fragment_comment_list, container, false); - - headerView = inflater.inflate(R.layout.gist_header, null); - created = (TextView) headerView.findViewById(R.id.tv_gist_creation); - updated = (TextView) headerView.findViewById(R.id.tv_gist_updated); - description = (TextView) headerView - .findViewById(R.id.tv_gist_description); - - loadingView = inflater.inflate(R.layout.loading_item, null); - ((TextView) loadingView.findViewById(R.id.tv_loading)) - .setText(R.string.loading_comments); - - footerView = inflater.inflate(R.layout.footer_separator, null); - - return root; + return inflater.inflate(R.layout.fragment_comment_list, container, false); } @Override - public void onViewCreated(View view, Bundle savedInstanceState) { + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + DividerItemDecoration itemDecoration = + new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL); + itemDecoration.setDrawable(getResources().getDrawable(R.drawable.list_divider_5dp)); - list = (ListView) view.findViewById(android.R.id.list); - progress = (ProgressBar) view.findViewById(R.id.pb_loading); - - Activity activity = getActivity(); - User user = gist.owner(); - String userName = null; - if(user != null) { - userName = user.login(); - } - - adapter = new HeaderFooterListAdapter<>(list, - new CommentListAdapter(activity.getLayoutInflater(), null, avatars, - imageGetter, editCommentListener, deleteCommentListener, userName, isOwner(), null)); + list.setLayoutManager(new LinearLayoutManager(getActivity())); + list.addItemDecoration(itemDecoration); list.setAdapter(adapter); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); + mainSection.add(filesSection); + mainSection.add(commentsSection); + adapter.add(mainSection); - list.setOnItemClickListener(this); - adapter.addHeader(headerView); - adapter.addFooter(footerView); + adapter.setOnItemClickListener(this); if (gist != null) { updateHeader(gist); @@ -184,7 +167,7 @@ public void onActivityCreated(Bundle savedInstanceState) { } if (gist == null || (gist.comments() > 0 && comments == null)) { - adapter.addHeader(loadingView, null, false); + mainSection.setFooter(new LoadingItem(R.string.loading_comments)); } if (gist != null && comments != null) { @@ -207,35 +190,7 @@ private boolean isOwner() { } private void updateHeader(Gist gist) { - Date createdAt = gist.createdAt(); - if (createdAt != null) { - StyledText text = new StyledText(); - text.append(getString(R.string.prefix_created)); - text.append(createdAt); - created.setText(text); - created.setVisibility(VISIBLE); - } else { - created.setVisibility(GONE); - } - - Date updatedAt = gist.updatedAt(); - if (updatedAt != null && !updatedAt.equals(createdAt)) { - StyledText text = new StyledText(); - text.append(getString(R.string.prefix_updated)); - text.append(updatedAt); - updated.setText(text); - updated.setVisibility(VISIBLE); - } else { - updated.setVisibility(GONE); - } - - String desc = gist.description(); - if (!TextUtils.isEmpty(desc)) { - description.setText(desc); - } else { - description.setText(R.string.no_description_given); - } - + mainSection.setHeader(new GistHeaderItem(getActivity(), gist)); progress.setVisibility(GONE); list.setVisibility(VISIBLE); } @@ -297,7 +252,7 @@ private void starGist() { .starGist(gistId) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .compose(this.bindToLifecycle()) + .as(AutoDisposeUtils.bindToLifecycle(this)) .subscribe(response -> starred = response.code() == 204, e -> ToastUtils.show((Activity) getContext(), e.getMessage())); } @@ -320,7 +275,7 @@ private void unstarGist() { .unstarGist(gistId) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .compose(this.bindToLifecycle()) + .as(AutoDisposeUtils.bindToLifecycle(this)) .subscribe(response -> starred = !(response.code() == 204), e -> ToastUtils.show((Activity) getContext(), e.getMessage())); } @@ -366,48 +321,48 @@ private void updateFiles(Gist gist) { return; } - for (View header : fileHeaders) { - adapter.removeHeader(header); - } - fileHeaders.clear(); - Map files = gist.files(); if (files == null || files.isEmpty()) { + filesSection.update(Collections.emptyList()); return; } - final LayoutInflater inflater = activity.getLayoutInflater(); + List fileItems = new ArrayList<>(); for (GistFile file : files.values()) { - View fileView = inflater.inflate(R.layout.gist_file_item, null); - ((TextView) fileView.findViewById(R.id.tv_file)).setText(file.filename()); - adapter.addHeader(fileView, file, true); - fileHeaders.add(fileView); + fileItems.add(new GistFileItem(file)); } + filesSection.update(fileItems); } private void updateList(Gist gist, List comments) { - adapter.getWrappedAdapter().setItems( - comments.toArray(new GitHubComment[comments.size()])); - adapter.removeHeader(loadingView); + List items = new ArrayList<>(); + String username = AccountUtils.getLogin(getActivity()); + boolean isOwner = isOwner(); + + for (GitHubComment comment : comments) { + items.add( + new GitHubCommentItem(avatars, imageGetter, editCommentListener, + deleteCommentListener, username, isOwner, comment) + ); + } + commentsSection.update(items); + mainSection.removeFooter(); - headerView.setVisibility(VISIBLE); updateHeader(gist); - updateFiles(gist); } private void refreshGist() { - new RefreshGistTask(getActivity(), gistId, imageGetter) + refreshGistTaskFactory.create(getActivity(), gistId) .refresh() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .filter(fullGist -> isUsable()) - .compose(this.bindToLifecycle()) + .as(AutoDisposeUtils.bindToLifecycle(this)) .subscribe(fullGist -> { FragmentActivity activity = getActivity(); if (activity instanceof OnLoadListener) { - ((OnLoadListener) activity) - .loaded(fullGist.getGist()); + ((OnLoadListener) activity).loaded(fullGist.getGist()); } starred = fullGist.isStarred(); @@ -419,12 +374,10 @@ private void refreshGist() { } @Override - public void onItemClick(AdapterView parent, View view, int position, - long id) { - Object item = parent.getItemAtPosition(position); - if (item instanceof GistFile) { - startActivity(GistFilesViewActivity - .createIntent(gist, position - 1)); + public void onItemClick(@NonNull Item item, @NonNull View view) { + if (item instanceof GistFileItem) { + int position = adapter.getAdapterPosition(item); + startActivity(GistFilesViewActivity.createIntent(gist, position - 1)); } } @@ -441,8 +394,8 @@ public void onDialogResult(int requestCode, int resultCode, Bundle arguments) { .deleteGistComment(gistId, comment.id()) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .compose(this.bindToLifecycle()) .compose(RxProgress.bindToLifecycle(getActivity(), R.string.deleting_comment)) + .as(AutoDisposeUtils.bindToLifecycle(this)) .subscribe(response -> { // Update comment list if (comments != null) { diff --git a/app/src/main/java/com/github/pockethub/android/ui/gist/GistListAdapter.java b/app/src/main/java/com/github/pockethub/android/ui/gist/GistListAdapter.java deleted file mode 100644 index 40de00940..000000000 --- a/app/src/main/java/com/github/pockethub/android/ui/gist/GistListAdapter.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) 2015 PocketHub - * - * 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.pockethub.android.ui.gist; - -import android.app.Activity; -import android.text.TextUtils; -import android.view.View; - -import com.meisolsson.githubsdk.model.Gist; -import com.meisolsson.githubsdk.model.User; -import com.github.kevinsawicki.wishlist.SingleTypeAdapter; -import com.github.pockethub.android.R; -import com.github.pockethub.android.ui.StyledText; -import com.github.pockethub.android.util.AvatarLoader; - -import java.util.Collection; - -/** - * Adapter to display a list of {@link Gist} objects - */ -public class GistListAdapter extends SingleTypeAdapter { - - private final AvatarLoader avatars; - - private String anonymous; - - /** - * @param avatars - * @param activity - * @param elements - */ - public GistListAdapter(AvatarLoader avatars, Activity activity, - Collection elements) { - super(activity, R.layout.gist_item); - - this.avatars = avatars; - setItems(elements); - } - - @Override - public long getItemId(final int position) { - final String id = getItem(position).id(); - return !TextUtils.isEmpty(id) ? id.hashCode() : super - .getItemId(position); - } - - @Override - protected int[] getChildViewIds() { - return new int[] { R.id.tv_gist_id, R.id.tv_gist_title, R.id.tv_gist_author, - R.id.tv_gist_comments, R.id.tv_gist_files, R.id.iv_avatar }; - } - - @Override - protected View initialize(View view) { - view = super.initialize(view); - - anonymous = view.getResources().getString(R.string.anonymous); - return view; - } - - @Override - protected void update(int position, Gist gist) { - setText(0, gist.id()); - - String description = gist.description(); - if (!TextUtils.isEmpty(description)) { - setText(1, description); - } else { - setText(1, R.string.no_description_given); - } - - User user = gist.owner(); - avatars.bind(imageView(5), user); - - StyledText authorText = new StyledText(); - if (user != null) { - authorText.bold(user.login()); - } else { - authorText.bold(anonymous); - } - authorText.append(' '); - authorText.append(gist.createdAt()); - setText(2, authorText); - - setNumber(3, gist.comments()); - setNumber(4, gist.files().size()); - } -} diff --git a/app/src/main/java/com/github/pockethub/android/ui/gist/GistsFragment.java b/app/src/main/java/com/github/pockethub/android/ui/gist/GistsFragment.java index 14ea4c7f9..b5f44a7d4 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/gist/GistsFragment.java +++ b/app/src/main/java/com/github/pockethub/android/ui/gist/GistsFragment.java @@ -17,19 +17,19 @@ import android.content.Intent; import android.os.Bundle; +import android.support.annotation.NonNull; import android.view.MenuItem; import android.view.View; -import android.widget.ListView; -import com.github.kevinsawicki.wishlist.SingleTypeAdapter; import com.github.pockethub.android.R; import com.github.pockethub.android.core.gist.GistStore; import com.github.pockethub.android.ui.PagedItemFragment; +import com.github.pockethub.android.ui.item.gist.GistItem; import com.github.pockethub.android.util.AvatarLoader; import com.meisolsson.githubsdk.model.Gist; -import com.google.inject.Inject; +import com.xwray.groupie.Item; -import java.util.List; +import javax.inject.Inject; import static com.github.pockethub.android.RequestCodes.GIST_CREATE; import static com.github.pockethub.android.RequestCodes.GIST_VIEW; @@ -52,9 +52,9 @@ public abstract class GistsFragment extends PagedItemFragment { protected GistStore store; @Override - public void onListItemClick(ListView l, View v, int position, long id) { - startActivityForResult(GistsViewActivity.createIntent(items, position), - GIST_VIEW); + public void onItemClick(@NonNull Item item, @NonNull View view) { + int position = getListAdapter().getAdapterPosition(item); + startActivityForResult(GistsViewActivity.createIntent(items, position), GIST_VIEW); } @Override @@ -91,7 +91,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } @Override - protected int getErrorMessage(Exception exception) { + protected int getErrorMessage() { return R.string.error_gists_load; } @@ -101,7 +101,7 @@ protected int getLoadingMessage() { } @Override - protected SingleTypeAdapter createAdapter(List items) { - return new GistListAdapter(avatars, getActivity(), items); + protected Item createItem(Gist item) { + return new GistItem(avatars, item); } } diff --git a/app/src/main/java/com/github/pockethub/android/ui/gist/GistsPagerFragment.java b/app/src/main/java/com/github/pockethub/android/ui/gist/GistsPagerFragment.java index 7418df626..78105f32f 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/gist/GistsPagerFragment.java +++ b/app/src/main/java/com/github/pockethub/android/ui/gist/GistsPagerFragment.java @@ -18,6 +18,7 @@ import android.app.Activity; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; import android.view.Menu; @@ -27,6 +28,7 @@ import com.github.pockethub.android.R; import com.github.pockethub.android.core.gist.GistStore; +import com.github.pockethub.android.rx.AutoDisposeUtils; import com.github.pockethub.android.rx.RxProgress; import com.github.pockethub.android.ui.BaseActivity; import com.github.pockethub.android.ui.TabPagerFragment; @@ -35,7 +37,7 @@ import com.meisolsson.githubsdk.model.Gist; import com.meisolsson.githubsdk.model.Page; import com.meisolsson.githubsdk.service.gists.GistService; -import com.google.inject.Inject; +import javax.inject.Inject; import java.util.Random; @@ -52,11 +54,11 @@ public class GistsPagerFragment extends TabPagerFragment getActivity().startActivityForResult( GistsViewActivity.createIntent(gist), GIST_VIEW), e -> { Log.d(TAG, "Exception opening random Gist", e); diff --git a/app/src/main/java/com/github/pockethub/android/ui/gist/GistsViewActivity.java b/app/src/main/java/com/github/pockethub/android/ui/gist/GistsViewActivity.java index 3fe62a5fe..e47ee62f4 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/gist/GistsViewActivity.java +++ b/app/src/main/java/com/github/pockethub/android/ui/gist/GistsViewActivity.java @@ -18,7 +18,6 @@ import android.content.Intent; import android.os.Bundle; import android.support.v7.app.ActionBar; -import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.MenuItem; @@ -26,23 +25,28 @@ import com.github.pockethub.android.R; import com.github.pockethub.android.core.OnLoadListener; import com.github.pockethub.android.core.gist.GistStore; +import com.github.pockethub.android.rx.AutoDisposeUtils; import com.github.pockethub.android.rx.RxProgress; import com.github.pockethub.android.ui.ConfirmDialogFragment; import com.github.pockethub.android.ui.FragmentProvider; import com.github.pockethub.android.ui.MainActivity; import com.github.pockethub.android.ui.PagerActivity; import com.github.pockethub.android.ui.ViewPager; +import com.github.pockethub.android.ui.item.gist.GistItem; import com.github.pockethub.android.ui.user.UriLauncherActivity; import com.github.pockethub.android.util.AvatarLoader; import com.github.pockethub.android.util.ToastUtils; import com.meisolsson.githubsdk.core.ServiceGenerator; import com.meisolsson.githubsdk.model.Gist; import com.meisolsson.githubsdk.service.gists.GistService; -import com.google.inject.Inject; +import com.xwray.groupie.Item; + +import javax.inject.Inject; import java.io.Serializable; import java.util.List; +import butterknife.BindView; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; @@ -56,8 +60,7 @@ /** * Activity to display a collection of Gists in a pager */ -public class GistsViewActivity extends PagerActivity implements - OnLoadListener { +public class GistsViewActivity extends PagerActivity implements OnLoadListener { private static final int REQUEST_CONFIRM_DELETE = 1; private static final String TAG = "GistsViewActivity"; @@ -76,14 +79,15 @@ public static Intent createIntent(Gist gist) { /** * Create an intent to show gists with an initial selected Gist * - * @param gists + * @param items * @param position * @return intent */ - public static Intent createIntent(List gists, int position) { - String[] ids = new String[gists.size()]; + public static Intent createIntent(List items, int position) { + String[] ids = new String[items.size()]; int index = 0; - for (Gist gist : gists) { + for (Item item : items) { + Gist gist = ((GistItem) item).getData(); ids[index++] = gist.id(); } return new Builder("gists.VIEW") @@ -91,7 +95,8 @@ public static Intent createIntent(List gists, int position) { .add(EXTRA_POSITION, position).toIntent(); } - private ViewPager pager; + @BindView(R.id.vp_pages) + protected ViewPager pager; private String[] gists; @@ -100,10 +105,10 @@ public static Intent createIntent(List gists, int position) { private int initialPosition; @Inject - private GistStore store; + protected GistStore store; @Inject - private AvatarLoader avatars; + protected AvatarLoader avatars; private GistsPagerAdapter adapter; @@ -111,14 +116,9 @@ public static Intent createIntent(List gists, int position) { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_pager); - - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); - gists = getStringArrayExtra(EXTRA_GIST_IDS); gist = getParcelableExtra(EXTRA_GIST); initialPosition = getIntExtra(EXTRA_POSITION); - pager = (ViewPager) findViewById(R.id.vp_pages); getSupportActionBar().setDisplayHomeAsUpEnabled(true); @@ -136,11 +136,22 @@ protected void onCreate(Bundle savedInstanceState) { adapter = new GistsPagerAdapter(this, gists); pager.setAdapter(adapter); - pager.setOnPageChangeListener(this); + pager.addOnPageChangeListener(this); pager.scheduleSetItem(initialPosition, this); onPageSelected(initialPosition); } + @Override + protected void onDestroy() { + super.onDestroy(); + pager.removeOnPageChangeListener(this); + } + + @Override + protected int getContentView() { + return R.layout.activity_pager; + } + @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { @@ -172,8 +183,8 @@ public void onDialogResult(int requestCode, int resultCode, Bundle arguments) { .deleteGist(gistId) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .compose(this.bindToLifecycle()) .compose(RxProgress.bindToLifecycle(this, R.string.deleting_gist)) + .as(AutoDisposeUtils.bindToLifecycle(this)) .subscribe(response -> { setResult(RESULT_OK); finish(); diff --git a/app/src/main/java/com/github/pockethub/android/ui/gist/MyGistsFragment.java b/app/src/main/java/com/github/pockethub/android/ui/gist/MyGistsFragment.java index c329ee407..f7994bdaa 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/gist/MyGistsFragment.java +++ b/app/src/main/java/com/github/pockethub/android/ui/gist/MyGistsFragment.java @@ -18,14 +18,15 @@ import android.accounts.Account; import android.content.Intent; -import com.github.pockethub.android.core.PageIterator; -import com.github.pockethub.android.core.ResourcePager; -import com.github.pockethub.android.core.gist.GistPager; import com.meisolsson.githubsdk.core.ServiceGenerator; import com.meisolsson.githubsdk.model.Gist; +import com.meisolsson.githubsdk.model.Page; import com.meisolsson.githubsdk.service.gists.GistService; -import com.google.inject.Inject; -import com.google.inject.Provider; +import javax.inject.Inject; +import javax.inject.Provider; + +import io.reactivex.Single; +import retrofit2.Response; import static android.app.Activity.RESULT_OK; import static com.github.pockethub.android.RequestCodes.GIST_CREATE; @@ -36,8 +37,10 @@ */ public class MyGistsFragment extends GistsFragment { + GistService service = ServiceGenerator.createService(getActivity(), GistService.class); + @Inject - private Provider accountProvider; + protected Provider accountProvider; @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { @@ -51,15 +54,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } @Override - protected ResourcePager createPager() { - return new GistPager(store) { - - @Override - public PageIterator createIterator(int page, int size) { - return new PageIterator<>(page1 -> - ServiceGenerator.createService(getActivity(), GistService.class) - .getUserGists(page1), page); - } - }; + protected Single>> loadData(int page) { + return service.getUserGists(page); } } diff --git a/app/src/main/java/com/github/pockethub/android/ui/gist/PublicGistsFragment.java b/app/src/main/java/com/github/pockethub/android/ui/gist/PublicGistsFragment.java index 90f28dd27..5f07976df 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/gist/PublicGistsFragment.java +++ b/app/src/main/java/com/github/pockethub/android/ui/gist/PublicGistsFragment.java @@ -15,28 +15,23 @@ */ package com.github.pockethub.android.ui.gist; -import com.github.pockethub.android.core.PageIterator; -import com.github.pockethub.android.core.ResourcePager; -import com.github.pockethub.android.core.gist.GistPager; import com.meisolsson.githubsdk.core.ServiceGenerator; import com.meisolsson.githubsdk.model.Gist; +import com.meisolsson.githubsdk.model.Page; import com.meisolsson.githubsdk.service.gists.GistService; +import io.reactivex.Single; +import retrofit2.Response; + /** * Fragment to display a list of public Gists */ public class PublicGistsFragment extends GistsFragment { - @Override - protected ResourcePager createPager() { - return new GistPager(store) { + GistService service = ServiceGenerator.createService(getActivity(), GistService.class); - @Override - public PageIterator createIterator(int page, int size) { - return new PageIterator<>(page1 -> - ServiceGenerator.createService(getActivity(), GistService.class) - .getPublicGists(page1), page); - } - }; + @Override + protected Single>> loadData(int page) { + return service.getPublicGists(page); } } diff --git a/app/src/main/java/com/github/pockethub/android/ui/gist/StarredGistsFragment.java b/app/src/main/java/com/github/pockethub/android/ui/gist/StarredGistsFragment.java index 6da77bf4f..f37beb59e 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/gist/StarredGistsFragment.java +++ b/app/src/main/java/com/github/pockethub/android/ui/gist/StarredGistsFragment.java @@ -15,28 +15,23 @@ */ package com.github.pockethub.android.ui.gist; -import com.github.pockethub.android.core.PageIterator; -import com.github.pockethub.android.core.ResourcePager; -import com.github.pockethub.android.core.gist.GistPager; import com.meisolsson.githubsdk.core.ServiceGenerator; import com.meisolsson.githubsdk.model.Gist; +import com.meisolsson.githubsdk.model.Page; import com.meisolsson.githubsdk.service.gists.GistService; +import io.reactivex.Single; +import retrofit2.Response; + /** * Fragment to display a list of Gists */ public class StarredGistsFragment extends GistsFragment { - @Override - protected ResourcePager createPager() { - return new GistPager(store) { + GistService service = ServiceGenerator.createService(getActivity(), GistService.class); - @Override - public PageIterator createIterator(int page, int size) { - return new PageIterator<>(page1 -> - ServiceGenerator.createService(getActivity(), GistService.class) - .getUserStarredGists(page1), page); - } - }; + @Override + protected Single>> loadData(int page) { + return service.getUserStarredGists(page); } } diff --git a/app/src/main/java/com/github/pockethub/android/ui/issue/AssigneeDialogFragment.java b/app/src/main/java/com/github/pockethub/android/ui/issue/AssigneeDialogFragment.java index 4903bcc6b..e1c2de70b 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/issue/AssigneeDialogFragment.java +++ b/app/src/main/java/com/github/pockethub/android/ui/issue/AssigneeDialogFragment.java @@ -15,66 +15,31 @@ */ package com.github.pockethub.android.ui.issue; -import android.app.Activity; import android.app.Dialog; -import android.content.DialogInterface; import android.os.Bundle; -import android.view.LayoutInflater; -import android.widget.ListView; +import android.support.annotation.NonNull; +import android.view.View; -import com.afollestad.materialdialogs.MaterialDialog; +import com.github.pockethub.android.ui.item.dialog.AssigneeDialogItem; import com.meisolsson.githubsdk.model.User; -import com.github.kevinsawicki.wishlist.SingleTypeAdapter; import com.github.pockethub.android.R; import com.github.pockethub.android.ui.BaseActivity; import com.github.pockethub.android.ui.SingleChoiceDialogFragment; import com.github.pockethub.android.util.AvatarLoader; -import com.google.inject.Inject; +import com.xwray.groupie.GroupAdapter; +import com.xwray.groupie.Item; + +import javax.inject.Inject; import java.util.ArrayList; import static android.app.Activity.RESULT_OK; -import static android.content.DialogInterface.BUTTON_NEGATIVE; -import static android.content.DialogInterface.BUTTON_NEUTRAL; /** * Dialog fragment to select an issue assignee from a list of collaborators */ public class AssigneeDialogFragment extends SingleChoiceDialogFragment { - private static class UserListAdapter extends SingleTypeAdapter { - - private final int selected; - - private final AvatarLoader loader; - - public UserListAdapter(LayoutInflater inflater, User[] users, - int selected, AvatarLoader loader) { - super(inflater, R.layout.collaborator_item); - - this.selected = selected; - this.loader = loader; - setItems(users); - } - - @Override - public long getItemId(int position) { - return getItem(position).id(); - } - - @Override - protected int[] getChildViewIds() { - return new int[] { R.id.tv_login, R.id.iv_avatar, R.id.rb_selected }; - } - - @Override - protected void update(int position, User item) { - setText(0, item.login()); - loader.bind(imageView(1), item); - setChecked(2, selected == position); - } - } - /** * Get selected user from results bundle * @@ -103,37 +68,25 @@ public static void show(final BaseActivity activity, } @Inject - private AvatarLoader loader; + protected AvatarLoader avatars; + @NonNull @Override public Dialog onCreateDialog(final Bundle savedInstanceState) { - Activity activity = getActivity(); - Bundle arguments = getArguments(); - - final MaterialDialog.Builder dialogBuilder = createDialogBuilder() - .negativeText(R.string.cancel) - .onNegative((dialog, which) -> onClick(dialog, BUTTON_NEGATIVE)) - .neutralText(R.string.clear) - .onNeutral((dialog, which) -> onClick(dialog, BUTTON_NEUTRAL)); - - LayoutInflater inflater = activity.getLayoutInflater(); + int selected = getArguments().getInt(ARG_SELECTED_CHOICE); - ListView view = (ListView) inflater.inflate(R.layout.dialog_list_view, - null); - view.setOnItemClickListener((parent, view1, position, id) -> - onClick(getDialog(), position)); - - ArrayList choices = getChoices(); - int selected = arguments.getInt(ARG_SELECTED_CHOICE); - UserListAdapter adapter = new UserListAdapter(inflater, - choices.toArray(new User[choices.size()]), selected, loader); - view.setAdapter(adapter); - if (selected >= 0) { - view.setSelection(selected); + GroupAdapter adapter = new GroupAdapter(); + for (User user : getChoices()) { + adapter.add(new AssigneeDialogItem(avatars, user, selected)); } - dialogBuilder.customView(view, false); - return dialogBuilder.build(); + + return createDialogBuilder() + .adapter(adapter, null) + .negativeText(R.string.cancel) + .neutralText(R.string.clear) + .onNeutral((dialog, which) -> onResult(RESULT_OK)) + .build(); } @SuppressWarnings("unchecked") @@ -142,17 +95,10 @@ private ArrayList getChoices() { } @Override - public void onClick(DialogInterface dialog, int which) { - super.onClick(dialog, which); - - switch (which) { - case BUTTON_NEGATIVE: - break; - case BUTTON_NEUTRAL: - onResult(RESULT_OK); - break; - default: - getArguments().putParcelable(ARG_SELECTED, getChoices().get(which)); + public void onItemClick(@NonNull Item item, @NonNull View view) { + super.onItemClick(item, view); + if (item instanceof AssigneeDialogItem) { + getArguments().putParcelable(ARG_SELECTED, ((AssigneeDialogItem) item).getData()); onResult(RESULT_OK); } } diff --git a/app/src/main/java/com/github/pockethub/android/ui/issue/CreateCommentActivity.java b/app/src/main/java/com/github/pockethub/android/ui/issue/CreateCommentActivity.java index 0de93d9df..4b62042e3 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/issue/CreateCommentActivity.java +++ b/app/src/main/java/com/github/pockethub/android/ui/issue/CreateCommentActivity.java @@ -19,6 +19,7 @@ import android.os.Bundle; import android.support.v7.app.ActionBar; +import com.github.pockethub.android.rx.AutoDisposeUtils; import com.meisolsson.githubsdk.core.ServiceGenerator; import com.meisolsson.githubsdk.model.Issue; import com.meisolsson.githubsdk.model.Repository; @@ -87,7 +88,7 @@ protected void createComment(String comment) { .createIssueComment(repositoryId.owner().login(), repositoryId.name(), issueNumber, commentRequest) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .compose(this.bindToLifecycle()) + .as(AutoDisposeUtils.bindToLifecycle(this)) .subscribe(response -> finish(response.body())); } diff --git a/app/src/main/java/com/github/pockethub/android/ui/issue/DashboardIssueFragment.java b/app/src/main/java/com/github/pockethub/android/ui/issue/DashboardIssueFragment.java index e258aea32..8c48e297f 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/issue/DashboardIssueFragment.java +++ b/app/src/main/java/com/github/pockethub/android/ui/issue/DashboardIssueFragment.java @@ -17,24 +17,31 @@ import android.content.Intent; import android.os.Bundle; +import android.support.annotation.NonNull; import android.view.View; -import android.widget.ListView; -import com.github.kevinsawicki.wishlist.SingleTypeAdapter; import com.github.pockethub.android.R; -import com.github.pockethub.android.core.PageIterator; -import com.github.pockethub.android.core.ResourcePager; import com.github.pockethub.android.core.issue.IssueStore; import com.github.pockethub.android.ui.PagedItemFragment; +import com.github.pockethub.android.ui.item.issue.IssueDashboardItem; +import com.github.pockethub.android.ui.item.issue.IssueItem; import com.github.pockethub.android.util.AvatarLoader; import com.meisolsson.githubsdk.core.ServiceGenerator; import com.meisolsson.githubsdk.model.Issue; +import com.meisolsson.githubsdk.model.Page; import com.meisolsson.githubsdk.service.issues.IssueService; -import com.google.inject.Inject; +import com.xwray.groupie.Item; -import java.util.List; +import javax.inject.Inject; + +import java.util.ArrayList; +import java.util.Collection; import java.util.Map; +import io.reactivex.Observable; +import io.reactivex.Single; +import retrofit2.Response; + import static com.github.pockethub.android.RequestCodes.ISSUE_VIEW; /** @@ -47,20 +54,21 @@ public class DashboardIssueFragment extends PagedItemFragment { */ public static final String ARG_FILTER = "filter"; + private IssueService service = ServiceGenerator.createService(getActivity(), IssueService.class); + @Inject - private IssueStore store; + protected IssueStore store; @Inject - private AvatarLoader avatars; + protected AvatarLoader avatars; private Map filterData; @SuppressWarnings("unchecked") @Override public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - filterData = (Map) getArguments().getSerializable(ARG_FILTER); + super.onActivityCreated(savedInstanceState); } @Override @@ -75,33 +83,22 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } @Override - public void onListItemClick(ListView l, View v, int position, long id) { - startActivityForResult( - IssuesViewActivity.createIntent(items, position - - getListAdapter().getHeadersCount()), ISSUE_VIEW); + protected Single>> loadData(int page) { + return service.getIssues(filterData, page); } @Override - protected ResourcePager createPager() { - return new ResourcePager() { - - @Override - protected Issue register(Issue resource) { - return store.addIssue(resource); - } - - @Override - protected Object getId(Issue resource) { - return resource.id(); + public void onItemClick(@NonNull Item clickedItem, @NonNull View view) { + if (clickedItem instanceof IssueDashboardItem) { + int position = getListAdapter().getAdapterPosition(clickedItem); + Collection issues = new ArrayList<>(); + for (Item item : items) { + if (item instanceof IssueDashboardItem) { + issues.add(((IssueItem) item).getData()); + } } - - @Override - public PageIterator createIterator(int page, int size) { - return new PageIterator<>(page1 -> - ServiceGenerator.createService(getActivity(), IssueService.class) - .getIssues(filterData, page1), page); - } - }; + startActivityForResult(IssuesViewActivity.createIntent(issues, position), ISSUE_VIEW); + } } @Override @@ -110,14 +107,12 @@ protected int getLoadingMessage() { } @Override - protected int getErrorMessage(Exception exception) { + protected int getErrorMessage() { return R.string.error_issues_load; } @Override - protected SingleTypeAdapter createAdapter( - List items) { - return new DashboardIssueListAdapter(avatars, getActivity() - .getLayoutInflater(), items.toArray(new Issue[items.size()])); + protected Item createItem(Issue item) { + return new IssueDashboardItem(avatars, item); } } diff --git a/app/src/main/java/com/github/pockethub/android/ui/issue/DashboardIssueListAdapter.java b/app/src/main/java/com/github/pockethub/android/ui/issue/DashboardIssueListAdapter.java deleted file mode 100644 index b12f7418b..000000000 --- a/app/src/main/java/com/github/pockethub/android/ui/issue/DashboardIssueListAdapter.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2015 PocketHub - * - * 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.pockethub.android.ui.issue; - -import android.view.LayoutInflater; -import android.view.View; - -import com.github.pockethub.android.R; -import com.github.pockethub.android.core.issue.IssueUtils; -import com.github.pockethub.android.util.AvatarLoader; -import com.meisolsson.githubsdk.model.Issue; - -/** - * Adapter to display a list of dashboard issues - */ -public class DashboardIssueListAdapter extends - IssueListAdapter { - - private int numberPaintFlags; - - /** - * Create adapter - * - * @param avatars - * @param inflater - * @param elements - */ - public DashboardIssueListAdapter(AvatarLoader avatars, - LayoutInflater inflater, Issue[] elements) { - super(R.layout.dashboard_issue_item, inflater, elements, avatars); - } - - @Override - public long getItemId(final int position) { - return getItem(position).id(); - } - - @Override - protected int getNumber(final Issue issue) { - return (int) issue.number(); - } - - @Override - protected View initialize(View view) { - view = super.initialize(view); - - numberPaintFlags = textView(view, 1).getPaintFlags(); - return view; - } - - @Override - protected int[] getChildViewIds() { - return new int[] { R.id.tv_issue_repo_name, R.id.tv_issue_number, - R.id.tv_issue_title, R.id.iv_avatar, R.id.tv_issue_creation, - R.id.tv_issue_comments, R.id.tv_pull_request_icon, R.id.v_label0, - R.id.v_label1, R.id.v_label2, R.id.v_label3, R.id.v_label4, - R.id.v_label5, R.id.v_label6, R.id.v_label7 }; - } - - @Override - protected void update(int position, Issue issue) { - updateNumber(issue.number(), issue.state(), numberPaintFlags, 1); - - avatars.bind(imageView(3), issue.user()); - - String[] segments = issue.url().split("/"); - int length = segments.length; - if (length >= 4) { - setText(0, segments[length - 4] + '/' + segments[length - 3]); - } else { - setText(0, null); - } - - setGone(6, !IssueUtils.isPullRequest(issue)); - - setText(2, issue.title()); - - updateReporter(issue.user().login(), issue.createdAt(), 4); - setNumber(5, issue.comments()); - updateLabels(issue.labels(), 7); - } -} diff --git a/app/src/main/java/com/github/pockethub/android/ui/issue/EditAssigneeTask.java b/app/src/main/java/com/github/pockethub/android/ui/issue/EditAssigneeTask.java index 2f86fb3a5..6cc43efe0 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/issue/EditAssigneeTask.java +++ b/app/src/main/java/com/github/pockethub/android/ui/issue/EditAssigneeTask.java @@ -15,7 +15,10 @@ */ package com.github.pockethub.android.ui.issue; +import com.github.pockethub.android.rx.AutoDisposeUtils; import com.github.pockethub.android.rx.RxProgress; +import com.google.auto.factory.AutoFactory; +import com.google.auto.factory.Provided; import com.meisolsson.githubsdk.model.Issue; import com.meisolsson.githubsdk.model.Repository; import com.meisolsson.githubsdk.model.User; @@ -23,14 +26,13 @@ import com.github.pockethub.android.core.issue.IssueStore; import com.github.pockethub.android.ui.BaseActivity; import com.meisolsson.githubsdk.model.request.issue.IssueRequest; -import com.google.inject.Inject; +import javax.inject.Inject; import java.util.Collections; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.functions.Consumer; import io.reactivex.schedulers.Schedulers; -import roboguice.RoboGuice; import static com.github.pockethub.android.RequestCodes.ISSUE_ASSIGNEE_UPDATE; @@ -38,10 +40,10 @@ * Task to edit the assignee */ //TODO Let this take multiple assignees +@AutoFactory public class EditAssigneeTask { - @Inject - private IssueStore store; + private final IssueStore store; private final AssigneeDialog assigneeDialog; @@ -60,16 +62,15 @@ public class EditAssigneeTask { * @param repositoryId * @param issueNumber */ - public EditAssigneeTask(final BaseActivity activity, + public EditAssigneeTask(@Provided IssueStore store, final BaseActivity activity, final Repository repositoryId, final int issueNumber, final Consumer observer) { + this.store = store; this.activity = activity; this.repositoryId = repositoryId; this.issueNumber = issueNumber; this.observer = observer; - assigneeDialog = new AssigneeDialog(activity, ISSUE_ASSIGNEE_UPDATE, - repositoryId); - RoboGuice.injectMembers(activity, this); + assigneeDialog = new AssigneeDialog(activity, ISSUE_ASSIGNEE_UPDATE, repositoryId); } /** @@ -100,8 +101,8 @@ public EditAssigneeTask edit(User assignee) { store.editIssue(repositoryId, issueNumber, edit) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .compose(activity.bindToLifecycle()) .compose(RxProgress.bindToLifecycle(activity, R.string.updating_assignee)) + .as(AutoDisposeUtils.bindToLifecycle(activity)) .subscribe(observer); return this; diff --git a/app/src/main/java/com/github/pockethub/android/ui/issue/EditCommentActivity.java b/app/src/main/java/com/github/pockethub/android/ui/issue/EditCommentActivity.java index 998d87d2e..6bb994597 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/issue/EditCommentActivity.java +++ b/app/src/main/java/com/github/pockethub/android/ui/issue/EditCommentActivity.java @@ -20,6 +20,7 @@ import android.support.v7.app.ActionBar; import android.util.Log; +import com.github.pockethub.android.rx.AutoDisposeUtils; import com.github.pockethub.android.rx.RxProgress; import com.meisolsson.githubsdk.core.ServiceGenerator; import com.meisolsson.githubsdk.model.GitHubComment; @@ -111,8 +112,8 @@ protected void editComment(String commentText) { comment.id(), commentRequest) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .compose(this.bindToLifecycle()) .compose(RxProgress.bindToLifecycle(this, R.string.editing_comment)) + .as(AutoDisposeUtils.bindToLifecycle(this)) .subscribe(response -> finish(response.body()), e -> { Log.d(TAG, "Exception editing comment on issue", e); ToastUtils.show(this, e.getMessage()); diff --git a/app/src/main/java/com/github/pockethub/android/ui/issue/EditIssueActivity.java b/app/src/main/java/com/github/pockethub/android/ui/issue/EditIssueActivity.java index 86af20a62..419184ced 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/issue/EditIssueActivity.java +++ b/app/src/main/java/com/github/pockethub/android/ui/issue/EditIssueActivity.java @@ -38,6 +38,7 @@ import android.widget.TextView; import com.afollestad.materialdialogs.MaterialDialog; +import com.github.pockethub.android.rx.AutoDisposeUtils; import com.github.pockethub.android.rx.RxProgress; import com.github.pockethub.android.util.ImageBinPoster; import com.github.pockethub.android.util.PermissionsUtils; @@ -60,13 +61,16 @@ import com.meisolsson.githubsdk.model.request.issue.IssueRequest; import com.meisolsson.githubsdk.service.issues.IssueService; import com.meisolsson.githubsdk.service.repositories.RepositoryCollaboratorService; -import com.google.inject.Inject; +import javax.inject.Inject; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import butterknife.BindView; +import butterknife.OnClick; +import butterknife.OnTextChanged; import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; @@ -129,26 +133,35 @@ public static Intent createIntent(final Issue issue, return builder.toIntent(); } - private EditText titleText; + @BindView(R.id.et_issue_title) + protected EditText titleText; - private EditText bodyText; + @BindView(R.id.et_issue_body) + protected EditText bodyText; - private View milestoneGraph; + @BindView(R.id.ll_milestone_graph) + protected View milestoneGraph; - private TextView milestoneText; + @BindView(R.id.tv_milestone) + protected TextView milestoneText; - private View milestoneClosed; + @BindView(R.id.v_closed) + protected View milestoneClosed; - private ImageView assigneeAvatar; + @BindView(R.id.iv_assignee_avatar) + protected ImageView assigneeAvatar; - private TextView assigneeText; + @BindView(R.id.tv_assignee_name) + protected TextView assigneeText; - private TextView labelsText; + @BindView(R.id.tv_labels) + protected TextView labelsText; - private FloatingActionButton addImageFab; + @BindView(R.id.fab_add_image) + protected FloatingActionButton addImageFab; @Inject - private AvatarLoader avatars; + protected AvatarLoader avatars; private Issue issue; @@ -167,19 +180,6 @@ public static Intent createIntent(final Issue issue, @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - setContentView(R.layout.activity_issue_edit); - - titleText = (EditText) findViewById(R.id.et_issue_title); - bodyText = (EditText) findViewById(R.id.et_issue_body); - milestoneGraph = findViewById(R.id.ll_milestone_graph); - milestoneText = (TextView) findViewById(R.id.tv_milestone); - milestoneClosed = findViewById(R.id.v_closed); - assigneeAvatar = (ImageView) findViewById(R.id.iv_assignee_avatar); - assigneeText = (TextView) findViewById(R.id.tv_assignee_name); - labelsText = (TextView) findViewById(R.id.tv_labels); - addImageFab = (FloatingActionButton) findViewById(R.id.fab_add_image); - Intent intent = getIntent(); if (savedInstanceState != null) { @@ -198,8 +198,6 @@ protected void onCreate(Bundle savedInstanceState) { checkCollaboratorStatus(); - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); - ActionBar actionBar = getSupportActionBar(); if (issue.number() != null && issue.number() > 0) { if (IssueUtils.isPullRequest(issue)) { @@ -215,44 +213,16 @@ protected void onCreate(Bundle savedInstanceState) { actionBar.setSubtitle(InfoUtils.createRepoId(repository)); avatars.bind(actionBar, (User) intent.getParcelableExtra(EXTRA_USER)); - titleText.addTextChangedListener(new TextWatcherAdapter() { - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - super.onTextChanged(s, start, before, count); - updateSaveMenu(s); - } - }); - - // @TargetApi(…) required to ensure build passes - // noinspection Convert2Lambda - addImageFab.setOnClickListener(new View.OnClickListener() { - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - @Override - public void onClick(View v) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - Activity activity = EditIssueActivity.this; - String permission = Manifest.permission.READ_EXTERNAL_STORAGE; - - if (ContextCompat.checkSelfPermission(activity, permission) - != PackageManager.PERMISSION_GRANTED) { - PermissionsUtils.askForPermission(activity, READ_PERMISSION_REQUEST, - permission, R.string.read_permission_title, - R.string.read_permission_content); - } else { - startImagePicker(); - } - } else { - startImagePicker(); - } - } - }); - updateSaveMenu(); titleText.setText(issue.title()); bodyText.setText(issue.body()); } + @Override + protected int getContentView() { + return R.layout.activity_issue_edit; + } + @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); @@ -333,6 +303,30 @@ public void onResponse(Call call, okhttp3.Response response) throws IOException } } + @OnTextChanged(R.id.et_issue_title) + protected void onIssueTitleChange(CharSequence text) { + updateSaveMenu(text); + } + + @OnClick(R.id.fab_add_image) + protected void onAddImageClicked() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + Activity activity = EditIssueActivity.this; + String permission = Manifest.permission.READ_EXTERNAL_STORAGE; + + if (ContextCompat.checkSelfPermission(activity, permission) + != PackageManager.PERMISSION_GRANTED) { + PermissionsUtils.askForPermission(activity, READ_PERMISSION_REQUEST, + permission, R.string.read_permission_title, + R.string.read_permission_content); + } else { + startImagePicker(); + } + } else { + startImagePicker(); + } + } + private void showImageError() { ToastUtils.show(this, R.string.error_image_upload); } @@ -434,9 +428,7 @@ protected void onSaveInstanceState(Bundle outState) { } private void updateSaveMenu() { - if (titleText != null) { - updateSaveMenu(titleText.getText()); - } + updateSaveMenu(titleText.getText()); } private void updateSaveMenu(final CharSequence text) { @@ -494,8 +486,8 @@ public boolean onOptionsItemSelected(MenuItem item) { single.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .compose(this.bindToLifecycle()) .compose(RxProgress.bindToLifecycle(this, message)) + .as(AutoDisposeUtils.bindToLifecycle(this)) .subscribe(response -> { Intent intent = new Intent(); intent.putExtra(EXTRA_ISSUE, response.body()); @@ -516,7 +508,7 @@ private void checkCollaboratorStatus() { .isUserCollaborator(repository.owner().login(), repository.name(), AccountUtils.getLogin(this)) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .compose(this.bindToLifecycle()) + .as(AutoDisposeUtils.bindToLifecycle(this)) .subscribe(response -> { showMainContent(); if (response.code() == 204) { diff --git a/app/src/main/java/com/github/pockethub/android/ui/issue/EditIssuesFilterActivity.java b/app/src/main/java/com/github/pockethub/android/ui/issue/EditIssuesFilterActivity.java index ccd88b0df..443e2edc7 100644 --- a/app/src/main/java/com/github/pockethub/android/ui/issue/EditIssuesFilterActivity.java +++ b/app/src/main/java/com/github/pockethub/android/ui/issue/EditIssuesFilterActivity.java @@ -21,7 +21,9 @@ import android.support.v7.widget.Toolbar; import android.view.Menu; import android.view.MenuItem; +import android.view.View; import android.view.View.OnClickListener; +import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.RadioButton; import android.widget.RadioGroup; @@ -37,9 +39,13 @@ import com.github.pockethub.android.ui.BaseActivity; import com.github.pockethub.android.util.AvatarLoader; import com.github.pockethub.android.util.InfoUtils; -import com.google.inject.Inject; +import javax.inject.Inject; import java.util.List; +import butterknife.BindView; +import butterknife.OnCheckedChanged; +import butterknife.OnClick; + import static android.view.View.GONE; import static com.github.pockethub.android.Intents.EXTRA_ISSUE_FILTER; @@ -65,8 +71,20 @@ public static Intent createIntent(IssueFilter filter) { private static final int REQUEST_ASSIGNEE = 3; + @BindView(R.id.tv_labels) + protected TextView labelsText; + + @BindView(R.id.tv_milestone) + protected TextView milestoneText; + + @BindView(R.id.tv_assignee) + protected TextView assigneeText; + + @BindView(R.id.iv_avatar) + protected ImageView avatarView; + @Inject - private AvatarLoader avatars; + protected AvatarLoader avatars; private LabelsDialog labelsDialog; @@ -76,25 +94,9 @@ public static Intent createIntent(IssueFilter filter) { private IssueFilter filter; - private TextView labelsText; - - private TextView milestoneText; - - private TextView assigneeText; - - private ImageView avatarView; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - setContentView(R.layout.activity_issues_filter_edit); - - labelsText = (TextView) findViewById(R.id.tv_labels); - milestoneText = (TextView) findViewById(R.id.tv_milestone); - assigneeText = (TextView) findViewById(R.id.tv_assignee); - avatarView = (ImageView) findViewById(R.id.iv_avatar); - if (savedInstanceState != null) { filter = savedInstanceState.getParcelable(EXTRA_ISSUE_FILTER); } @@ -103,55 +105,24 @@ protected void onCreate(Bundle savedInstanceState) { filter = getIntent().getParcelableExtra(EXTRA_ISSUE_FILTER); } - final Repository repository = filter.getRepository(); - - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); + Repository repository = filter.getRepository(); ActionBar actionBar = getSupportActionBar(); actionBar.setTitle(R.string.filter_issues_title); actionBar.setSubtitle(InfoUtils.createRepoId(repository)); avatars.bind(actionBar, repository.owner()); - OnClickListener assigneeListener = v -> { - if (assigneeDialog == null) { - assigneeDialog = new AssigneeDialog(this, REQUEST_ASSIGNEE, repository); - } - assigneeDialog.show(filter.getAssignee()); - }; - - findViewById(R.id.tv_assignee_label).setOnClickListener(assigneeListener); - assigneeText.setOnClickListener(assigneeListener); - - OnClickListener milestoneListener = v -> { - if (milestoneDialog == null) { - milestoneDialog = new MilestoneDialog(this, REQUEST_MILESTONE, repository); - } - milestoneDialog.show(filter.getMilestone()); - }; - - findViewById(R.id.tv_milestone_label).setOnClickListener(milestoneListener); - milestoneText.setOnClickListener(milestoneListener); - - OnClickListener labelsListener = v -> { - if (labelsDialog == null) { - labelsDialog = new LabelsDialog(this, REQUEST_LABELS, repository); - } - labelsDialog.show(filter.getLabels()); - }; - - findViewById(R.id.tv_labels_label).setOnClickListener(labelsListener); - labelsText.setOnClickListener(labelsListener); - updateAssignee(); updateMilestone(); updateLabels(); - RadioGroup status = (RadioGroup) findViewById(R.id.issue_filter_status); - RadioGroup sortOrder = (RadioGroup) findViewById(R.id.issue_sort_order); - RadioGroup sortType = (RadioGroup) findViewById(R.id.issue_sort_type); + RadioGroup status = findViewById(R.id.issue_filter_status); + RadioGroup sortOrder = findViewById(R.id.issue_sort_order); + RadioGroup sortType = findViewById(R.id.issue_sort_type); - status.setOnCheckedChangeListener((radioGroup, checkedId) -> - filter.setOpen(checkedId == R.id.rb_open)); + status.setOnCheckedChangeListener(this::onStatusChanged); + sortOrder.setOnCheckedChangeListener(this::onSortOrderChanged); + sortType.setOnCheckedChangeListener(this::onSortTypeChanged); if (filter.isOpen()) { status.check(R.id.rb_open); @@ -159,13 +130,6 @@ protected void onCreate(Bundle savedInstanceState) { status.check(R.id.rb_closed); } - sortOrder.setOnCheckedChangeListener((radioGroup, checkedId) -> { - if (checkedId == R.id.rb_asc) { - filter.setDirection(IssueFilter.DIRECTION_ASCENDING); - } else { - filter.setDirection(IssueFilter.DIRECTION_DESCENDING); - } - }); if (filter.getDirection().equals(IssueFilter.DIRECTION_ASCENDING)) { sortOrder.check(R.id.rb_asc); @@ -173,22 +137,6 @@ protected void onCreate(Bundle savedInstanceState) { sortOrder.check(R.id.rb_desc); } - sortType.setOnCheckedChangeListener((radioGroup, checkedId) -> { - switch (checkedId) { - case R.id.rb_created: - filter.setSortType(IssueFilter.SORT_CREATED); - break; - case R.id.rb_updated: - filter.setSortType(IssueFilter.SORT_UPDATED); - break; - case R.id.rb_comments: - filter.setSortType(IssueFilter.SORT_COMMENTS); - break; - default: - break; - } - }); - switch (filter.getSortType()) { case IssueFilter.SORT_CREATED: sortType.check(R.id.rb_created); @@ -204,6 +152,11 @@ protected void onCreate(Bundle savedInstanceState) { } } + @Override + protected int getContentView() { + return R.layout.activity_issues_filter_edit; + } + @Override public boolean onCreateOptionsMenu(Menu options) { getMenuInflater().inflate(R.menu.activity_issue_filter, options); @@ -231,6 +184,58 @@ protected void onSaveInstanceState(Bundle outState) { outState.putParcelable(EXTRA_ISSUE_FILTER, filter); } + @OnClick({R.id.tv_assignee, R.id.tv_assignee_label}) + protected void onAssigneeClicked() { + if (assigneeDialog == null) { + assigneeDialog = new AssigneeDialog(this, REQUEST_ASSIGNEE, filter.getRepository()); + } + assigneeDialog.show(filter.getAssignee()); + } + + @OnClick({R.id.tv_milestone, R.id.tv_milestone_label}) + protected void onMilestoneClicked() { + if (milestoneDialog == null) { + milestoneDialog = new MilestoneDialog(this, REQUEST_MILESTONE, filter.getRepository()); + } + milestoneDialog.show(filter.getMilestone()); + } + + @OnClick({R.id.tv_labels, R.id.tv_labels_label}) + protected void onLabelsClicked() { + if (labelsDialog == null) { + labelsDialog = new LabelsDialog(this, REQUEST_LABELS, filter.getRepository()); + } + labelsDialog.show(filter.getLabels()); + } + + private void onStatusChanged(RadioGroup radioGroup, int checkedId) { + filter.setOpen(checkedId == R.id.rb_open); + } + + private void onSortOrderChanged(RadioGroup radioGroup, int checkedId) { + if (checkedId == R.id.rb_asc) { + filter.setDirection(IssueFilter.DIRECTION_ASCENDING); + } else { + filter.setDirection(IssueFilter.DIRECTION_DESCENDING); + } + } + + private void onSortTypeChanged(RadioGroup radioGroup, int checkedId) { + switch (checkedId) { + case R.id.rb_created: + filter.setSortType(IssueFilter.SORT_CREATED); + break; + case R.id.rb_updated: + filter.setSortType(IssueFilter.SORT_UPDATED); + break; + case R.id.rb_comments: + filter.setSortType(IssueFilter.SORT_COMMENTS); + break; + default: + break; + } + } + private void updateLabels() { List