Skip to content
Snippets Groups Projects
Commit af79125c authored by John Carlson's avatar John Carlson
Browse files

Merged branch develop into master

parents 5212b54f 747021e0
No related branches found
No related tags found
No related merge requests found
Pipeline #
Showing
with 1520 additions and 1717 deletions
Loading
Loading
@@ -21,4 +21,4 @@ stages:
build:
stage: build
script:
- ./gradlew testFdroidDebug
\ No newline at end of file
- ./gradlew testFdroidDevDebug
\ No newline at end of file
Loading
Loading
@@ -67,7 +67,7 @@ If you are making substantial changes, please refer to Commit 451's style [guide
License
--------
 
Copyright 2016 Commit 451
Copyright 2017 Commit 451
 
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Loading
Loading
Loading
Loading
@@ -4,7 +4,7 @@ buildscript {
}
 
dependencies {
classpath 'io.fabric.tools:gradle:1.22.0'
classpath 'io.fabric.tools:gradle:1.22.1'
}
}
 
Loading
Loading
@@ -13,6 +13,7 @@ repositories {
}
 
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'io.fabric'
 
def versionMajor = 2
Loading
Loading
@@ -27,6 +28,7 @@ android {
project.ext {
LABCOAT_FABRIC_KEY = project.hasProperty("LABCOAT_FABRIC_KEY") ? project.LABCOAT_FABRIC_KEY : "";
}
defaultConfig {
applicationId "com.commit451.gitlab"
minSdkVersion 16
Loading
Loading
@@ -36,22 +38,38 @@ android {
versionName "${versionMajor}.${versionMinor}.${versionPatch}"
manifestPlaceholders = [fabric_key: project.ext.LABCOAT_FABRIC_KEY]
vectorDrawables.useSupportLibrary = true
multiDexEnabled true
}
 
flavorDimensions "TYPE", "API"
productFlavors {
// https://developer.android.com/tools/building/multidex.html#dev-build
dev {
dimension "API"
minSdkVersion 21
}
prod {
dimension "API"
}
normal {
dimension "TYPE"
}
 
fdroid {
dimension "TYPE"
}
}
 
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
ext.enableCrashlytics = true
shrinkResources true
}
debug {
minifyEnabled false
Loading
Loading
@@ -62,25 +80,26 @@ android {
lintOptions {
abortOnError false
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
}
 
ext.supportLibVersion = '25.1.0'
ext.retrofitVersion = '2.1.0'
ext.okHttpVersion = '3.5.0'
ext.butterknifeVersion = '8.4.0'
ext.loganSquareVersion = '1.3.7'
ext.parcelerVersion = '1.1.6'
ext.reptarVersion = '2.1.0'
ext.adapterLayout = '1.1.0'
ext.materialDialogsVersion = '0.9.2.2'
ext {
supportLibVersion = '25.1.1'
retrofitVersion = '2.1.0'
okHttpVersion = '3.6.0'
butterknifeVersion = '8.5.1'
loganSquareVersion = '1.3.7'
parcelerVersion = '1.1.6'
reptarVersion = '2.2.1'
adapterLayout = '1.1.0'
materialDialogsVersion = '0.9.3.0'
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
testCompile 'org.robolectric:robolectric:3.2.1'
 
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
 
compile "com.android.support:appcompat-v7:$supportLibVersion"
compile "com.android.support:design:$supportLibVersion"
Loading
Loading
@@ -88,37 +107,45 @@ dependencies {
compile "com.android.support:cardview-v7:$supportLibVersion"
compile "com.android.support:palette-v7:$supportLibVersion"
compile "com.android.support:customtabs:$supportLibVersion"
compile 'com.android.support:multidex:1.0.1'
compile "com.squareup.retrofit2:retrofit:$retrofitVersion"
compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
compile("com.squareup.retrofit2:converter-simplexml:$retrofitVersion") {
exclude group: 'xpp3', module: 'xpp3'
exclude group: 'stax', module: 'stax-api'
exclude group: 'stax', module: 'stax'
}
compile "com.squareup.retrofit2:converter-scalars:$retrofitVersion"
compile 'com.github.Commit451:OkioProGuardRules:1.11.0.0'
compile 'com.github.Commit451:RetrofitProguardRules:2.1.0.0'
compile 'com.github.aurae.retrofit2:converter-logansquare:1.4.1'
compile "com.bluelinelabs:logansquare:$loganSquareVersion"
annotationProcessor "com.bluelinelabs:logansquare-compiler:$loganSquareVersion"
compile 'com.github.Commit451:LoganSquareProGuardRules:1.3.7.0'
compile "com.squareup.okhttp3:okhttp:$okHttpVersion"
compile "com.squareup.okhttp3:logging-interceptor:$okHttpVersion"
compile 'com.squareup.picasso:picasso:2.5.2'
compile 'com.github.aurae.retrofit2:converter-logansquare:1.4.1'
compile "com.bluelinelabs:logansquare:$loganSquareVersion"
kapt "com.bluelinelabs:logansquare-compiler:$loganSquareVersion"
compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'
compile "com.jakewharton:butterknife:$butterknifeVersion"
kapt "com.jakewharton:butterknife-compiler:$butterknifeVersion"
compile 'com.jakewharton.timber:timber:4.5.1'
compile 'org.greenrobot:eventbus:3.0.0'
compile 'com.github.Commit451:EventBusProGuardRules:3.0.0.0'
compile 'io.reactivex.rxjava2:rxjava:2.0.1'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'com.trello.rxlifecycle2:rxlifecycle-components:2.0.1'
compile "com.jakewharton:butterknife:$butterknifeVersion"
annotationProcessor "com.jakewharton:butterknife-compiler:$butterknifeVersion"
compile 'com.jakewharton.timber:timber:4.4.0'
compile 'net.danlew:android.joda:2.9.5.1'
compile "org.parceler:parceler-api:$parcelerVersion"
annotationProcessor "org.parceler:parceler:$parcelerVersion"
kapt "org.parceler:parceler:$parcelerVersion"
compile "com.github.Commit451.Reptar:reptar:$reptarVersion@aar"
compile "com.github.Commit451.Reptar:reptar-retrofit:$reptarVersion@aar"
compile "com.github.Commit451.Reptar:reptar-kotlin:$reptarVersion@aar"
compile 'com.github.Commit451:bypasses:1.0.4'
compile 'com.github.Commit451:ElasticDragDismissLayout:1.0.4'
compile "com.github.Commit451.AdapterLayout:adapterlayout:$adapterLayout"
Loading
Loading
@@ -130,29 +157,54 @@ dependencies {
compile 'com.github.Commit451:Jounce:1.0.1'
compile 'com.github.Commit451:ForegroundViews:2.3.1'
compile 'com.github.Commit451:MorphTransitions:2.0.0'
compile 'com.github.Commit451:Alakazam:0.0.1'
compile 'com.github.Commit451.Alakazam:alakazam:1.0.1@aar'
compile 'com.github.Commit451:Lift:1.2.0'
compile 'com.github.Commit451:OkioProGuardRules:1.11.0.0'
compile 'com.github.Commit451:RetrofitProguardRules:2.1.0.0'
compile 'com.github.Commit451:LoganSquareProGuardRules:1.3.7.0'
compile 'com.github.Commit451:EventBusProGuardRules:3.0.0.0'
compile 'me.zhanghai.android.materialprogressbar:library:1.3.0'
compile 'com.github.Jawnnypoo:PhysicsLayout:2.1.0'
compile 'com.alexgwyn.recyclerviewsquire:recyclerviewsquire:0.0.6'
compile 'com.github.ivbaranov:materiallettericon:0.2.2'
compile 'com.github.johnkil.android-robototextview:robototextview:2.5.1'
compile 'com.github.alorma:diff-textview:1.3.0'
compile 'com.wdullaer:materialdatetimepicker:3.0.0'
compile 'com.wdullaer:materialdatetimepicker:3.1.1'
compile 'com.wefika:flowlayout:0.4.1'
compile 'com.github.novoda:simple-chrome-custom-tabs:0.1.4'
compile "com.afollestad.material-dialogs:core:$materialDialogsVersion"
compile "com.afollestad.material-dialogs:commons:$materialDialogsVersion"
compile 'de.hdodenhof:circleimageview:2.1.0'
compile('com.vdurmont:emoji-java:3.1.3') {
exclude group: 'org.json', module: 'json'
}
compile 'com.github.jkwiecien:EasyImage:1.4.1'
compile('com.github.ozodrukh:CircularReveal:2.0.1@aar') {
transitive = true;
}
 
normalCompile('com.crashlytics.sdk.android:crashlytics:2.6.5@aar') {
normalCompile('com.crashlytics.sdk.android:crashlytics:2.6.6@aar') {
transitive = true;
}
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile 'junit:junit:4.12'
testCompile 'org.robolectric:robolectric:3.2.2'
}
Loading
Loading
@@ -44,6 +44,7 @@
android:name=".activity.TodosActivity"
android:launchMode="singleTask"
android:theme="@style/Activity.Todos" />
<activity android:name=".activity.ProjectActivity" />
 
<activity
Loading
Loading
@@ -59,9 +60,11 @@
<activity android:name=".activity.AboutActivity" />
 
<activity android:name=".activity.AddUserActivity" />
<activity
android:name=".activity.UserActivity"
android:theme="@style/Activity.User" />
<activity android:name=".activity.SearchActivity" />
 
<activity
Loading
Loading
@@ -91,6 +94,7 @@
<activity
android:name=".activity.PickBranchOrTagActivity"
android:theme="@style/Activity.Translucent" />
<activity android:name=".activity.WebLoginActivity" />
 
<activity
Loading
Loading
package com.commit451.gitlab;
import android.app.Application;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.net.Uri;
import android.support.annotation.VisibleForTesting;
import com.bluelinelabs.logansquare.LoganSquare;
import com.commit451.gitlab.api.GitLab;
import com.commit451.gitlab.api.GitLabFactory;
import com.commit451.gitlab.api.GitLabRss;
import com.commit451.gitlab.api.GitLabRssFactory;
import com.commit451.gitlab.api.OkHttpClientFactory;
import com.commit451.gitlab.api.PicassoFactory;
import com.commit451.gitlab.api.converter.UriTypeConverter;
import com.commit451.gitlab.data.Prefs;
import com.commit451.gitlab.model.Account;
import com.commit451.gitlab.util.FabricUtil;
import com.commit451.lift.Lift;
import com.novoda.simplechromecustomtabs.SimpleChromeCustomTabs;
import com.squareup.leakcanary.LeakCanary;
import com.squareup.picasso.Picasso;
import net.danlew.android.joda.JodaTimeAndroid;
import org.greenrobot.eventbus.EventBus;
import java.util.List;
import java.util.Locale;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import timber.log.Timber;
/**
* App for one time init things and to house singletons
*/
public class App extends Application {
/**
* Register our type converters on our singleton LoganSquare get. Needs to be set here
* since we are fetching accounts immediately with LoganSquare
*/
static {
LoganSquare.registerTypeConverter(Uri.class, new UriTypeConverter());
}
private static EventBus bus;
private static App instance;
public static EventBus bus() {
if (bus == null) {
bus = EventBus.getDefault();
}
return bus;
}
public static App get() {
return instance;
}
private Account account;
private GitLab gitLab;
private GitLabRss gitLabRss;
private Picasso picasso;
private Prefs prefs;
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
setupLeakCanary();
instance = this;
prefs = new Prefs(this);
//So that we don't get weird half translations
forceLocale(Locale.ENGLISH);
setupCrashReporting();
if (BuildConfig.DEBUG) {
Timber.plant(new Timber.DebugTree());
}
JodaTimeAndroid.init(this);
SimpleChromeCustomTabs.initialize(this);
List<Account> accounts = Account.getAccounts();
if(!accounts.isEmpty()) {
setAccount(accounts.get(0));
}
Lift.check(this, new Lift.Callback() {
@Override
public void onUpgrade(int oldVersion, int newVersion) {
}
});
}
@VisibleForTesting
protected void setupCrashReporting() {
FabricUtil.init(this);
}
@VisibleForTesting
protected void setupLeakCanary() {
LeakCanary.install(this);
}
private void forceLocale(Locale locale){
try {
Locale.setDefault(locale);
Resources[] resources = new Resources[]{
Resources.getSystem(),
getBaseContext().getResources()
};
for (Resources res : resources) {
Configuration configuration = res.getConfiguration();
configuration.locale = locale;
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
} catch (Exception e) {
Timber.e(e);
}
}
public GitLab getGitLab() {
return gitLab;
}
public GitLabRss getGitLabRss() {
return gitLabRss;
}
public Picasso getPicasso() {
return picasso;
}
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
OkHttpClient.Builder clientBuilder = OkHttpClientFactory.create(account);
if (BuildConfig.DEBUG) {
clientBuilder.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY));
}
OkHttpClient client = clientBuilder.build();
initGitLab(account, client);
initGitLabRss(account, client);
//This is kinda weird, but basically, I don't want to see all the annoying logs from bitmap
//decoding since the Okhttpclient is going to log everything, but it does not matter in release
//builds, and will actually speed up the init time to share the same client between all these
if (BuildConfig.DEBUG) {
initPicasso(OkHttpClientFactory.create(account).build());
} else {
initPicasso(client);
}
}
public Prefs getPrefs() {
return prefs;
}
private void initGitLab(Account account, OkHttpClient client) {
gitLab = GitLabFactory.create(account, client);
}
private void initGitLabRss(Account account, OkHttpClient client) {
gitLabRss = GitLabRssFactory.create(account, client);
}
private void initPicasso(OkHttpClient client) {
picasso = PicassoFactory.createPicasso(client);
}
}
package com.commit451.gitlab
import android.app.Application
import android.content.Context
import android.content.res.Resources
import android.support.annotation.VisibleForTesting
import android.support.multidex.MultiDex
import com.commit451.gitlab.api.*
import com.commit451.gitlab.data.Prefs
import com.commit451.gitlab.model.Account
import com.commit451.gitlab.util.FabricUtil
import com.commit451.lift.Lift
import com.novoda.simplechromecustomtabs.SimpleChromeCustomTabs
import com.squareup.leakcanary.LeakCanary
import com.squareup.picasso.Picasso
import net.danlew.android.joda.JodaTimeAndroid
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import org.greenrobot.eventbus.EventBus
import timber.log.Timber
import java.util.*
/**
* App for one time init things and to house singletons
*/
open class App : Application() {
companion object {
var bus: EventBus = EventBus.getDefault()
lateinit private var instance: App
fun bus(): EventBus {
return bus
}
fun get(): App {
return instance
}
}
lateinit var currentAccount: Account
lateinit var gitLab: GitLabService
lateinit var gitLabRss: GitLabRss
override fun onCreate() {
super.onCreate()
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return
}
setupLeakCanary()
instance = this
GitLab.init()
Prefs.init(this)
//So that we don't get weird half translations
forceLocale(Locale.ENGLISH)
setupCrashReporting()
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
JodaTimeAndroid.init(this)
SimpleChromeCustomTabs.initialize(this)
val accounts = Account.getAccounts()
if (!accounts.isEmpty()) {
setAccount(accounts[0])
}
Lift.track(this)
}
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
setupMultidex()
}
fun setAccount(account: Account) {
currentAccount = account
//This is kinda weird, but basically, I don't want to see all the annoying logs from bitmap
//decoding since the OkHttpClient is going to log everything, but it does not matter in release
//builds, and will actually speed up the init time to share the same client between all these
val clientBuilder = OkHttpClientFactory.create(account)
if (BuildConfig.DEBUG) {
clientBuilder.addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
}
val client = clientBuilder.build()
initGitLab(account, client)
initGitLabRss(account, client)
if (BuildConfig.DEBUG) {
initPicasso(OkHttpClientFactory.create(account).build())
} else {
initPicasso(client)
}
}
fun getAccount(): Account {
return currentAccount
}
@VisibleForTesting
protected open fun setupMultidex() {
MultiDex.install(this)
}
@VisibleForTesting
protected open fun setupCrashReporting() {
FabricUtil.init(this)
}
@VisibleForTesting
protected open fun setupLeakCanary() {
LeakCanary.install(this)
}
private fun forceLocale(locale: Locale) {
try {
Locale.setDefault(locale)
val resources = arrayOf(Resources.getSystem(), baseContext.resources)
for (res in resources) {
val configuration = res.configuration
configuration.locale = locale
res.updateConfiguration(configuration, res.displayMetrics)
}
} catch (e: Exception) {
Timber.e(e)
}
}
private fun initGitLab(account: Account, client: OkHttpClient) {
gitLab = GitLabFactory.create(account, client)
}
private fun initGitLabRss(account: Account, client: OkHttpClient) {
gitLabRss = GitLabRssFactory.create(account, client)
}
private fun initPicasso(client: OkHttpClient) {
Picasso.setSingletonInstance(PicassoFactory.createPicasso(client))
}
}
package com.commit451.gitlab.activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.commit451.gimbal.Gimbal;
import com.commit451.gitlab.App;
import com.commit451.gitlab.BuildConfig;
import com.commit451.gitlab.R;
import com.commit451.gitlab.model.api.Contributor;
import com.commit451.gitlab.navigation.Navigator;
import com.commit451.gitlab.rx.CustomSingleObserver;
import com.commit451.gitlab.util.ImageUtil;
import com.commit451.gitlab.util.IntentUtil;
import com.commit451.gitlab.view.PhysicsFlowLayout;
import com.jawnnypoo.physicslayout.Physics;
import com.jawnnypoo.physicslayout.PhysicsConfig;
import com.wefika.flowlayout.FlowLayout;
import org.jbox2d.common.Vec2;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import de.hdodenhof.circleimageview.CircleImageView;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
/**
* Thats what its all about
*/
public class AboutActivity extends BaseActivity {
private static final String REPO_ID = "473568";
public static Intent newIntent(Context context) {
Intent intent = new Intent(context, AboutActivity.class);
return intent;
}
@BindView(R.id.root)
ViewGroup root;
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.contributors)
TextView textContributors;
@BindView(R.id.physics_layout)
PhysicsFlowLayout physicsLayout;
@BindView(R.id.progress)
View progress;
SensorManager sensorManager;
Sensor gravitySensor;
Gimbal gimbal;
@OnClick(R.id.sauce)
void onSauceClick() {
if (getString(R.string.url_gitlab).equals(App.get().getAccount().getServerUrl().toString())) {
Navigator.navigateToProject(AboutActivity.this, REPO_ID);
} else {
IntentUtil.openPage(AboutActivity.this, getString(R.string.source_url));
}
}
private final SensorEventListener sensorEventListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_GRAVITY) {
if (physicsLayout.getPhysics().getWorld() != null) {
gimbal.normalizeGravityEvent(event);
physicsLayout.getPhysics().getWorld().setGravity(new Vec2(-event.values[0], event.values[1]));
}
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
gimbal = new Gimbal(this);
gimbal.lock();
setContentView(R.layout.activity_about);
ButterKnife.bind(this);
toolbar.setNavigationIcon(R.drawable.ic_back_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
toolbar.setTitle(R.string.about);
toolbar.setSubtitle(BuildConfig.VERSION_NAME);
physicsLayout.getPhysics().enableFling();
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
gravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
App.get().getGitLab().getContributors(REPO_ID)
.compose(this.<List<Contributor>>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomSingleObserver<List<Contributor>>() {
@Override
public void error(@NonNull Throwable t) {
Timber.e(t);
progress.setVisibility(View.GONE);
Snackbar.make(root, R.string.failed_to_load_contributors, Snackbar.LENGTH_SHORT)
.show();
}
@Override
public void success(@NonNull List<Contributor> contributors) {
progress.setVisibility(View.GONE);
addContributors(Contributor.groupContributors(contributors));
}
});
progress.setVisibility(View.VISIBLE);
}
@Override
protected void onResume() {
super.onResume();
sensorManager.registerListener(sensorEventListener, gravitySensor, SensorManager.SENSOR_DELAY_GAME);
}
@Override
protected void onPause() {
super.onPause();
sensorManager.unregisterListener(sensorEventListener);
}
private void addContributors(List<Contributor> contributors) {
PhysicsConfig config = PhysicsConfig.create();
config.shapeType = PhysicsConfig.SHAPE_TYPE_CIRCLE;
int borderSize = getResources().getDimensionPixelSize(R.dimen.border_size);
int imageSize = getResources().getDimensionPixelSize(R.dimen.circle_size);
for (int i = 0; i < contributors.size(); i++) {
Contributor contributor = contributors.get(i);
CircleImageView imageView = new CircleImageView(this);
FlowLayout.LayoutParams llp = new FlowLayout.LayoutParams(
imageSize,
imageSize);
imageView.setLayoutParams(llp);
imageView.setBorderWidth(borderSize);
imageView.setBorderColor(Color.BLACK);
Physics.setPhysicsConfig(imageView, config);
physicsLayout.addView(imageView);
Uri url = ImageUtil.getAvatarUrl(contributor.getEmail(), imageSize);
App.get().getPicasso()
.load(url)
.into(imageView);
}
physicsLayout.requestLayout();
}
}
package com.commit451.gitlab.activity
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v7.widget.Toolbar
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import butterknife.BindView
import butterknife.ButterKnife
import butterknife.OnClick
import com.commit451.gimbal.Gimbal
import com.commit451.gitlab.App
import com.commit451.gitlab.BuildConfig
import com.commit451.gitlab.R
import com.commit451.gitlab.extension.setup
import com.commit451.gitlab.model.api.Contributor
import com.commit451.gitlab.navigation.Navigator
import com.commit451.gitlab.rx.CustomSingleObserver
import com.commit451.gitlab.util.ImageUtil
import com.commit451.gitlab.util.IntentUtil
import com.commit451.gitlab.view.PhysicsFlowLayout
import com.jawnnypoo.physicslayout.Physics
import com.jawnnypoo.physicslayout.PhysicsConfig
import com.squareup.picasso.Picasso
import com.wefika.flowlayout.FlowLayout
import de.hdodenhof.circleimageview.CircleImageView
import org.jbox2d.common.Vec2
import timber.log.Timber
/**
* Thats what its all about
*/
class AboutActivity : BaseActivity() {
companion object {
private val REPO_ID = "473568"
fun newIntent(context: Context): Intent {
val intent = Intent(context, AboutActivity::class.java)
return intent
}
}
@BindView(R.id.root) lateinit var root: ViewGroup
@BindView(R.id.toolbar) lateinit var toolbar: Toolbar
@BindView(R.id.contributors) lateinit var textContributors: TextView
@BindView(R.id.physics_layout) lateinit var physicsLayout: PhysicsFlowLayout
@BindView(R.id.progress) lateinit var progress: View
lateinit var sensorManager: SensorManager
lateinit var gimbal: Gimbal
var gravitySensor: Sensor? = null
val sensorEventListener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent) {
if (event.sensor.type == Sensor.TYPE_GRAVITY) {
if (physicsLayout.physics.world != null) {
gimbal.normalizeGravityEvent(event)
physicsLayout.physics.world.gravity = Vec2(-event.values[0], event.values[1])
}
}
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
}
@OnClick(R.id.sauce)
fun onSauceClick() {
if (getString(R.string.url_gitlab) == App.get().getAccount().serverUrl.toString()) {
Navigator.navigateToProject(this@AboutActivity, REPO_ID)
} else {
IntentUtil.openPage(this@AboutActivity, getString(R.string.source_url))
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
gimbal = Gimbal(this)
gimbal.lock()
setContentView(R.layout.activity_about)
ButterKnife.bind(this)
toolbar.setNavigationIcon(R.drawable.ic_back_24dp)
toolbar.setNavigationOnClickListener { onBackPressed() }
toolbar.setTitle(R.string.about)
toolbar.subtitle = BuildConfig.VERSION_NAME
physicsLayout.physics.enableFling()
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
gravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)
App.get().gitLab.getContributors(REPO_ID)
.setup(bindToLifecycle())
.subscribe(object : CustomSingleObserver<List<Contributor>>() {
override fun error(t: Throwable) {
Timber.e(t)
progress.visibility = View.GONE
Snackbar.make(root, R.string.failed_to_load_contributors, Snackbar.LENGTH_SHORT)
.show()
}
override fun success(contributors: List<Contributor>) {
progress.visibility = View.GONE
addContributors(Contributor.groupContributors(contributors))
}
})
progress.visibility = View.VISIBLE
}
override fun onResume() {
super.onResume()
sensorManager.registerListener(sensorEventListener, gravitySensor, SensorManager.SENSOR_DELAY_GAME)
}
override fun onPause() {
super.onPause()
sensorManager.unregisterListener(sensorEventListener)
}
override fun hasBrowsableLinks(): Boolean {
return true
}
fun addContributors(contributors: List<Contributor>) {
val config = PhysicsConfig.create()
config.shapeType = PhysicsConfig.SHAPE_TYPE_CIRCLE
val borderSize = resources.getDimensionPixelSize(R.dimen.border_size)
val imageSize = resources.getDimensionPixelSize(R.dimen.circle_size)
for (i in contributors.indices) {
val contributor = contributors[i]
val imageView = CircleImageView(this)
val llp = FlowLayout.LayoutParams(
imageSize,
imageSize)
imageView.layoutParams = llp
imageView.borderWidth = borderSize
imageView.borderColor = Color.BLACK
Physics.setPhysicsConfig(imageView, config)
physicsLayout.addView(imageView)
val url = ImageUtil.getAvatarUrl(contributor.email, imageSize)
Picasso.with(this)
.load(url)
.into(imageView)
}
physicsLayout.requestLayout()
}
}
package com.commit451.gitlab.activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.widget.Toolbar;
import android.view.View;
import com.commit451.gitlab.App;
import com.commit451.gitlab.R;
import com.commit451.gitlab.data.Prefs;
import com.commit451.gitlab.event.CloseDrawerEvent;
import com.commit451.gitlab.fragment.FeedFragment;
import org.greenrobot.eventbus.Subscribe;
import butterknife.BindView;
import butterknife.ButterKnife;
import timber.log.Timber;
/**
* Displays the current users projects feed
*/
public class ActivityActivity extends BaseActivity {
private static final String TAG_FEED_FRAGMENT = "feed_fragment";
public static Intent newIntent(Context context) {
Intent intent = new Intent(context, ActivityActivity.class);
return intent;
}
@BindView(R.id.drawer_layout)
DrawerLayout drawerLayout;
@BindView(R.id.toolbar)
Toolbar toolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
App.get().getPrefs().setStartingView(Prefs.STARTING_VIEW_ACTIVITY);
setContentView(R.layout.activity_activity);
ButterKnife.bind(this);
App.bus().register(this);
toolbar.setTitle(R.string.nav_activity);
toolbar.setNavigationIcon(R.drawable.ic_menu_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
drawerLayout.openDrawer(GravityCompat.START);
}
});
FeedFragment feedFragment = (FeedFragment) getSupportFragmentManager().findFragmentByTag(TAG_FEED_FRAGMENT);
if (feedFragment == null) {
Uri feedUri = App.get().getAccount().getServerUrl();
feedUri = feedUri.buildUpon()
.appendPath("dashboard")
.appendPath("projects.atom")
.build();
Timber.d("Showing activity feed for: %s", feedUri.toString());
feedFragment = FeedFragment.newInstance(feedUri);
getSupportFragmentManager().beginTransaction()
.replace(R.id.root_fragment, feedFragment, TAG_FEED_FRAGMENT)
.commit();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
App.bus().unregister(this);
}
@Subscribe
public void onCloseDrawerEvent(CloseDrawerEvent event) {
drawerLayout.closeDrawers();
}
}
package com.commit451.gitlab.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.support.v4.view.GravityCompat
import android.support.v4.widget.DrawerLayout
import android.support.v7.widget.Toolbar
import butterknife.BindView
import butterknife.ButterKnife
import com.commit451.gitlab.App
import com.commit451.gitlab.R
import com.commit451.gitlab.data.Prefs
import com.commit451.gitlab.event.CloseDrawerEvent
import com.commit451.gitlab.fragment.FeedFragment
import org.greenrobot.eventbus.Subscribe
import timber.log.Timber
/**
* Displays the current users projects feed
*/
class ActivityActivity : BaseActivity() {
companion object {
private val TAG_FEED_FRAGMENT = "feed_fragment"
fun newIntent(context: Context): Intent {
val intent = Intent(context, ActivityActivity::class.java)
return intent
}
}
@BindView(R.id.drawer_layout) lateinit var drawerLayout: DrawerLayout
@BindView(R.id.toolbar) lateinit var toolbar: Toolbar
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Prefs.startingView = Prefs.STARTING_VIEW_ACTIVITY
setContentView(R.layout.activity_activity)
ButterKnife.bind(this)
App.bus().register(this)
toolbar.setTitle(R.string.nav_activity)
toolbar.setNavigationIcon(R.drawable.ic_menu_24dp)
toolbar.setNavigationOnClickListener { drawerLayout.openDrawer(GravityCompat.START) }
var feedFragment: FeedFragment? = supportFragmentManager.findFragmentByTag(TAG_FEED_FRAGMENT) as? FeedFragment
if (feedFragment == null) {
var feedUri = App.get().getAccount().serverUrl
feedUri = feedUri.buildUpon()
.appendPath("dashboard")
.appendPath("projects.atom")
.build()
Timber.d("Showing activity feed for: %s", feedUri.toString())
feedFragment = FeedFragment.newInstance(feedUri)
supportFragmentManager.beginTransaction()
.replace(R.id.root_fragment, feedFragment, TAG_FEED_FRAGMENT)
.commit()
}
}
override fun hasBrowsableLinks(): Boolean {
return true
}
override fun onDestroy() {
super.onDestroy()
App.bus().unregister(this)
}
@Subscribe
fun onEvent(event: CloseDrawerEvent) {
drawerLayout.closeDrawers()
}
}
package com.commit451.gitlab.activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TextInputLayout;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.Spinner;
import android.widget.TextView;
import com.commit451.adapterflowlayout.AdapterFlowLayout;
import com.commit451.gitlab.App;
import com.commit451.gitlab.R;
import com.commit451.gitlab.adapter.AddIssueLabelAdapter;
import com.commit451.gitlab.adapter.AssigneeSpinnerAdapter;
import com.commit451.gitlab.adapter.MilestoneSpinnerAdapter;
import com.commit451.gitlab.event.IssueChangedEvent;
import com.commit451.gitlab.event.IssueCreatedEvent;
import com.commit451.gitlab.model.api.Issue;
import com.commit451.gitlab.model.api.Label;
import com.commit451.gitlab.model.api.Member;
import com.commit451.gitlab.model.api.Milestone;
import com.commit451.gitlab.model.api.Project;
import com.commit451.gitlab.navigation.Navigator;
import com.commit451.gitlab.rx.CustomResponseSingleObserver;
import com.commit451.gitlab.rx.CustomSingleObserver;
import com.commit451.teleprinter.Teleprinter;
import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import retrofit2.Response;
import timber.log.Timber;
/**
* Activity to input new issues, but not really a dialog at all wink wink
*/
public class AddIssueActivity extends MorphActivity {
private static final int REQUEST_LABEL = 1;
private static final String KEY_PROJECT = "project";
private static final String KEY_ISSUE = "issue";
public static Intent newIntent(Context context, Project project, Issue issue) {
Intent intent = new Intent(context, AddIssueActivity.class);
intent.putExtra(KEY_PROJECT, Parcels.wrap(project));
if (issue != null) {
intent.putExtra(KEY_ISSUE, Parcels.wrap(issue));
}
return intent;
}
@BindView(R.id.root)
FrameLayout root;
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.title_text_input_layout)
TextInputLayout textInputLayoutTitle;
@BindView(R.id.description)
EditText textDescription;
@BindView(R.id.progress)
View progress;
@BindView(R.id.assignee_progress)
View progressAssignee;
@BindView(R.id.assignee_spinner)
Spinner spinnerAssignee;
@BindView(R.id.milestone_progress)
View progressMilestone;
@BindView(R.id.milestone_spinner)
Spinner spinnerMilestone;
@BindView(R.id.label_label)
TextView textLabel;
@BindView(R.id.labels_progress)
View progressLabels;
@BindView(R.id.root_add_labels)
ViewGroup rootAddLabels;
@BindView(R.id.list_labels)
AdapterFlowLayout listLabels;
AddIssueLabelAdapter adapterLabels;
Teleprinter teleprinter;
Project project;
Issue issue;
HashSet<Member> members;
@OnClick(R.id.text_add_labels)
void onAddLabelClicked() {
Navigator.navigateToAddLabels(this, project, REQUEST_LABEL);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_issue);
ButterKnife.bind(this);
morph(root);
teleprinter = new Teleprinter(this);
project = Parcels.unwrap(getIntent().getParcelableExtra(KEY_PROJECT));
issue = Parcels.unwrap(getIntent().getParcelableExtra(KEY_ISSUE));
members = new HashSet<>();
adapterLabels = new AddIssueLabelAdapter(new AddIssueLabelAdapter.Listener() {
@Override
public void onLabelClicked(final Label label) {
new AlertDialog.Builder(AddIssueActivity.this)
.setTitle(R.string.remove)
.setMessage(R.string.are_you_sure_you_want_to_remove)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
adapterLabels.removeLabel(label);
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.show();
}
});
listLabels.setAdapter(adapterLabels);
toolbar.setNavigationIcon(R.drawable.ic_back_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_create:
case R.id.action_edit:
save();
return true;
}
return false;
}
});
if (issue != null) {
bindIssue();
toolbar.inflateMenu(R.menu.menu_edit_milestone);
} else {
toolbar.inflateMenu(R.menu.menu_add_milestone);
}
load();
}
private void load() {
App.get().getGitLab().getMilestones(project.getId(), getString(R.string.milestone_state_value_default))
.compose(this.<Response<List<Milestone>>>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomResponseSingleObserver<List<Milestone>>() {
@Override
public void error(@NonNull Throwable t) {
Timber.e(t);
progressMilestone.setVisibility(View.GONE);
spinnerMilestone.setVisibility(View.GONE);
}
@Override
public void responseSuccess(@NonNull List<Milestone> milestones) {
progressMilestone.setVisibility(View.GONE);
spinnerMilestone.setVisibility(View.VISIBLE);
MilestoneSpinnerAdapter milestoneSpinnerAdapter = new MilestoneSpinnerAdapter(AddIssueActivity.this, milestones);
spinnerMilestone.setAdapter(milestoneSpinnerAdapter);
if (issue != null) {
spinnerMilestone.setSelection(milestoneSpinnerAdapter.getSelectedItemPosition(issue.getMilestone()));
}
}
});
App.get().getGitLab().getProjectMembers(project.getId())
.compose(this.<Response<List<Member>>>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomResponseSingleObserver<List<Member>>() {
@Override
public void error(@NonNull Throwable t) {
Timber.e(t);
spinnerAssignee.setVisibility(View.GONE);
progressAssignee.setVisibility(View.GONE);
}
@Override
public void responseSuccess(@NonNull List<Member> members) {
AddIssueActivity.this.members.addAll(members);
if (project.belongsToGroup()) {
Timber.d("Project belongs to a group, loading those users too");
App.get().getGitLab().getGroupMembers(project.getNamespace().getId())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomResponseSingleObserver<List<Member>>() {
@Override
public void error(@NonNull Throwable t) {
Timber.e(t);
spinnerAssignee.setVisibility(View.GONE);
progressAssignee.setVisibility(View.GONE);
}
@Override
public void responseSuccess(@NonNull List<Member> members) {
AddIssueActivity.this.members.addAll(members);
setAssignees();
}
});
} else {
setAssignees();
}
}
});
App.get().getGitLab().getLabels(project.getId())
.compose(this.<List<Label>>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomSingleObserver<List<Label>>() {
@Override
public void error(@NonNull Throwable t) {
Timber.e(t);
listLabels.setVisibility(View.GONE);
progressLabels.setVisibility(View.GONE);
textLabel.setVisibility(View.GONE);
}
@Override
public void success(@NonNull List<Label> labels) {
progressLabels.setVisibility(View.GONE);
rootAddLabels.setVisibility(View.VISIBLE);
setLabels(labels);
}
});
}
private void showLoading() {
progress.setVisibility(View.VISIBLE);
progress.setAlpha(0.0f);
progress.animate().alpha(1.0f);
}
private void bindIssue() {
if (!TextUtils.isEmpty(issue.getTitle())) {
textInputLayoutTitle.getEditText().setText(issue.getTitle());
}
if (!TextUtils.isEmpty(issue.getDescription())) {
textDescription.setText(issue.getDescription());
}
}
private void setAssignees() {
progressAssignee.setVisibility(View.GONE);
spinnerAssignee.setVisibility(View.VISIBLE);
AssigneeSpinnerAdapter assigneeSpinnerAdapter = new AssigneeSpinnerAdapter(this, new ArrayList<>(members));
spinnerAssignee.setAdapter(assigneeSpinnerAdapter);
if (issue != null) {
spinnerAssignee.setSelection(assigneeSpinnerAdapter.getSelectedItemPosition(issue.getAssignee()));
}
}
private void setLabels(List<Label> projectLabels) {
if (projectLabels != null && !projectLabels.isEmpty() && issue != null && issue.getLabels() != null) {
ArrayList<Label> currentLabels = new ArrayList<>();
for (Label label : projectLabels) {
for (String labelName : issue.getLabels()) {
if (labelName.equals(label.getName())) {
currentLabels.add(label);
}
}
}
if (!currentLabels.isEmpty()) {
adapterLabels.setLabels(currentLabels);
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case REQUEST_LABEL:
if (resultCode == RESULT_OK) {
Label label = Parcels.unwrap(data.getParcelableExtra(AddLabelActivity.KEY_LABEL));
if (adapterLabels.containsLabel(label)) {
Snackbar.make(root, R.string.label_already_added, Snackbar.LENGTH_SHORT)
.show();
} else {
adapterLabels.addLabel(label);
}
}
break;
}
}
@Override
public void onBackPressed() {
new AlertDialog.Builder(this)
.setTitle(R.string.discard)
.setMessage(R.string.are_you_sure_you_want_to_discard)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dismiss();
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.show();
}
private void save() {
if (!TextUtils.isEmpty(textInputLayoutTitle.getEditText().getText())) {
teleprinter.hideKeyboard();
textInputLayoutTitle.setError(null);
showLoading();
Long assigneeId = null;
if (spinnerAssignee.getAdapter() != null) {
//the user did make a selection of some sort. So update it
Member member = (Member) spinnerAssignee.getSelectedItem();
if (member == null) {
//Removes the assignment
assigneeId = 0L;
} else {
assigneeId = member.getId();
}
}
Long milestoneId = null;
if (spinnerMilestone.getAdapter() != null) {
//the user did make a selection of some sort. So update it
Milestone milestone = (Milestone) spinnerMilestone.getSelectedItem();
if (milestone == null) {
//Removes the assignment
milestoneId = 0L;
} else {
milestoneId = milestone.getId();
}
}
String labelsCommaSeperated = adapterLabels.getCommaSeperatedStringOfLabels();
createOrSaveIssue(textInputLayoutTitle.getEditText().getText().toString(),
textDescription.getText().toString(),
assigneeId,
milestoneId,
labelsCommaSeperated);
} else {
textInputLayoutTitle.setError(getString(R.string.required_field));
}
}
private void createOrSaveIssue(String title, String description, @Nullable Long assigneeId,
@Nullable Long milestoneId, @Nullable String labels) {
if (issue == null) {
observeUpdate(App.get().getGitLab().createIssue(
project.getId(),
title,
description,
assigneeId,
milestoneId,
labels));
} else {
observeUpdate(App.get().getGitLab().updateIssue(project.getId(),
issue.getId(),
title,
description,
assigneeId,
milestoneId,
labels));
}
}
private void observeUpdate(Single<Issue> observable) {
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomSingleObserver<Issue>() {
@Override
public void error(@NonNull Throwable t) {
Timber.e(t);
Snackbar.make(root, getString(R.string.failed_to_create_issue), Snackbar.LENGTH_SHORT)
.show();
}
@Override
public void success(@NonNull Issue issue) {
if (AddIssueActivity.this.issue == null) {
App.bus().post(new IssueCreatedEvent(issue));
} else {
App.bus().post(new IssueChangedEvent(issue));
}
dismiss();
}
});
}
}
\ No newline at end of file
package com.commit451.gitlab.activity
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.support.design.widget.Snackbar
import android.support.design.widget.TextInputLayout
import android.support.v7.app.AlertDialog
import android.support.v7.widget.SwitchCompat
import android.support.v7.widget.Toolbar
import android.text.TextUtils
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.FrameLayout
import android.widget.Spinner
import android.widget.TextView
import butterknife.BindView
import butterknife.ButterKnife
import butterknife.OnClick
import com.commit451.adapterflowlayout.AdapterFlowLayout
import com.commit451.gitlab.App
import com.commit451.gitlab.R
import com.commit451.gitlab.adapter.AddIssueLabelAdapter
import com.commit451.gitlab.adapter.AssigneeSpinnerAdapter
import com.commit451.gitlab.adapter.MilestoneSpinnerAdapter
import com.commit451.gitlab.event.IssueChangedEvent
import com.commit451.gitlab.event.IssueCreatedEvent
import com.commit451.gitlab.extension.setup
import com.commit451.gitlab.model.api.*
import com.commit451.gitlab.navigation.Navigator
import com.commit451.gitlab.rx.CustomResponseSingleObserver
import com.commit451.gitlab.rx.CustomSingleObserver
import com.commit451.teleprinter.Teleprinter
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import org.parceler.Parcels
import retrofit2.Response
import timber.log.Timber
import java.util.*
/**
* Activity to input new issues, but not really a dialog at all wink wink
*/
class AddIssueActivity : MorphActivity() {
companion object {
private val REQUEST_LABEL = 1
private val KEY_PROJECT = "project"
private val KEY_ISSUE = "issue"
fun newIntent(context: Context, project: Project, issue: Issue?): Intent {
val intent = Intent(context, AddIssueActivity::class.java)
intent.putExtra(KEY_PROJECT, Parcels.wrap(project))
if (issue != null) {
intent.putExtra(KEY_ISSUE, Parcels.wrap(issue))
}
return intent
}
}
@BindView(R.id.root) lateinit var root: FrameLayout
@BindView(R.id.toolbar) lateinit var toolbar: Toolbar
@BindView(R.id.title_text_input_layout) lateinit var textInputLayoutTitle: TextInputLayout
@BindView(R.id.description) lateinit var textDescription: EditText
@BindView(R.id.progress) lateinit var progress: View
@BindView(R.id.assignee_progress) lateinit var progressAssignee: View
@BindView(R.id.assignee_spinner) lateinit var spinnerAssignee: Spinner
@BindView(R.id.milestone_progress) lateinit var progressMilestone: View
@BindView(R.id.milestone_spinner) lateinit var spinnerMilestone: Spinner
@BindView(R.id.label_label) lateinit var textLabel: TextView
@BindView(R.id.labels_progress) lateinit var progressLabels: View
@BindView(R.id.root_add_labels) lateinit var rootAddLabels: ViewGroup
@BindView(R.id.list_labels) lateinit var listLabels: AdapterFlowLayout
@BindView(R.id.confidential_switch) lateinit var switchConfidential: SwitchCompat
lateinit var adapterLabels: AddIssueLabelAdapter
lateinit var teleprinter: Teleprinter
lateinit var project: Project
var issue: Issue? = null
lateinit var members: HashSet<Member>
@OnClick(R.id.text_add_labels)
fun onAddLabelClicked() {
Navigator.navigateToAddLabels(this, project, REQUEST_LABEL)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_issue)
ButterKnife.bind(this)
morph(root)
teleprinter = Teleprinter(this)
project = Parcels.unwrap<Project>(intent.getParcelableExtra<Parcelable>(KEY_PROJECT))
issue = Parcels.unwrap<Issue>(intent.getParcelableExtra<Parcelable>(KEY_ISSUE))
members = HashSet<Member>()
adapterLabels = AddIssueLabelAdapter(object : AddIssueLabelAdapter.Listener {
override fun onLabelClicked(label: Label) {
AlertDialog.Builder(this@AddIssueActivity)
.setTitle(R.string.remove)
.setMessage(R.string.are_you_sure_you_want_to_remove)
.setPositiveButton(android.R.string.yes) { dialog, which -> adapterLabels.removeLabel(label) }
.setNegativeButton(android.R.string.no) { dialog, which -> dialog.dismiss() }
.show()
}
})
listLabels.adapter = adapterLabels
toolbar.setNavigationIcon(R.drawable.ic_back_24dp)
toolbar.setNavigationOnClickListener { onBackPressed() }
toolbar.setOnMenuItemClickListener(Toolbar.OnMenuItemClickListener { item ->
when (item.itemId) {
R.id.action_create, R.id.action_edit -> {
save()
return@OnMenuItemClickListener true
}
}
false
})
if (issue != null) {
bindIssue()
toolbar.inflateMenu(R.menu.edit)
} else {
toolbar.inflateMenu(R.menu.create)
}
load()
}
private fun load() {
App.get().gitLab.getMilestones(project.id, getString(R.string.milestone_state_value_default))
.setup(bindToLifecycle())
.subscribe(object : CustomResponseSingleObserver<List<Milestone>>() {
override fun error(t: Throwable) {
Timber.e(t)
progressMilestone.visibility = View.GONE
spinnerMilestone.visibility = View.GONE
}
override fun responseSuccess(milestones: List<Milestone>) {
progressMilestone.visibility = View.GONE
spinnerMilestone.visibility = View.VISIBLE
val maybeNullMilestones = mutableListOf<Milestone?>()
maybeNullMilestones.addAll(milestones)
val milestoneSpinnerAdapter = MilestoneSpinnerAdapter(this@AddIssueActivity, maybeNullMilestones)
spinnerMilestone.adapter = milestoneSpinnerAdapter
if (issue != null) {
spinnerMilestone.setSelection(milestoneSpinnerAdapter.getSelectedItemPosition(issue!!.milestone))
}
}
})
App.get().gitLab.getProjectMembers(project.id)
.compose(this.bindToLifecycle<Response<List<Member>>>())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : CustomResponseSingleObserver<List<Member>>() {
override fun error(t: Throwable) {
Timber.e(t)
spinnerAssignee.visibility = View.GONE
progressAssignee.visibility = View.GONE
}
override fun responseSuccess(members: List<Member>) {
this@AddIssueActivity.members.addAll(members)
if (project.belongsToGroup()) {
Timber.d("Project belongs to a group, loading those users too")
App.get().gitLab.getGroupMembers(project.namespace.id)
.setup(bindToLifecycle())
.subscribe(object : CustomResponseSingleObserver<List<Member>>() {
override fun error(t: Throwable) {
Timber.e(t)
spinnerAssignee.visibility = View.GONE
progressAssignee.visibility = View.GONE
}
override fun responseSuccess(members: List<Member>) {
this@AddIssueActivity.members.addAll(members)
setAssignees()
}
})
} else {
setAssignees()
}
}
})
App.get().gitLab.getLabels(project.id)
.setup(bindToLifecycle())
.subscribe(object : CustomSingleObserver<List<Label>>() {
override fun error(t: Throwable) {
Timber.e(t)
listLabels.visibility = View.GONE
progressLabels.visibility = View.GONE
textLabel.visibility = View.GONE
}
override fun success(labels: List<Label>) {
progressLabels.visibility = View.GONE
rootAddLabels.visibility = View.VISIBLE
setLabels(labels)
}
})
}
private fun showLoading() {
progress.visibility = View.VISIBLE
progress.alpha = 0.0f
progress.animate().alpha(1.0f)
}
private fun bindIssue() {
if (!TextUtils.isEmpty(issue!!.title)) {
textInputLayoutTitle.editText!!.setText(issue!!.title)
}
if (!TextUtils.isEmpty(issue!!.description)) {
textDescription.setText(issue!!.description)
}
switchConfidential.isChecked = issue!!.isConfidential
}
private fun setAssignees() {
progressAssignee.visibility = View.GONE
spinnerAssignee.visibility = View.VISIBLE
val assigneeSpinnerAdapter = AssigneeSpinnerAdapter(this, ArrayList(members))
spinnerAssignee.adapter = assigneeSpinnerAdapter
if (issue != null) {
spinnerAssignee.setSelection(assigneeSpinnerAdapter.getSelectedItemPosition(issue!!.assignee))
}
}
private fun setLabels(projectLabels: List<Label>?) {
if (projectLabels != null && !projectLabels.isEmpty() && issue != null && issue!!.labels != null) {
val currentLabels = ArrayList<Label>()
for (label in projectLabels) {
for (labelName in issue!!.labels) {
if (labelName == label.name) {
currentLabels.add(label)
}
}
}
if (!currentLabels.isEmpty()) {
adapterLabels.setLabels(currentLabels)
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_LABEL -> if (resultCode == Activity.RESULT_OK) {
val label = Parcels.unwrap<Label>(data?.getParcelableExtra<Parcelable>(AddLabelActivity.KEY_LABEL))
if (adapterLabels.containsLabel(label)) {
Snackbar.make(root, R.string.label_already_added, Snackbar.LENGTH_SHORT)
.show()
} else {
adapterLabels.addLabel(label)
}
}
}
}
override fun onBackPressed() {
AlertDialog.Builder(this)
.setTitle(R.string.discard)
.setMessage(R.string.are_you_sure_you_want_to_discard)
.setPositiveButton(android.R.string.yes) { dialog, which -> dismiss() }
.setNegativeButton(android.R.string.no) { dialog, which -> dialog.dismiss() }
.show()
}
private fun save() {
if (!TextUtils.isEmpty(textInputLayoutTitle.editText!!.text)) {
teleprinter.hideKeyboard()
textInputLayoutTitle.error = null
showLoading()
var assigneeId: Long? = null
if (spinnerAssignee.adapter != null) {
//the user did make a selection of some sort. So update it
val member = spinnerAssignee.selectedItem as? Member?
if (member == null) {
//Removes the assignment
assigneeId = 0L
} else {
assigneeId = member.id
}
}
var milestoneId: Long? = null
if (spinnerMilestone.adapter != null) {
//the user did make a selection of some sort. So update it
val milestone = spinnerMilestone.selectedItem as? Milestone?
if (milestone == null) {
//Removes the assignment
milestoneId = 0L
} else {
milestoneId = milestone.id
}
}
val labelsCommaSeperated = adapterLabels.getCommaSeperatedStringOfLabels()
createOrSaveIssue(textInputLayoutTitle.editText!!.text.toString(),
textDescription.text.toString(),
assigneeId,
milestoneId,
labelsCommaSeperated,
switchConfidential.isChecked)
} else {
textInputLayoutTitle.error = getString(R.string.required_field)
}
}
private fun createOrSaveIssue(title: String, description: String, assigneeId: Long?,
milestoneId: Long?, labels: String?, isConfidential: Boolean) {
if (issue == null) {
observeUpdate(App.get().gitLab.createIssue(
project.id,
title,
description,
assigneeId,
milestoneId,
labels,
isConfidential))
} else {
observeUpdate(App.get().gitLab.updateIssue(project.id,
issue!!.id,
title,
description,
assigneeId,
milestoneId,
labels,
isConfidential))
}
}
private fun observeUpdate(observable: Single<Issue>) {
observable.setup(bindToLifecycle())
.subscribe(object : CustomSingleObserver<Issue>() {
override fun error(t: Throwable) {
Timber.e(t)
Snackbar.make(root, getString(R.string.failed_to_create_issue), Snackbar.LENGTH_SHORT)
.show()
}
override fun success(issue: Issue) {
if (this@AddIssueActivity.issue == null) {
App.bus().post(IssueCreatedEvent(issue))
} else {
App.bus().post(IssueChangedEvent(issue))
}
dismiss()
}
})
}
}
\ No newline at end of file
package com.commit451.gitlab.activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.commit451.gitlab.App;
import com.commit451.gitlab.R;
import com.commit451.gitlab.adapter.LabelAdapter;
import com.commit451.gitlab.model.api.Label;
import com.commit451.gitlab.navigation.Navigator;
import com.commit451.gitlab.rx.CustomSingleObserver;
import com.commit451.gitlab.viewHolder.LabelViewHolder;
import org.parceler.Parcels;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
import static com.commit451.gitlab.R.string.labels;
/**
* Add labels!
*/
public class AddLabelActivity extends BaseActivity {
private static final String KEY_PROJECT_ID = "project_id";
private static final int REQUEST_NEW_LABEL = 1;
public static final String KEY_LABEL = "label";
public static Intent newIntent(Context context, long projectId) {
Intent intent = new Intent(context, AddLabelActivity.class);
intent.putExtra(KEY_PROJECT_ID, projectId);
return intent;
}
@BindView(R.id.root)
ViewGroup root;
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.swipe_layout)
SwipeRefreshLayout swipeRefreshLayout;
@BindView(R.id.list)
RecyclerView list;
LabelAdapter adapterLabel;
@BindView(R.id.message_text)
TextView textMessage;
long projectId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_label);
ButterKnife.bind(this);
projectId = getIntent().getLongExtra(KEY_PROJECT_ID, -1);
toolbar.setTitle(labels);
toolbar.inflateMenu(R.menu.menu_add_label);
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_add_label:
Navigator.navigateToAddNewLabel(AddLabelActivity.this, projectId, REQUEST_NEW_LABEL);
return true;
}
return false;
}
});
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
load();
}
});
adapterLabel = new LabelAdapter(new LabelAdapter.Listener() {
@Override
public void onLabelClicked(Label label, LabelViewHolder viewHolder) {
Intent data = new Intent();
data.putExtra(KEY_LABEL, Parcels.wrap(label));
setResult(RESULT_OK, data);
finish();
}
});
list.setAdapter(adapterLabel);
list.setLayoutManager(new LinearLayoutManager(this));
toolbar.setNavigationIcon(R.drawable.ic_back_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
load();
}
private void load() {
textMessage.setVisibility(View.GONE);
swipeRefreshLayout.post(new Runnable() {
@Override
public void run() {
if (swipeRefreshLayout != null) {
swipeRefreshLayout.setRefreshing(true);
}
}
});
App.get().getGitLab().getLabels(projectId)
.compose(this.<List<Label>>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomSingleObserver<List<Label>>() {
@Override
public void error(@NonNull Throwable t) {
Timber.e(t);
swipeRefreshLayout.setRefreshing(false);
textMessage.setVisibility(View.VISIBLE);
}
@Override
public void success(@NonNull List<Label> labels) {
swipeRefreshLayout.setRefreshing(false);
if (labels.isEmpty()) {
textMessage.setVisibility(View.VISIBLE);
}
adapterLabel.setItems(labels);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case REQUEST_NEW_LABEL:
if (resultCode == RESULT_OK) {
Label newLabel = Parcels.unwrap(data.getParcelableExtra(AddNewLabelActivity.KEY_NEW_LABEL));
adapterLabel.addLabel(newLabel);
}
break;
}
}
}
package com.commit451.gitlab.activity
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.support.v4.widget.SwipeRefreshLayout
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.Toolbar
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import butterknife.BindView
import butterknife.ButterKnife
import com.commit451.gitlab.App
import com.commit451.gitlab.R
import com.commit451.gitlab.R.string.labels
import com.commit451.gitlab.adapter.LabelAdapter
import com.commit451.gitlab.extension.setup
import com.commit451.gitlab.model.api.Label
import com.commit451.gitlab.navigation.Navigator
import com.commit451.gitlab.rx.CustomSingleObserver
import com.commit451.gitlab.viewHolder.LabelViewHolder
import org.parceler.Parcels
import timber.log.Timber
/**
* Add labels!
*/
class AddLabelActivity : BaseActivity() {
companion object {
private val KEY_PROJECT_ID = "project_id"
private val REQUEST_NEW_LABEL = 1
val KEY_LABEL = "label"
fun newIntent(context: Context, projectId: Long): Intent {
val intent = Intent(context, AddLabelActivity::class.java)
intent.putExtra(KEY_PROJECT_ID, projectId)
return intent
}
}
@BindView(R.id.root) lateinit var root: ViewGroup
@BindView(R.id.toolbar) lateinit var toolbar: Toolbar
@BindView(R.id.swipe_layout) lateinit var swipeRefreshLayout: SwipeRefreshLayout
@BindView(R.id.list) lateinit var list: RecyclerView
@BindView(R.id.message_text) lateinit var textMessage: TextView
lateinit var adapterLabel: LabelAdapter
var projectId: Long = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_label)
ButterKnife.bind(this)
projectId = intent.getLongExtra(KEY_PROJECT_ID, -1)
toolbar.setTitle(labels)
toolbar.inflateMenu(R.menu.create)
toolbar.setOnMenuItemClickListener(Toolbar.OnMenuItemClickListener { item ->
when (item.itemId) {
R.id.action_create -> {
Navigator.navigateToAddNewLabel(this@AddLabelActivity, projectId, REQUEST_NEW_LABEL)
return@OnMenuItemClickListener true
}
}
false
})
swipeRefreshLayout.setOnRefreshListener { load() }
adapterLabel = LabelAdapter(object : LabelAdapter.Listener {
override fun onLabelClicked(label: Label, viewHolder: LabelViewHolder) {
val data = Intent()
data.putExtra(KEY_LABEL, Parcels.wrap(label))
setResult(Activity.RESULT_OK, data)
finish()
}
})
list.adapter = adapterLabel
list.layoutManager = LinearLayoutManager(this)
toolbar.setNavigationIcon(R.drawable.ic_back_24dp)
toolbar.setNavigationOnClickListener { onBackPressed() }
load()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_NEW_LABEL -> if (resultCode == Activity.RESULT_OK) {
val newLabel = Parcels.unwrap<Label>(data?.getParcelableExtra<Parcelable>(AddNewLabelActivity.KEY_NEW_LABEL))
adapterLabel.addLabel(newLabel)
}
}
}
fun load() {
textMessage.visibility = View.GONE
swipeRefreshLayout.isRefreshing = true
App.get().gitLab.getLabels(projectId)
.setup(bindToLifecycle())
.subscribe(object : CustomSingleObserver<List<Label>>() {
override fun error(t: Throwable) {
Timber.e(t)
swipeRefreshLayout.isRefreshing = false
textMessage.visibility = View.VISIBLE
}
override fun success(labels: List<Label>) {
swipeRefreshLayout.isRefreshing = false
if (labels.isEmpty()) {
textMessage.visibility = View.VISIBLE
}
adapterLabel.setItems(labels)
}
})
}
}
package com.commit451.gitlab.activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TextInputLayout;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.FrameLayout;
import com.commit451.easel.Easel;
import com.commit451.gitlab.App;
import com.commit451.gitlab.R;
import com.commit451.gitlab.event.MilestoneChangedEvent;
import com.commit451.gitlab.event.MilestoneCreatedEvent;
import com.commit451.gitlab.model.api.Milestone;
import com.commit451.gitlab.rx.CustomSingleObserver;
import com.commit451.teleprinter.Teleprinter;
import com.wdullaer.materialdatetimepicker.date.DatePickerDialog;
import org.parceler.Parcels;
import java.util.Calendar;
import java.util.Date;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
public class AddMilestoneActivity extends MorphActivity {
private static final String KEY_PROJECT_ID = "project_id";
private static final String KEY_MILESTONE = "milestone";
public static Intent newIntent(Context context, long projectId) {
return newIntent(context, projectId, null);
}
public static Intent newIntent(Context context, long projectId, Milestone milestone) {
Intent intent = new Intent(context, AddMilestoneActivity.class);
intent.putExtra(KEY_PROJECT_ID, projectId);
if (milestone != null) {
intent.putExtra(KEY_MILESTONE, Parcels.wrap(milestone));
}
return intent;
}
@BindView(R.id.root)
FrameLayout root;
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.title_text_input_layout)
TextInputLayout textInputLayoutTitle;
@BindView(R.id.title)
EditText textTitle;
@BindView(R.id.description)
EditText textDescription;
@BindView(R.id.due_date)
Button buttonDueDate;
@BindView(R.id.progress)
View progress;
Teleprinter teleprinter;
long projectId;
Milestone milestone;
Date currentDate;
private final DatePickerDialog.OnDateSetListener onDateSetListener = new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePickerDialog view, int year, int monthOfYear, int dayOfMonth) {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, year);
calendar.set(Calendar.MONTH, monthOfYear);
calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
currentDate = calendar.getTime();
bind(currentDate);
}
};
@OnClick(R.id.due_date)
void onDueDateClicked() {
Calendar now = Calendar.getInstance();
if (currentDate != null) {
now.setTime(currentDate);
}
DatePickerDialog dpd = DatePickerDialog.newInstance(
onDateSetListener,
now.get(Calendar.YEAR),
now.get(Calendar.MONTH),
now.get(Calendar.DAY_OF_MONTH)
);
dpd.setAccentColor(Easel.getThemeAttrColor(this, R.attr.colorAccent));
dpd.show(getFragmentManager(), "date_picker");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_milestone);
ButterKnife.bind(this);
morph(root);
teleprinter = new Teleprinter(this);
projectId = getIntent().getLongExtra(KEY_PROJECT_ID, -1);
milestone = Parcels.unwrap(getIntent().getParcelableExtra(KEY_MILESTONE));
if (milestone != null) {
bind(milestone);
toolbar.inflateMenu(R.menu.menu_edit_milestone);
} else {
toolbar.inflateMenu(R.menu.menu_add_milestone);
}
toolbar.setNavigationIcon(R.drawable.ic_back_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_create:
case R.id.action_edit:
createMilestone();
return true;
}
return false;
}
});
}
private void createMilestone() {
teleprinter.hideKeyboard();
if (TextUtils.isEmpty(textTitle.getText())) {
textInputLayoutTitle.setError(getString(R.string.required_field));
return;
}
progress.setVisibility(View.VISIBLE);
String dueDate = null;
if (currentDate != null) {
dueDate = Milestone.DUE_DATE_FORMAT.format(currentDate);
}
if (milestone == null) {
createOrEditMilestone(App.get().getGitLab().createMilestone(projectId,
textTitle.getText().toString(),
textDescription.getText().toString(),
dueDate));
} else {
createOrEditMilestone(App.get().getGitLab().editMilestone(projectId,
milestone.getId(),
textTitle.getText().toString(),
textDescription.getText().toString(),
dueDate));
}
}
private void createOrEditMilestone(Single<Milestone> observable) {
observable.subscribeOn(Schedulers.io())
.compose(this.<Milestone>bindToLifecycle())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomSingleObserver<Milestone>() {
@Override
public void error(@NonNull Throwable t) {
Timber.e(t);
progress.setVisibility(View.GONE);
showError();
}
@Override
public void success(@NonNull Milestone milestone) {
progress.setVisibility(View.GONE);
if (AddMilestoneActivity.this.milestone == null) {
App.bus().post(new MilestoneCreatedEvent(milestone));
} else {
App.bus().post(new MilestoneChangedEvent(milestone));
}
finish();
}
});
}
private void showError() {
Snackbar.make(root, getString(R.string.failed_to_create_milestone), Snackbar.LENGTH_SHORT)
.show();
}
private void bind(Date date) {
buttonDueDate.setText(Milestone.DUE_DATE_FORMAT.format(date));
}
private void bind(Milestone milestone) {
textTitle.setText(milestone.getTitle());
if (milestone.getDescription() != null) {
textDescription.setText(milestone.getDescription());
}
if (milestone.getDueDate() != null) {
currentDate = milestone.getDueDate();
bind(currentDate);
}
}
}
package com.commit451.gitlab.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.support.design.widget.Snackbar
import android.support.design.widget.TextInputLayout
import android.support.v7.widget.Toolbar
import android.text.TextUtils
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.FrameLayout
import butterknife.BindView
import butterknife.ButterKnife
import butterknife.OnClick
import com.commit451.easel.Easel
import com.commit451.gitlab.App
import com.commit451.gitlab.R
import com.commit451.gitlab.event.MilestoneChangedEvent
import com.commit451.gitlab.event.MilestoneCreatedEvent
import com.commit451.gitlab.extension.setup
import com.commit451.gitlab.model.api.Milestone
import com.commit451.gitlab.rx.CustomSingleObserver
import com.commit451.teleprinter.Teleprinter
import com.wdullaer.materialdatetimepicker.date.DatePickerDialog
import io.reactivex.Single
import org.parceler.Parcels
import timber.log.Timber
import java.util.*
class AddMilestoneActivity : MorphActivity() {
companion object {
private val KEY_PROJECT_ID = "project_id"
private val KEY_MILESTONE = "milestone"
@JvmOverloads fun newIntent(context: Context, projectId: Long, milestone: Milestone? = null): Intent {
val intent = Intent(context, AddMilestoneActivity::class.java)
intent.putExtra(KEY_PROJECT_ID, projectId)
if (milestone != null) {
intent.putExtra(KEY_MILESTONE, Parcels.wrap(milestone))
}
return intent
}
}
@BindView(R.id.root) lateinit var root: FrameLayout
@BindView(R.id.toolbar) lateinit var toolbar: Toolbar
@BindView(R.id.title_text_input_layout) lateinit var textInputLayoutTitle: TextInputLayout
@BindView(R.id.title) lateinit var textTitle: EditText
@BindView(R.id.description) lateinit var textDescription: EditText
@BindView(R.id.due_date) lateinit var buttonDueDate: Button
@BindView(R.id.progress) lateinit var progress: View
lateinit var teleprinter: Teleprinter
var projectId: Long = 0
var milestone: Milestone? = null
var currentDate: Date? = null
val onDateSetListener = DatePickerDialog.OnDateSetListener { view, year, monthOfYear, dayOfMonth ->
val calendar = Calendar.getInstance()
calendar.set(Calendar.YEAR, year)
calendar.set(Calendar.MONTH, monthOfYear)
calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth)
currentDate = calendar.time
bind(currentDate!!)
}
@OnClick(R.id.due_date)
fun onDueDateClicked() {
val now = Calendar.getInstance()
if (currentDate != null) {
now.time = currentDate
}
val dpd = DatePickerDialog.newInstance(
onDateSetListener,
now.get(Calendar.YEAR),
now.get(Calendar.MONTH),
now.get(Calendar.DAY_OF_MONTH)
)
dpd.accentColor = Easel.getThemeAttrColor(this, R.attr.colorAccent)
dpd.show(fragmentManager, "date_picker")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_milestone)
ButterKnife.bind(this)
morph(root)
teleprinter = Teleprinter(this)
projectId = intent.getLongExtra(KEY_PROJECT_ID, -1)
milestone = Parcels.unwrap<Milestone>(intent.getParcelableExtra<Parcelable>(KEY_MILESTONE))
if (milestone != null) {
bind(milestone!!)
toolbar.inflateMenu(R.menu.edit)
} else {
toolbar.inflateMenu(R.menu.create)
}
toolbar.setNavigationIcon(R.drawable.ic_back_24dp)
toolbar.setNavigationOnClickListener { onBackPressed() }
toolbar.setOnMenuItemClickListener(Toolbar.OnMenuItemClickListener { item ->
when (item.itemId) {
R.id.action_create, R.id.action_edit -> {
createMilestone()
return@OnMenuItemClickListener true
}
}
false
})
}
fun createMilestone() {
teleprinter.hideKeyboard()
if (TextUtils.isEmpty(textTitle.text)) {
textInputLayoutTitle.error = getString(R.string.required_field)
return
}
progress.visibility = View.VISIBLE
var dueDate: String? = null
if (currentDate != null) {
dueDate = Milestone.DUE_DATE_FORMAT.format(currentDate)
}
if (milestone == null) {
createOrEditMilestone(App.get().gitLab.createMilestone(projectId,
textTitle.text.toString(),
textDescription.text.toString(),
dueDate))
} else {
createOrEditMilestone(App.get().gitLab.editMilestone(projectId,
milestone!!.id,
textTitle.text.toString(),
textDescription.text.toString(),
dueDate))
}
}
fun createOrEditMilestone(observable: Single<Milestone>) {
observable.setup(bindToLifecycle())
.subscribe(object : CustomSingleObserver<Milestone>() {
override fun error(t: Throwable) {
Timber.e(t)
progress.visibility = View.GONE
showError()
}
override fun success(milestone: Milestone) {
progress.visibility = View.GONE
if (this@AddMilestoneActivity.milestone == null) {
App.bus().post(MilestoneCreatedEvent(milestone))
} else {
App.bus().post(MilestoneChangedEvent(milestone))
}
finish()
}
})
}
fun showError() {
Snackbar.make(root, getString(R.string.failed_to_create_milestone), Snackbar.LENGTH_SHORT)
.show()
}
fun bind(date: Date) {
buttonDueDate.text = Milestone.DUE_DATE_FORMAT.format(date)
}
fun bind(milestone: Milestone) {
textTitle.setText(milestone.title)
if (milestone.description != null) {
textDescription.setText(milestone.description)
}
if (milestone.dueDate != null) {
currentDate = milestone.dueDate
bind(currentDate!!)
}
}
}
package com.commit451.gitlab.activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TextInputLayout;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.afollestad.materialdialogs.color.ColorChooserDialog;
import com.commit451.gitlab.App;
import com.commit451.gitlab.R;
import com.commit451.gitlab.model.api.Label;
import com.commit451.gitlab.rx.CustomResponseSingleObserver;
import com.commit451.gitlab.util.ColorUtil;
import com.commit451.gitlab.util.Validator;
import com.jakewharton.retrofit2.adapter.rxjava2.HttpException;
import org.parceler.Parcels;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import retrofit2.Response;
import timber.log.Timber;
/**
* Create a brand new label
*/
public class AddNewLabelActivity extends BaseActivity implements ColorChooserDialog.ColorCallback {
private static final String KEY_PROJECT_ID = "project_id";
public static final String KEY_NEW_LABEL = "new_label";
public static Intent newIntent(Context context, long projectId) {
Intent intent = new Intent(context, AddNewLabelActivity.class);
intent.putExtra(KEY_PROJECT_ID, projectId);
return intent;
}
@BindView(R.id.root)
ViewGroup root;
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.title_text_input_layout)
TextInputLayout textInputLayoutTitle;
@BindView(R.id.description)
TextView textDescription;
@BindView(R.id.image_color)
ImageView imageColor;
@BindView(R.id.progress)
View progress;
int chosenColor = -1;
@OnClick(R.id.root_color)
void onChooseColorClicked() {
// Pass AppCompatActivity which implements ColorCallback, along with the textTitle of the dialog
new ColorChooserDialog.Builder(this, R.string.add_new_label_choose_color)
.preselect(chosenColor)
.show();
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_new_label);
ButterKnife.bind(this);
toolbar.setNavigationIcon(R.drawable.ic_back_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onBackPressed();
}
});
toolbar.inflateMenu(R.menu.menu_add_new_label);
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_create:
createLabel();
return true;
}
return false;
}
});
}
@Override
public void onColorSelection(@NonNull ColorChooserDialog dialog, @ColorInt int selectedColor) {
chosenColor = selectedColor;
imageColor.setImageDrawable(new ColorDrawable(selectedColor));
}
private long getProjectId() {
return getIntent().getLongExtra(KEY_PROJECT_ID, -1);
}
private void createLabel() {
if (Validator.validateFieldsNotEmpty(getString(R.string.required_field), textInputLayoutTitle)) {
if (chosenColor == -1) {
Snackbar.make(root, R.string.add_new_label_color_is_required, Snackbar.LENGTH_SHORT)
.show();
return;
}
String title = textInputLayoutTitle.getEditText().getText().toString();
String description = null;
if (!TextUtils.isEmpty(textDescription.getText())) {
description = textDescription.getText().toString();
}
String color = null;
if (chosenColor != -1) {
color = ColorUtil.convertColorIntToString(chosenColor);
Timber.d("Setting color to %s", color);
}
progress.setVisibility(View.VISIBLE);
progress.setAlpha(0.0f);
progress.animate().alpha(1.0f);
App.get().getGitLab().createLabel(getProjectId(), title, color, description)
.compose(this.<Response<Label>>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomResponseSingleObserver<Label>() {
@Override
public void error(@NonNull Throwable e) {
Timber.e(e);
progress.setVisibility(View.GONE);
if (e instanceof HttpException && ((HttpException) e).response().code() == 409) {
Snackbar.make(root, R.string.label_already_exists, Snackbar.LENGTH_SHORT)
.show();
} else {
Snackbar.make(root, R.string.failed_to_create_label, Snackbar.LENGTH_SHORT)
.show();
}
}
@Override
public void responseSuccess(@NonNull Label label) {
Intent data = new Intent();
data.putExtra(KEY_NEW_LABEL, Parcels.wrap(label));
setResult(RESULT_OK, data);
finish();
}
});
}
}
}
package com.commit451.gitlab.activity
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.support.annotation.ColorInt
import android.support.design.widget.Snackbar
import android.support.design.widget.TextInputLayout
import android.support.v7.widget.Toolbar
import android.text.TextUtils
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import butterknife.BindView
import butterknife.ButterKnife
import butterknife.OnClick
import com.afollestad.materialdialogs.color.ColorChooserDialog
import com.commit451.gitlab.App
import com.commit451.gitlab.R
import com.commit451.gitlab.extension.checkValid
import com.commit451.gitlab.model.api.Label
import com.commit451.gitlab.rx.CustomResponseSingleObserver
import com.commit451.gitlab.util.ColorUtil
import com.jakewharton.retrofit2.adapter.rxjava2.HttpException
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import org.parceler.Parcels
import retrofit2.Response
import timber.log.Timber
/**
* Create a brand new label
*/
class AddNewLabelActivity : BaseActivity(), ColorChooserDialog.ColorCallback {
companion object {
private val KEY_PROJECT_ID = "project_id"
val KEY_NEW_LABEL = "new_label"
fun newIntent(context: Context, projectId: Long): Intent {
val intent = Intent(context, AddNewLabelActivity::class.java)
intent.putExtra(KEY_PROJECT_ID, projectId)
return intent
}
}
@BindView(R.id.root) lateinit var root: ViewGroup
@BindView(R.id.toolbar) lateinit var toolbar: Toolbar
@BindView(R.id.title_text_input_layout) lateinit var textInputLayoutTitle: TextInputLayout
@BindView(R.id.description) lateinit var textDescription: TextView
@BindView(R.id.image_color) lateinit var imageColor: ImageView
@BindView(R.id.progress) lateinit var progress: View
var chosenColor = -1
@OnClick(R.id.root_color)
fun onChooseColorClicked() {
// Pass AppCompatActivity which implements ColorCallback, along with the textTitle of the dialog
ColorChooserDialog.Builder(this, R.string.add_new_label_choose_color)
.preselect(chosenColor)
.show()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_new_label)
ButterKnife.bind(this)
toolbar.setNavigationIcon(R.drawable.ic_back_24dp)
toolbar.setNavigationOnClickListener { onBackPressed() }
toolbar.inflateMenu(R.menu.create)
toolbar.setOnMenuItemClickListener(Toolbar.OnMenuItemClickListener { item ->
when (item.itemId) {
R.id.action_create -> {
createLabel()
return@OnMenuItemClickListener true
}
}
false
})
}
override fun onColorSelection(dialog: ColorChooserDialog, @ColorInt selectedColor: Int) {
chosenColor = selectedColor
imageColor.setImageDrawable(ColorDrawable(selectedColor))
}
override fun onColorChooserDismissed(dialog: ColorChooserDialog) {
}
private val projectId: Long
get() = intent.getLongExtra(KEY_PROJECT_ID, -1)
private fun createLabel() {
val valid = textInputLayoutTitle.checkValid()
if (valid) {
if (chosenColor == -1) {
Snackbar.make(root, R.string.add_new_label_color_is_required, Snackbar.LENGTH_SHORT)
.show()
return
}
val title = textInputLayoutTitle.editText!!.text.toString()
var description: String? = null
if (!TextUtils.isEmpty(textDescription.text)) {
description = textDescription.text.toString()
}
var color: String? = null
if (chosenColor != -1) {
color = ColorUtil.convertColorIntToString(chosenColor)
Timber.d("Setting color to %s", color)
}
progress.visibility = View.VISIBLE
progress.alpha = 0.0f
progress.animate().alpha(1.0f)
App.get().gitLab.createLabel(projectId, title, color, description)
.compose(this.bindToLifecycle<Response<Label>>())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : CustomResponseSingleObserver<Label>() {
override fun error(e: Throwable) {
Timber.e(e)
progress.visibility = View.GONE
if (e is HttpException && e.response().code() == 409) {
Snackbar.make(root, R.string.label_already_exists, Snackbar.LENGTH_SHORT)
.show()
} else {
Snackbar.make(root, R.string.failed_to_create_label, Snackbar.LENGTH_SHORT)
.show()
}
}
override fun responseSuccess(label: Label) {
val data = Intent()
data.putExtra(KEY_NEW_LABEL, Parcels.wrap(label))
setResult(Activity.RESULT_OK, data)
finish()
}
})
}
}
}
package com.commit451.gitlab.activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import com.commit451.alakazam.HideRunnable;
import com.commit451.gitlab.App;
import com.commit451.gitlab.R;
import com.commit451.gitlab.adapter.UserAdapter;
import com.commit451.gitlab.dialog.AccessDialog;
import com.commit451.gitlab.event.MemberAddedEvent;
import com.commit451.gitlab.model.api.Group;
import com.commit451.gitlab.model.api.Member;
import com.commit451.gitlab.model.api.UserBasic;
import com.commit451.gitlab.rx.CustomResponseSingleObserver;
import com.commit451.gitlab.util.LinkHeaderParser;
import com.commit451.gitlab.viewHolder.UserViewHolder;
import com.commit451.teleprinter.Teleprinter;
import com.jakewharton.retrofit2.adapter.rxjava2.HttpException;
import org.parceler.Parcels;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.OnEditorAction;
import butterknife.OnTextChanged;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import retrofit2.Response;
import timber.log.Timber;
/**
* Add a new user to the repo or to the group, depending on the mode
*/
public class AddUserActivity extends MorphActivity {
private static final String KEY_PROJECT_ID = "project_id";
private static final String KEY_GROUP = "group";
public static Intent newIntent(Context context, long projectId) {
Intent intent = new Intent(context, AddUserActivity.class);
intent.putExtra(KEY_PROJECT_ID, projectId);
return intent;
}
public static Intent newIntent(Context context, Group group) {
Intent intent = new Intent(context, AddUserActivity.class);
intent.putExtra(KEY_GROUP, Parcels.wrap(group));
return intent;
}
@BindView(R.id.root)
ViewGroup root;
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.search)
EditText textSearch;
@BindView(R.id.swipe_layout)
SwipeRefreshLayout swipeRefreshLayout;
@BindView(R.id.list)
RecyclerView list;
@BindView(R.id.clear)
View buttonClear;
GridLayoutManager layoutManager;
UserAdapter adapter;
AccessDialog dialogAccess;
UserBasic selectedUser;
Teleprinter teleprinter;
long projectId;
Group group;
String query;
Uri nextPageUrl;
boolean loading = false;
@OnClick(R.id.clear)
void onClearClick() {
buttonClear.animate().alpha(0.0f).withEndAction(new Runnable() {
@Override
public void run() {
buttonClear.setVisibility(View.GONE);
textSearch.getText().clear();
teleprinter.showKeyboard(textSearch);
}
});
}
@OnEditorAction(R.id.search)
boolean onEditorAction() {
if (!TextUtils.isEmpty(textSearch.getText())) {
query = textSearch.getText().toString();
loadData();
}
return true;
}
@OnTextChanged(R.id.search)
void onTextChanged(CharSequence s, int start, int before, int count) {
if (TextUtils.isEmpty(s)) {
buttonClear.animate()
.alpha(0.0f)
.withEndAction(new HideRunnable(buttonClear));
} else {
buttonClear.setVisibility(View.VISIBLE);
buttonClear.animate().alpha(1.0f);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_user);
ButterKnife.bind(this);
teleprinter = new Teleprinter(this);
projectId = getIntent().getLongExtra(KEY_PROJECT_ID, -1);
group = Parcels.unwrap(getIntent().getParcelableExtra(KEY_GROUP));
dialogAccess = new AccessDialog(this, new AccessDialog.Listener() {
@Override
public void onAccessApplied(int accessLevel) {
dialogAccess.showLoading();
if (group == null) {
add(App.get().getGitLab().addProjectMember(projectId, selectedUser.getId(), accessLevel));
} else {
add(App.get().getGitLab().addGroupMember(projectId, selectedUser.getId(), accessLevel));
}
}
});
toolbar.setNavigationIcon(R.drawable.ic_back_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
adapter = new UserAdapter(new UserAdapter.Listener() {
@Override
public void onUserClicked(UserBasic user, UserViewHolder userViewHolder) {
selectedUser = user;
dialogAccess.show();
}
});
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
loadData();
}
});
list.setAdapter(adapter);
layoutManager = new GridLayoutManager(this, 2);
layoutManager.setSpanSizeLookup(adapter.getSpanSizeLookup());
list.setLayoutManager(layoutManager);
list.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int visibleItemCount = layoutManager.getChildCount();
int totalItemCount = layoutManager.getItemCount();
int firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
if (firstVisibleItem + visibleItemCount >= totalItemCount && !loading && nextPageUrl != null) {
loadMore();
}
}
});
morph(root);
}
private void loadData() {
teleprinter.hideKeyboard();
swipeRefreshLayout.setRefreshing(true);
loading = true;
App.get().getGitLab().searchUsers(query)
.compose(this.<Response<List<UserBasic>>>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomResponseSingleObserver<List<UserBasic>>() {
@Override
public void error(@NonNull Throwable t) {
Timber.e(t);
swipeRefreshLayout.setRefreshing(false);
loading = false;
Snackbar.make(root, getString(R.string.connection_error_users), Snackbar.LENGTH_SHORT)
.show();
}
@Override
public void responseSuccess(@NonNull List<UserBasic> users) {
swipeRefreshLayout.setRefreshing(false);
loading = false;
adapter.setData(users);
nextPageUrl = LinkHeaderParser.parse(response()).getNext();
Timber.d("Next page url is %s", nextPageUrl);
}
});
}
private void loadMore() {
loading = true;
adapter.setLoading(true);
Timber.d("loadMore " + nextPageUrl.toString() + " " + query);
App.get().getGitLab().searchUsers(nextPageUrl.toString(), query)
.compose(this.<Response<List<UserBasic>>>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomResponseSingleObserver<List<UserBasic>>() {
@Override
public void error(@NonNull Throwable t) {
Timber.e(t);
adapter.setLoading(false);
}
@Override
public void responseSuccess(@NonNull List<UserBasic> users) {
loading = false;
adapter.setLoading(false);
adapter.addData(users);
nextPageUrl = LinkHeaderParser.parse(response()).getNext();
}
});
}
private void add(Single<Response<Member>> observable) {
observable.subscribeOn(Schedulers.io())
.compose(this.<Response<Member>>bindToLifecycle())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomResponseSingleObserver<Member>() {
@Override
public void error(@NonNull Throwable t) {
Timber.e(t);
String message = getString(R.string.error_failed_to_add_user);
if (t instanceof HttpException) {
switch (((HttpException) t).code()) {
case 409:
message = getString(R.string.error_user_conflict);
}
}
Snackbar.make(root, message, Snackbar.LENGTH_SHORT)
.show();
}
@Override
public void responseSuccess(@NonNull Member member) {
Snackbar.make(root, R.string.user_added_successfully, Snackbar.LENGTH_SHORT)
.show();
dialogAccess.dismiss();
dismiss();
App.bus().post(new MemberAddedEvent(member));
}
});
}
}
package com.commit451.gitlab.activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
import android.support.design.widget.Snackbar
import android.support.v4.widget.SwipeRefreshLayout
import android.support.v7.widget.GridLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.Toolbar
import android.text.TextUtils
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import butterknife.*
import com.commit451.alakazam.HideRunnable
import com.commit451.gitlab.App
import com.commit451.gitlab.R
import com.commit451.gitlab.adapter.UserAdapter
import com.commit451.gitlab.dialog.AccessDialog
import com.commit451.gitlab.event.MemberAddedEvent
import com.commit451.gitlab.extension.setup
import com.commit451.gitlab.model.api.Group
import com.commit451.gitlab.model.api.Member
import com.commit451.gitlab.model.api.UserBasic
import com.commit451.gitlab.rx.CustomResponseSingleObserver
import com.commit451.gitlab.util.LinkHeaderParser
import com.commit451.gitlab.viewHolder.UserViewHolder
import com.commit451.teleprinter.Teleprinter
import com.jakewharton.retrofit2.adapter.rxjava2.HttpException
import io.reactivex.Single
import org.parceler.Parcels
import retrofit2.Response
import timber.log.Timber
/**
* Add a new user to the repo or to the group, depending on the mode
*/
class AddUserActivity : MorphActivity() {
companion object {
private val KEY_PROJECT_ID = "project_id"
private val KEY_GROUP = "group"
fun newIntent(context: Context, projectId: Long): Intent {
val intent = Intent(context, AddUserActivity::class.java)
intent.putExtra(KEY_PROJECT_ID, projectId)
return intent
}
fun newIntent(context: Context, group: Group): Intent {
val intent = Intent(context, AddUserActivity::class.java)
intent.putExtra(KEY_GROUP, Parcels.wrap(group))
return intent
}
}
@BindView(R.id.root) lateinit var root: ViewGroup
@BindView(R.id.toolbar) lateinit var toolbar: Toolbar
@BindView(R.id.search) lateinit var textSearch: EditText
@BindView(R.id.swipe_layout) lateinit var swipeRefreshLayout: SwipeRefreshLayout
@BindView(R.id.list) lateinit var list: RecyclerView
@BindView(R.id.clear) lateinit var buttonClear: View
lateinit var layoutManager: GridLayoutManager
lateinit var adapter: UserAdapter
lateinit var dialogAccess: AccessDialog
lateinit var teleprinter: Teleprinter
var projectId: Long = 0
var group: Group? = null
var query: String? = null
var nextPageUrl: Uri? = null
var loading = false
var selectedUser: UserBasic? = null
@OnClick(R.id.clear)
fun onClearClick() {
buttonClear.animate().alpha(0.0f).withEndAction {
buttonClear.visibility = View.GONE
textSearch.text.clear()
teleprinter.showKeyboard(textSearch)
}
}
@OnEditorAction(R.id.search)
internal fun onEditorAction(): Boolean {
if (!TextUtils.isEmpty(textSearch.text)) {
query = textSearch.text.toString()
loadData()
}
return true
}
@OnTextChanged(R.id.search)
internal fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
if (TextUtils.isEmpty(s)) {
buttonClear.animate()
.alpha(0.0f)
.withEndAction(HideRunnable(buttonClear))
} else {
buttonClear.visibility = View.VISIBLE
buttonClear.animate().alpha(1.0f)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_user)
ButterKnife.bind(this)
teleprinter = Teleprinter(this)
projectId = intent.getLongExtra(KEY_PROJECT_ID, -1)
group = Parcels.unwrap<Group>(intent.getParcelableExtra<Parcelable>(KEY_GROUP))
dialogAccess = AccessDialog(this, object : AccessDialog.Listener {
override fun onAccessApplied(accessLevel: Int) {
dialogAccess.showLoading()
if (group == null) {
add(App.get().gitLab.addProjectMember(projectId, selectedUser!!.id, accessLevel))
} else {
add(App.get().gitLab.addGroupMember(projectId, selectedUser!!.id, accessLevel))
}
}
})
toolbar.setNavigationIcon(R.drawable.ic_back_24dp)
toolbar.setNavigationOnClickListener { onBackPressed() }
adapter = UserAdapter(object : UserAdapter.Listener {
override fun onUserClicked(user: UserBasic, userViewHolder: UserViewHolder) {
selectedUser = user
dialogAccess.show()
}
})
swipeRefreshLayout.setOnRefreshListener { loadData() }
list.adapter = adapter
layoutManager = GridLayoutManager(this, 2)
layoutManager.spanSizeLookup = adapter.spanSizeLookup
list.layoutManager = layoutManager
list.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val visibleItemCount = layoutManager.childCount
val totalItemCount = layoutManager.itemCount
val firstVisibleItem = layoutManager.findFirstVisibleItemPosition()
if (firstVisibleItem + visibleItemCount >= totalItemCount && !loading && nextPageUrl != null) {
loadMore()
}
}
})
morph(root)
}
private fun loadData() {
teleprinter.hideKeyboard()
swipeRefreshLayout.isRefreshing = true
loading = true
App.get().gitLab.searchUsers(query!!)
.setup(bindToLifecycle())
.subscribe(object : CustomResponseSingleObserver<List<UserBasic>>() {
override fun error(t: Throwable) {
Timber.e(t)
swipeRefreshLayout.isRefreshing = false
loading = false
Snackbar.make(root, getString(R.string.connection_error_users), Snackbar.LENGTH_SHORT)
.show()
}
override fun responseSuccess(users: List<UserBasic>) {
swipeRefreshLayout.isRefreshing = false
loading = false
adapter.setData(users)
nextPageUrl = LinkHeaderParser.parse(response()).next
Timber.d("Next page url is %s", nextPageUrl)
}
})
}
private fun loadMore() {
loading = true
adapter.setLoading(true)
Timber.d("loadMore " + nextPageUrl!!.toString() + " " + query)
App.get().gitLab.searchUsers(nextPageUrl!!.toString(), query!!)
.setup(bindToLifecycle())
.subscribe(object : CustomResponseSingleObserver<List<UserBasic>>() {
override fun error(t: Throwable) {
Timber.e(t)
adapter.setLoading(false)
}
override fun responseSuccess(users: List<UserBasic>) {
loading = false
adapter.setLoading(false)
adapter.addData(users)
nextPageUrl = LinkHeaderParser.parse(response()).next
}
})
}
private fun add(observable: Single<Response<Member>>) {
observable.setup(bindToLifecycle())
.subscribe(object : CustomResponseSingleObserver<Member>() {
override fun error(t: Throwable) {
Timber.e(t)
var message = getString(R.string.error_failed_to_add_user)
if (t is HttpException) {
when (t.code()) {
409 -> message = getString(R.string.error_user_conflict)
}
}
Snackbar.make(root, message, Snackbar.LENGTH_SHORT)
.show()
}
override fun responseSuccess(member: Member) {
Snackbar.make(root, R.string.user_added_successfully, Snackbar.LENGTH_SHORT)
.show()
dialogAccess.dismiss()
dismiss()
App.bus().post(MemberAddedEvent(member))
}
})
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment