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

Merged branch develop into master

parents 9bfb829b c3ed3520
No related branches found
No related tags found
No related merge requests found
Pipeline #
Showing
with 1330 additions and 1133 deletions
image: commit451/android-config:latest
#Derived from https://about.gitlab.com/2016/11/30/setting-up-gitlab-ci-for-android-projects/
image: openjdk:8-jdk
variables:
ANDROID_COMPILE_SDK: "25"
ANDROID_BUILD_TOOLS: "25.0.2"
ANDROID_SDK_TOOLS: "25.2.3"
 
before_script:
# Make sure that there won't be any interactive prompts that await user interaction
- unset DISPLAY
- export TERM=dumb
- export _JAVA_OPTIONS="-Djava.awt.headless=true"
# Increase the heap size to 3 GB for faster builds
- export JAVA_OPTS="-Xmx3G"
# Make the gradle wrapper executable
- chmod +x gradlew
- wget --quiet --output-document=android-sdk.zip https://dl.google.com/android/repository/tools_r${ANDROID_SDK_TOOLS}-linux.zip
- unzip -q android-sdk.zip -d android-sdk
- export ANDROID_HOME=$PWD/android-sdk
- echo y | $ANDROID_HOME/tools/bin/sdkmanager "platforms;android-$ANDROID_COMPILE_SDK"
- echo y | $ANDROID_HOME/tools/bin/sdkmanager "build-tools;$ANDROID_BUILD_TOOLS"
- echo y | $ANDROID_HOME/tools/bin/sdkmanager "extras;android;m2repository"
- chmod +x ./gradlew
stages:
- build
 
build:
stage: build
script:
- ./gradlew testFdroidDebug --stacktrace
- ./gradlew testFdroidDebug
\ No newline at end of file
# LabCoat for GitLab
[![Google Play](https://gitlab.com/Commit451/LabCoat/raw/master/art/google-play-badge.png)](https://play.google.com/store/apps/details?id=com.commit451.gitlab)
 
[![build status](https://gitlab.com/ci/projects/7701/status.png?ref=master)](https://gitlab.com/ci/projects/7701?ref=master)
 
![Image](https://gitlab.com/Commit451/LabCoat/raw/master/art/screenshot-1.png)
[![Google Play](https://gitlab.com/Commit451/LabCoat/raw/master/art/google-play-badge.png)](https://play.google.com/store/apps/details?id=com.commit451.gitlab)
 
## Issues
Please see the [issues](https://gitlab.com/Commit451/LabCoat/issues) section to report any bugs or feature requests and to see the list of known issues.
 
## Building
You should be able to build the project from Android Studio without any further setup. The app uses Fabric for Crashlytics, so if you wanted to do a release build, you would need to generate your own Crashlytics/Fabric key. All in all, your gradle.properties will look something like this:
You should be able to build the project from Android Studio without any further setup. The app uses Fabric for crash reporting, so if you wanted to do a release build, you would need to generate your own Crashlytics/Fabric key. All in all, your `gradle.properties` will look something like this:
```Gradle
LABCOAT_FABRIC_KEY = FABRIC_KEY_GOES_HERE_BUT_ONLY_REALLY_NEEDED_FOR_RELEASE_BUILDS
```
Loading
Loading
@@ -52,13 +52,18 @@ The following 3rd party libraries and resources are the reason this app works. R
- MaterialDateTimePicker (https://github.com/wdullaer/MaterialDateTimePicker)
- FlowLayout (https://github.com/blazsolar/FlowLayout)
- SimpleChromeCustomTabs (https://github.com/novoda/simple-chrome-custom-tabs)
- Crashlytics (https://www.crashlytics.com)
- Material Dialogs (https://github.com/afollestad/material-dialogs)
- CircleImageView (https://github.com/hdodenhof/CircleImageView)
- EasyImage (https://github.com/jkwiecien/EasyImage)
- emoji-java (https://github.com/vdurmont/emoji-java)
- Crashlytics (https://www.crashlytics.com)
- highlight.js (https://highlightjs.org/)
 
## Contributing
Please fork this repository and contribute back! All Merge Requests should be made against the `develop` branch, as it is the active branch for development. Please make your best effort to break up commits as much as possible to improve the reviewing process.
 
If you are making substantial changes, please refer to Commit 451's style [guidelines](https://github.com/Commit451/guidelines) for Android
License
--------
 
Loading
Loading
Loading
Loading
@@ -4,7 +4,7 @@ buildscript {
}
 
dependencies {
classpath 'io.fabric.tools:gradle:1.21.7'
classpath 'io.fabric.tools:gradle:1.22.0'
}
}
 
Loading
Loading
@@ -13,17 +13,16 @@ repositories {
}
 
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'
apply plugin: 'io.fabric'
 
def versionMajor = 2
def versionMinor = 4
def versionPatch = 1
def versionPatch = 2
def versionBuild = 0 // bump for dogfood builds, public betas, etc.
 
android {
compileSdkVersion 24
buildToolsVersion "24.0.2"
compileSdkVersion 25
buildToolsVersion "25.0.2"
 
project.ext {
LABCOAT_FABRIC_KEY = project.hasProperty("LABCOAT_FABRIC_KEY") ? project.LABCOAT_FABRIC_KEY : "";
Loading
Loading
@@ -31,7 +30,7 @@ android {
defaultConfig {
applicationId "com.commit451.gitlab"
minSdkVersion 16
targetSdkVersion 24
targetSdkVersion 25
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
versionCode versionMajor * 1000000 + versionMinor * 10000 + versionPatch * 100 + versionBuild
versionName "${versionMajor}.${versionMinor}.${versionPatch}"
Loading
Loading
@@ -65,75 +64,95 @@ android {
}
}
 
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.1'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
//Don't update this unless you can confirm the tests still pass!
testCompile 'org.robolectric:robolectric:3.0'
testCompile 'org.robolectric:robolectric:3.2.1'
 
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
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 'com.android.support:appcompat-v7:24.2.0'
compile 'com.android.support:design:24.2.0'
compile 'com.android.support:recyclerview-v7:24.2.0'
compile 'com.android.support:cardview-v7:24.2.0'
compile 'com.android.support:palette-v7:24.2.0'
compile 'com.android.support:customtabs:24.2.0'
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile('com.squareup.retrofit2:converter-simplexml:2.1.0') {
compile "com.android.support:appcompat-v7:$supportLibVersion"
compile "com.android.support:design:$supportLibVersion"
compile "com.android.support:recyclerview-v7:$supportLibVersion"
compile "com.android.support:cardview-v7:$supportLibVersion"
compile "com.android.support:palette-v7:$supportLibVersion"
compile "com.android.support:customtabs:$supportLibVersion"
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:2.1.0'
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.squareup.okhttp3:okhttp:3.4.1'
compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
compile "com.bluelinelabs:logansquare:$loganSquareVersion"
annotationProcessor "com.bluelinelabs:logansquare-compiler:$loganSquareVersion"
compile 'com.github.Commit451:LoganSquareProGuardRules:1.3.6.0'
compile "com.squareup.okhttp3:okhttp:$okHttpVersion"
compile "com.squareup.okhttp3:logging-interceptor:$okHttpVersion"
compile 'com.squareup.picasso:picasso:2.5.2'
compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.0.2'
compile 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'
compile 'org.greenrobot:eventbus:3.0.0'
compile 'io.reactivex:rxjava:1.1.9'
compile 'com.artemzin.rxjava:proguard-rules:1.1.9.0'
compile 'io.reactivex:rxandroid:1.2.1'
compile 'com.jakewharton:butterknife:8.3.0'
apt 'com.jakewharton:butterknife-compiler:8.3.0'
compile 'com.jakewharton.timber:timber:4.3.0'
apt 'com.bluelinelabs:logansquare-compiler:1.3.7'
compile 'com.bluelinelabs:logansquare:1.3.7'
compile 'net.danlew:android.joda:2.9.4.1'
compile 'org.parceler:parceler-api:1.1.5'
apt 'org.parceler:parceler:1.1.5'
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"
compile "com.github.Commit451.Reptar:reptar:$reptarVersion@aar"
compile "com.github.Commit451.Reptar:reptar-retrofit:$reptarVersion@aar"
compile 'com.github.Commit451:bypasses:1.0.4'
compile 'com.github.Commit451:ElasticDragDismissLayout:1.0.4'
compile 'com.github.Commit451.AdapterLayout:adapterlayout:1.0.3'
compile 'com.github.Commit451.AdapterLayout:adapterflowlayout:1.0.3'
compile 'com.github.Commit451:Easel:1.0.0'
compile 'com.github.Commit451:Gimbal:2.0.0'
compile "com.github.Commit451.AdapterLayout:adapterlayout:$adapterLayout"
compile "com.github.Commit451.AdapterLayout:adapterflowlayout:$adapterLayout"
compile 'com.github.Commit451.Easel:easel:2.1.1@aar'
compile 'com.github.Commit451:Gimbal:2.0.2'
compile 'com.github.Commit451:Teleprinter:1.0.2'
compile 'com.github.Commit451:BypassPicassoImageGetter:1.1.0'
compile 'com.github.Commit451:BypassPicassoImageGetter:1.1.1'
compile 'com.github.Commit451:Jounce:1.0.1'
compile 'com.github.Commit451:EasyCallback:1.1.0'
compile 'com.github.Commit451:ForegroundViews:2.1.0'
compile 'me.zhanghai.android.materialprogressbar:library:1.1.7'
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:Lift:1.2.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.5'
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.0'
compile 'com.github.johnkil.android-robototextview:robototextview:2.5.1'
compile 'com.github.alorma:diff-textview:1.3.0'
compile 'com.wdullaer:materialdatetimepicker:2.5.0'
compile 'com.wdullaer:materialdatetimepicker:3.0.0'
compile 'com.wefika:flowlayout:0.4.1'
compile 'com.github.novoda:simple-chrome-custom-tabs:0.1.3-rc1'
compile 'com.afollestad.material-dialogs:core:0.9.0.1'
compile 'com.afollestad.material-dialogs:commons:0.9.0.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') {
compile('com.vdurmont:emoji-java:3.1.3') {
exclude group: 'org.json', module: 'json'
}
compile 'com.github.jkwiecien:EasyImage:1.2.3'
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.2@aar') {
normalCompile('com.crashlytics.sdk.android:crashlytics:2.6.5@aar') {
transitive = true;
}
}
Loading
Loading
@@ -27,21 +27,6 @@
# Picasso rules
-dontwarn com.squareup.okhttp.**
 
# Retrofit rules
# Platform calls Class.forName on types which do not exist on Android to determine platform.
-dontnote retrofit2.Platform
# Platform used when running on RoboVM on iOS. Will not be used at runtime.
-dontnote retrofit2.Platform$IOS$MainThreadExecutor
# Platform used when running on Java 8 VMs. Will not be used at runtime.
-dontwarn retrofit2.Platform$Java8
# Retain generic type information for use by reflection by converters and adapters.
-keepattributes Signature
# Retain declared checked exceptions for use by a Proxy instance.
-keepattributes Exceptions
# Okio
-dontwarn okio.**
# Simple-Xml Proguard Config
# Keep public classes and methods.
-dontwarn com.bea.xml.stream.**
Loading
Loading
@@ -53,18 +38,6 @@
-dontwarn javax.xml.stream.events.**
-dontwarn javax.xml.**
 
# EventBus
-keepattributes *Annotation*
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(java.lang.Throwable);
}
## joda-time-android 2.8.0
# This is only necessary if you are not including the optional joda-convert dependency
 
Loading
Loading
@@ -78,13 +51,5 @@
 
-keep class org.parceler.Parceler$$Parcels
 
# Unicoding Bypass library
-keep class in.uncod.android.** { *; }
# LoganSquare
-keep class com.bluelinelabs.logansquare.** { *; }
-keep @com.bluelinelabs.logansquare.annotation.JsonObject class *
-keep class **$$JsonObjectMapper { *; }
# Custom rules
-keep class com.commit451.gitlab.ssl.CustomSSLSocketFactory
\ No newline at end of file
Loading
Loading
@@ -5,12 +5,11 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
 
<application
android:name=".App"
android:allowBackup="true"
android:fullBackupContent="true"
android:allowBackup="false"
android:fullBackupContent="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
Loading
Loading
@@ -31,50 +30,74 @@
android:name=".activity.ProjectsActivity"
android:launchMode="singleTask"
android:theme="@style/Activity.Projects" />
<activity
android:name=".activity.GroupsActivity"
android:launchMode="singleTask"
android:theme="@style/Activity.Groups" />
<activity
android:name=".activity.ActivityActivity"
android:launchMode="singleTask"
android:theme="@style/Activity.Activity" />
<activity
android:name=".activity.TodosActivity"
android:launchMode="singleTask"
android:theme="@style/Activity.Todos" />
<activity android:name=".activity.ProjectActivity" />
<activity
android:name=".activity.LoginActivity"
android:theme="@style/Activity.Login" />
<activity android:name=".activity.FileActivity" />
<activity android:name=".activity.IssueActivity" />
<activity android:name=".activity.DiffActivity" />
<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
android:name=".activity.GroupActivity"
android:theme="@style/Activity.Group" />
<activity android:name=".activity.MergeRequestActivity" />
<activity android:name=".activity.AddIssueActivity" />
<activity android:name=".activity.MilestoneActivity" />
<activity android:name=".activity.AddMilestoneActivity" />
<activity android:name=".activity.BuildActivity" />
<activity
android:name=".activity.LoadSomeInfoActivity"
android:theme="@style/Activity.Translucent" />
<activity android:name=".activity.SettingsActivity" />
<activity android:name=".activity.AddLabelActivity" />
<activity android:name=".activity.AddNewLabelActivity" />
<activity android:name=".activity.PickBranchOrTagActivity"
android:theme="@style/Activity.Translucent"/>
<activity android:name=".activity.WebviewLoginActivity" />
<activity android:name=".activity.AttachActivity"
android:theme="@style/Activity.Translucent"/>
<activity
android:name=".activity.PickBranchOrTagActivity"
android:theme="@style/Activity.Translucent" />
<activity android:name=".activity.WebLoginActivity" />
<activity
android:name=".activity.AttachActivity"
android:theme="@style/Activity.Translucent" />
<activity
android:name=".activity.RoutingActivity"
android:launchMode="singleTask"
Loading
Loading
@@ -97,6 +120,14 @@
</intent-filter>
</activity>
 
<activity android:name=".widget.ProjectFeedWidgetConfigureActivity">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<activity android:name=".widget.ProjectFeedWidgetConfigureProjectActivity" />
<receiver
android:name=".widget.UserFeedWidgetProvider"
android:label="User Feed">
Loading
Loading
Loading
Loading
@@ -14,8 +14,10 @@ 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;
Loading
Loading
@@ -37,40 +39,48 @@ import timber.log.Timber;
public class App extends Application {
 
/**
* Register our type converters on our singleton LoganSquare instance. Needs to be set here
* 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 sBus;
private static App sInstance;
private static EventBus bus;
private static App instance;
 
public static EventBus bus() {
if (sBus == null) {
sBus = EventBus.getDefault();
if (bus == null) {
bus = EventBus.getDefault();
}
return sBus;
return bus;
}
 
public static App instance() {
return sInstance;
public static App get() {
return instance;
}
 
private Account mAccount;
private GitLab mGitLab;
private GitLabRss mGitLabRss;
private Picasso mPicasso;
private Account account;
private GitLab gitLab;
private GitLabRss gitLabRss;
private Picasso picasso;
private Prefs prefs;
 
@Override
public void onCreate() {
super.onCreate();
sInstance = this;
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();
setupLeakCanary();
 
if (BuildConfig.DEBUG) {
Timber.plant(new Timber.DebugTree());
Loading
Loading
@@ -79,10 +89,17 @@ public class App extends Application {
JodaTimeAndroid.init(this);
SimpleChromeCustomTabs.initialize(this);
 
List<Account> accounts = Account.getAccounts(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
Loading
Loading
@@ -109,28 +126,28 @@ public class App extends Application {
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
} catch (Exception e) {
Timber.e(e, null);
Timber.e(e);
}
}
 
public GitLab getGitLab() {
return mGitLab;
return gitLab;
}
 
public GitLabRss getGitLabRss() {
return mGitLabRss;
return gitLabRss;
}
 
public Picasso getPicasso() {
return mPicasso;
return picasso;
}
 
public Account getAccount() {
return mAccount;
return account;
}
 
public void setAccount(Account account) {
mAccount = account;
this.account = account;
OkHttpClient.Builder clientBuilder = OkHttpClientFactory.create(account);
if (BuildConfig.DEBUG) {
clientBuilder.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY));
Loading
Loading
@@ -148,15 +165,19 @@ public class App extends Application {
}
}
 
public Prefs getPrefs() {
return prefs;
}
private void initGitLab(Account account, OkHttpClient client) {
mGitLab = GitLabFactory.create(account, client);
gitLab = GitLabFactory.create(account, client);
}
 
private void initGitLabRss(Account account, OkHttpClient client) {
mGitLabRss = GitLabRssFactory.create(account, client);
gitLabRss = GitLabRssFactory.create(account, client);
}
 
private void initPicasso(OkHttpClient client) {
mPicasso = PicassoFactory.createPicasso(client);
picasso = PicassoFactory.createPicasso(client);
}
}
Loading
Loading
@@ -16,12 +16,12 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
 
import com.commit451.easycallback.EasyCallback;
import com.commit451.gimbal.Gimbal;
import com.commit451.gitlab.App;
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;
Loading
Loading
@@ -37,7 +37,8 @@ import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import de.hdodenhof.circleimageview.CircleImageView;
import retrofit2.Callback;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
 
/**
Loading
Loading
@@ -52,36 +53,36 @@ public class AboutActivity extends BaseActivity {
}
 
@BindView(R.id.root)
ViewGroup mRoot;
ViewGroup root;
@BindView(R.id.toolbar)
Toolbar mToolbar;
Toolbar toolbar;
@BindView(R.id.contributors)
TextView mContributors;
TextView textContributors;
@BindView(R.id.physics_layout)
PhysicsFlowLayout mPhysicsLayout;
PhysicsFlowLayout physicsLayout;
@BindView(R.id.progress)
View mProgress;
View progress;
SensorManager sensorManager;
Sensor gravitySensor;
Gimbal gimbal;
 
@OnClick(R.id.sauce)
void onSauceClick() {
if (getString(R.string.url_gitlab).equals(App.instance().getAccount().getServerUrl().toString())) {
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));
}
}
 
SensorManager sensorManager;
Sensor gravitySensor;
Gimbal mGimbal;
private final SensorEventListener sensorEventListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_GRAVITY) {
if (mPhysicsLayout.getPhysics().getWorld() != null) {
mGimbal.normalizeGravityEvent(event);
mPhysicsLayout.getPhysics().getWorld().setGravity(new Vec2(-event.values[0], event.values[1]));
if (physicsLayout.getPhysics().getWorld() != null) {
gimbal.normalizeGravityEvent(event);
physicsLayout.getPhysics().getWorld().setGravity(new Vec2(-event.values[0], event.values[1]));
}
}
}
Loading
Loading
@@ -91,42 +92,45 @@ public class AboutActivity extends BaseActivity {
}
};
 
private Callback<List<Contributor>> mContributorResponseCallback = new EasyCallback<List<Contributor>>() {
@Override
public void success(@NonNull List<Contributor> response) {
mProgress.setVisibility(View.GONE);
addContributors(Contributor.groupContributors(response));
}
@Override
public void failure(Throwable t) {
Timber.e(t, null);
mProgress.setVisibility(View.GONE);
Snackbar.make(mRoot, R.string.failed_to_load_contributors, Snackbar.LENGTH_SHORT)
.show();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGimbal = new Gimbal(this);
mGimbal.lock();
gimbal = new Gimbal(this);
gimbal.lock();
setContentView(R.layout.activity_about);
ButterKnife.bind(this);
mToolbar.setNavigationIcon(R.drawable.ic_back_24dp);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
toolbar.setNavigationIcon(R.drawable.ic_back_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
mToolbar.setTitle(R.string.about);
mPhysicsLayout.getPhysics().enableFling();
toolbar.setTitle(R.string.about);
physicsLayout.getPhysics().enableFling();
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
gravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
App.instance().getGitLab().getContributors(REPO_ID).enqueue(mContributorResponseCallback);
mProgress.setVisibility(View.VISIBLE);
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
Loading
Loading
@@ -146,7 +150,7 @@ public class AboutActivity extends BaseActivity {
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++) {
for (int i = 0; i < contributors.size(); i++) {
Contributor contributor = contributors.get(i);
CircleImageView imageView = new CircleImageView(this);
FlowLayout.LayoutParams llp = new FlowLayout.LayoutParams(
Loading
Loading
@@ -156,13 +160,13 @@ public class AboutActivity extends BaseActivity {
imageView.setBorderWidth(borderSize);
imageView.setBorderColor(Color.BLACK);
Physics.setPhysicsConfig(imageView, config);
mPhysicsLayout.addView(imageView);
physicsLayout.addView(imageView);
 
Uri url = ImageUtil.getAvatarUrl(contributor.getEmail(), imageSize);
App.instance().getPicasso()
App.get().getPicasso()
.load(url)
.into(imageView);
}
mPhysicsLayout.requestLayout();
physicsLayout.requestLayout();
}
}
Loading
Loading
@@ -32,10 +32,10 @@ public class ActivityActivity extends BaseActivity {
return intent;
}
 
@BindView(R.id.drawer_layout) DrawerLayout mDrawerLayout;
@BindView(R.id.toolbar) Toolbar mToolbar;
EventReceiver mEventReceiver;
@BindView(R.id.drawer_layout)
DrawerLayout drawerLayout;
@BindView(R.id.toolbar)
Toolbar toolbar;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
Loading
Loading
@@ -43,21 +43,20 @@ public class ActivityActivity extends BaseActivity {
setContentView(R.layout.activity_activity);
ButterKnife.bind(this);
 
mEventReceiver = new EventReceiver();
App.bus().register(mEventReceiver);
App.bus().register(this);
 
mToolbar.setTitle(R.string.nav_activity);
mToolbar.setNavigationIcon(R.drawable.ic_menu_24dp);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
toolbar.setTitle(R.string.nav_activity);
toolbar.setNavigationIcon(R.drawable.ic_menu_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDrawerLayout.openDrawer(GravityCompat.START);
drawerLayout.openDrawer(GravityCompat.START);
}
});
 
FeedFragment feedFragment = (FeedFragment) getSupportFragmentManager().findFragmentByTag(TAG_FEED_FRAGMENT);
if (feedFragment == null) {
Uri feedUri = App.instance().getAccount().getServerUrl();
Uri feedUri = App.get().getAccount().getServerUrl();
 
feedUri = feedUri.buildUpon()
.appendPath("dashboard")
Loading
Loading
@@ -75,14 +74,11 @@ public class ActivityActivity extends BaseActivity {
@Override
protected void onDestroy() {
super.onDestroy();
App.bus().unregister(mEventReceiver);
App.bus().unregister(this);
}
 
private class EventReceiver {
@Subscribe
public void onCloseDrawerEvent(CloseDrawerEvent event) {
mDrawerLayout.closeDrawers();
}
@Subscribe
public void onCloseDrawerEvent(CloseDrawerEvent event) {
drawerLayout.closeDrawers();
}
}
Loading
Loading
@@ -13,12 +13,12 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
 
import com.commit451.easycallback.EasyCallback;
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;
Loading
Loading
@@ -27,8 +27,12 @@ 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!
*/
Loading
Loading
@@ -46,18 +50,18 @@ public class AddLabelActivity extends BaseActivity {
}
 
@BindView(R.id.root)
ViewGroup mRoot;
ViewGroup root;
@BindView(R.id.toolbar)
Toolbar mToolbar;
Toolbar toolbar;
@BindView(R.id.swipe_layout)
SwipeRefreshLayout mSwipeRefreshLayout;
SwipeRefreshLayout swipeRefreshLayout;
@BindView(R.id.list)
RecyclerView mList;
LabelAdapter mLabelAdapter;
RecyclerView list;
LabelAdapter adapterLabel;
@BindView(R.id.message_text)
TextView mTextMessage;
TextView textMessage;
 
long mProjectId;
long projectId;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
Loading
Loading
@@ -65,27 +69,27 @@ public class AddLabelActivity extends BaseActivity {
setContentView(R.layout.activity_add_label);
ButterKnife.bind(this);
 
mProjectId = getIntent().getLongExtra(KEY_PROJECT_ID, -1);
mToolbar.setTitle(R.string.labels);
mToolbar.inflateMenu(R.menu.menu_add_label);
mToolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
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, mProjectId, REQUEST_NEW_LABEL);
Navigator.navigateToAddNewLabel(AddLabelActivity.this, projectId, REQUEST_NEW_LABEL);
return true;
}
return false;
}
});
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
load();
}
});
mLabelAdapter = new LabelAdapter(new LabelAdapter.Listener() {
adapterLabel = new LabelAdapter(new LabelAdapter.Listener() {
@Override
public void onLabelClicked(Label label, LabelViewHolder viewHolder) {
Intent data = new Intent();
Loading
Loading
@@ -94,11 +98,11 @@ public class AddLabelActivity extends BaseActivity {
finish();
}
});
mList.setAdapter(mLabelAdapter);
mList.setLayoutManager(new LinearLayoutManager(this));
list.setAdapter(adapterLabel);
list.setLayoutManager(new LinearLayoutManager(this));
 
mToolbar.setNavigationIcon(R.drawable.ic_back_24dp);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
toolbar.setNavigationIcon(R.drawable.ic_back_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
Loading
Loading
@@ -109,32 +113,37 @@ public class AddLabelActivity extends BaseActivity {
}
 
private void load() {
mTextMessage.setVisibility(View.GONE);
mSwipeRefreshLayout.post(new Runnable() {
textMessage.setVisibility(View.GONE);
swipeRefreshLayout.post(new Runnable() {
@Override
public void run() {
if (mSwipeRefreshLayout != null) {
mSwipeRefreshLayout.setRefreshing(true);
}
}
});
App.instance().getGitLab().getLabels(mProjectId).enqueue(new EasyCallback<List<Label>>() {
@Override
public void success(@NonNull List<Label> response) {
mSwipeRefreshLayout.setRefreshing(false);
if (response.isEmpty()) {
mTextMessage.setVisibility(View.VISIBLE);
if (swipeRefreshLayout != null) {
swipeRefreshLayout.setRefreshing(true);
}
mLabelAdapter.setItems(response);
}
@Override
public void failure(Throwable t) {
mSwipeRefreshLayout.setRefreshing(false);
mTextMessage.setVisibility(View.VISIBLE);
Timber.e(t, null);
}
});
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
Loading
Loading
@@ -144,7 +153,7 @@ public class AddLabelActivity extends BaseActivity {
case REQUEST_NEW_LABEL:
if (resultCode == RESULT_OK) {
Label newLabel = Parcels.unwrap(data.getParcelableExtra(AddNewLabelActivity.KEY_NEW_LABEL));
mLabelAdapter.addLabel(newLabel);
adapterLabel.addLabel(newLabel);
}
break;
}
Loading
Loading
Loading
Loading
@@ -18,11 +18,10 @@ import android.widget.FrameLayout;
import com.commit451.easel.Easel;
import com.commit451.gitlab.App;
import com.commit451.gitlab.R;
import com.commit451.easycallback.EasyCallback;
import com.commit451.gitlab.api.GitLabFactory;
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;
 
Loading
Loading
@@ -34,7 +33,9 @@ import java.util.Date;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import retrofit2.Callback;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
 
public class AddMilestoneActivity extends MorphActivity {
Loading
Loading
@@ -56,99 +57,77 @@ public class AddMilestoneActivity extends MorphActivity {
}
 
@BindView(R.id.root)
FrameLayout mRoot;
FrameLayout root;
@BindView(R.id.toolbar)
Toolbar mToolbar;
Toolbar toolbar;
@BindView(R.id.title_text_input_layout)
TextInputLayout mTitleTextInputLayout;
TextInputLayout textInputLayoutTitle;
@BindView(R.id.title)
EditText mTitle;
EditText textTitle;
@BindView(R.id.description)
EditText mDescription;
EditText textDescription;
@BindView(R.id.due_date)
Button mDueDate;
Button buttonDueDate;
@BindView(R.id.progress)
View mProgress;
View progress;
 
@OnClick(R.id.due_date)
void onDueDateClicked() {
Calendar now = Calendar.getInstance();
if (mCurrentDate != null) {
now.setTime(mCurrentDate);
}
DatePickerDialog dpd = DatePickerDialog.newInstance(
mOnDateSetListener,
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");
}
Teleprinter teleprinter;
 
long mProjectId;
Milestone mMilestone;
Date mCurrentDate;
Teleprinter mTeleprinter;
long projectId;
Milestone milestone;
Date currentDate;
 
private final DatePickerDialog.OnDateSetListener mOnDateSetListener = new DatePickerDialog.OnDateSetListener() {
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);
mCurrentDate = calendar.getTime();
bind(mCurrentDate);
}
};
private final View.OnClickListener mOnBackPressed = new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
currentDate = calendar.getTime();
bind(currentDate);
}
};
 
private Callback<Milestone> mMilestoneCallback = new EasyCallback<Milestone>() {
@Override
public void success(@NonNull Milestone response) {
mProgress.setVisibility(View.GONE);
if (mMilestone == null) {
App.bus().post(new MilestoneCreatedEvent(response));
} else {
App.bus().post(new MilestoneChangedEvent(response));
}
finish();
}
@Override
public void failure(Throwable t) {
Timber.e(t, null);
mProgress.setVisibility(View.GONE);
showError();
@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(mRoot);
mTeleprinter = new Teleprinter(this);
mProjectId = getIntent().getLongExtra(KEY_PROJECT_ID, -1);
mMilestone = Parcels.unwrap(getIntent().getParcelableExtra(KEY_MILESTONE));
if (mMilestone != null) {
bind(mMilestone);
mToolbar.inflateMenu(R.menu.menu_edit_milestone);
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 {
mToolbar.inflateMenu(R.menu.menu_add_milestone);
toolbar.inflateMenu(R.menu.menu_add_milestone);
}
mToolbar.setNavigationIcon(R.drawable.ic_back_24dp);
mToolbar.setNavigationOnClickListener(mOnBackPressed);
mToolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
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()) {
Loading
Loading
@@ -163,50 +142,76 @@ public class AddMilestoneActivity extends MorphActivity {
}
 
private void createMilestone() {
mTeleprinter.hideKeyboard();
if (TextUtils.isEmpty(mTitle.getText())) {
mTitleTextInputLayout.setError(getString(R.string.required_field));
teleprinter.hideKeyboard();
if (TextUtils.isEmpty(textTitle.getText())) {
textInputLayoutTitle.setError(getString(R.string.required_field));
return;
}
 
mProgress.setVisibility(View.VISIBLE);
progress.setVisibility(View.VISIBLE);
String dueDate = null;
if (mCurrentDate != null) {
dueDate = Milestone.DUE_DATE_FORMAT.format(mCurrentDate);
if (currentDate != null) {
dueDate = Milestone.DUE_DATE_FORMAT.format(currentDate);
}
 
if (mMilestone == null) {
App.instance().getGitLab().createMilestone(mProjectId,
mTitle.getText().toString(),
mDescription.getText().toString(),
dueDate).enqueue(mMilestoneCallback);
if (milestone == null) {
createOrEditMilestone(App.get().getGitLab().createMilestone(projectId,
textTitle.getText().toString(),
textDescription.getText().toString(),
dueDate));
} else {
App.instance().getGitLab().editMilestone(mProjectId,
mMilestone.getId(),
mTitle.getText().toString(),
mDescription.getText().toString(),
dueDate).enqueue(mMilestoneCallback);
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(mRoot, getString(R.string.failed_to_create_milestone), Snackbar.LENGTH_SHORT)
Snackbar.make(root, getString(R.string.failed_to_create_milestone), Snackbar.LENGTH_SHORT)
.show();
}
 
private void bind(Date date) {
mDueDate.setText(Milestone.DUE_DATE_FORMAT.format(date));
buttonDueDate.setText(Milestone.DUE_DATE_FORMAT.format(date));
}
 
private void bind(Milestone milestone) {
mTitle.setText(milestone.getTitle());
textTitle.setText(milestone.getTitle());
if (milestone.getDescription() != null) {
mDescription.setText(milestone.getDescription());
textDescription.setText(milestone.getDescription());
}
if (milestone.getDueDate() != null) {
mCurrentDate = milestone.getDueDate();
bind(mCurrentDate);
currentDate = milestone.getDueDate();
bind(currentDate);
}
}
}
Loading
Loading
@@ -18,19 +18,22 @@ import android.widget.ImageView;
import android.widget.TextView;
 
import com.afollestad.materialdialogs.color.ColorChooserDialog;
import com.commit451.easycallback.EasyCallback;
import com.commit451.easycallback.HttpException;
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;
 
/**
Loading
Loading
@@ -49,48 +52,25 @@ public class AddNewLabelActivity extends BaseActivity implements ColorChooserDia
}
 
@BindView(R.id.root)
ViewGroup mRoot;
ViewGroup root;
@BindView(R.id.toolbar)
Toolbar mToolbar;
Toolbar toolbar;
@BindView(R.id.title_text_input_layout)
TextInputLayout mTextInputLayoutTitle;
TextInputLayout textInputLayoutTitle;
@BindView(R.id.description)
TextView mDescription;
TextView textDescription;
@BindView(R.id.image_color)
ImageView mImageColor;
ImageView imageColor;
@BindView(R.id.progress)
View mProgress;
View progress;
 
int mChosenColor = -1;
private final EasyCallback<Label> mCreateLabelCallback = new EasyCallback<Label>() {
@Override
public void success(@NonNull Label response) {
Intent data = new Intent();
data.putExtra(KEY_NEW_LABEL, Parcels.wrap(response));
setResult(RESULT_OK, data);
finish();
}
@Override
public void failure(Throwable t) {
Timber.e(t, null);
mProgress.setVisibility(View.GONE);
if (t instanceof HttpException && ((HttpException) t).getCode() == 409) {
Snackbar.make(mRoot, R.string.label_already_exists, Snackbar.LENGTH_SHORT)
.show();
} else {
Snackbar.make(mRoot, R.string.failed_to_create_label, Snackbar.LENGTH_SHORT)
.show();
}
}
};
int chosenColor = -1;
 
@OnClick(R.id.root_color)
void onChooseColorClicked() {
// Pass AppCompatActivity which implements ColorCallback, along with the title of the dialog
// Pass AppCompatActivity which implements ColorCallback, along with the textTitle of the dialog
new ColorChooserDialog.Builder(this, R.string.add_new_label_choose_color)
.preselect(mChosenColor)
.preselect(chosenColor)
.show();
}
 
Loading
Loading
@@ -100,16 +80,16 @@ public class AddNewLabelActivity extends BaseActivity implements ColorChooserDia
setContentView(R.layout.activity_add_new_label);
ButterKnife.bind(this);
 
mToolbar.setNavigationIcon(R.drawable.ic_back_24dp);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
toolbar.setNavigationIcon(R.drawable.ic_back_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onBackPressed();
}
});
 
mToolbar.inflateMenu(R.menu.menu_add_new_label);
mToolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
toolbar.inflateMenu(R.menu.menu_add_new_label);
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
Loading
Loading
@@ -124,8 +104,8 @@ public class AddNewLabelActivity extends BaseActivity implements ColorChooserDia
 
@Override
public void onColorSelection(@NonNull ColorChooserDialog dialog, @ColorInt int selectedColor) {
mChosenColor = selectedColor;
mImageColor.setImageDrawable(new ColorDrawable(selectedColor));
chosenColor = selectedColor;
imageColor.setImageDrawable(new ColorDrawable(selectedColor));
}
 
private long getProjectId() {
Loading
Loading
@@ -133,27 +113,52 @@ public class AddNewLabelActivity extends BaseActivity implements ColorChooserDia
}
 
private void createLabel() {
if (Validator.validateFieldsNotEmpty(getString(R.string.required_field), mTextInputLayoutTitle)) {
if (mChosenColor == -1) {
Snackbar.make(mRoot, R.string.add_new_label_color_is_required, Snackbar.LENGTH_SHORT)
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 = mTextInputLayoutTitle.getEditText().getText().toString();
String title = textInputLayoutTitle.getEditText().getText().toString();
String description = null;
if (!TextUtils.isEmpty(mDescription.getText())) {
description = mDescription.getText().toString();
if (!TextUtils.isEmpty(textDescription.getText())) {
description = textDescription.getText().toString();
}
String color = null;
if (mChosenColor != -1) {
color = ColorUtil.convertColorIntToString(mChosenColor);
if (chosenColor != -1) {
color = ColorUtil.convertColorIntToString(chosenColor);
Timber.d("Setting color to %s", color);
}
mProgress.setVisibility(View.VISIBLE);
mProgress.setAlpha(0.0f);
mProgress.animate().alpha(1.0f);
App.instance().getGitLab().createLabel(getProjectId(), title, color, description)
.enqueue(mCreateLabelCallback);
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();
}
});
}
}
}
Loading
Loading
@@ -10,29 +10,25 @@ 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.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView;
 
import com.commit451.easycallback.EasyCallback;
import com.commit451.easycallback.HttpException;
import com.commit451.alakazam.HideRunnable;
import com.commit451.gitlab.App;
import com.commit451.gitlab.R;
import com.commit451.gitlab.adapter.UsersAdapter;
import com.commit451.gitlab.animation.HideRunnable;
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.util.PaginationUtil;
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;
 
Loading
Loading
@@ -41,7 +37,12 @@ import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import retrofit2.Callback;
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;
 
/**
Loading
Loading
@@ -65,223 +66,207 @@ public class AddUserActivity extends MorphActivity {
}
 
@BindView(R.id.root)
ViewGroup mRoot;
ViewGroup root;
@BindView(R.id.toolbar)
Toolbar mToolbar;
Toolbar toolbar;
@BindView(R.id.search)
EditText mUserSearch;
EditText textSearch;
@BindView(R.id.swipe_layout)
SwipeRefreshLayout mSwipeRefreshLayout;
SwipeRefreshLayout swipeRefreshLayout;
@BindView(R.id.list)
RecyclerView mRecyclerView;
RecyclerView list;
@BindView(R.id.clear)
View mClearView;
GridLayoutManager mUserLinearLayoutManager;
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() {
mClearView.animate().alpha(0.0f).withEndAction(new Runnable() {
buttonClear.animate().alpha(0.0f).withEndAction(new Runnable() {
@Override
public void run() {
mClearView.setVisibility(View.GONE);
mUserSearch.getText().clear();
mTeleprinter.showKeyboard(mUserSearch);
buttonClear.setVisibility(View.GONE);
textSearch.getText().clear();
teleprinter.showKeyboard(textSearch);
}
});
}
 
UsersAdapter mAdapter;
AccessDialog mAccessDialog;
UserBasic mSelectedUser;
long mProjectId;
Group mGroup;
String mSearchQuery;
Uri mNextPageUrl;
boolean mLoading = false;
Teleprinter mTeleprinter;
private final View.OnClickListener mOnBackPressed = new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
};
private final RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int visibleItemCount = mUserLinearLayoutManager.getChildCount();
int totalItemCount = mUserLinearLayoutManager.getItemCount();
int firstVisibleItem = mUserLinearLayoutManager.findFirstVisibleItemPosition();
if (firstVisibleItem + visibleItemCount >= totalItemCount && !mLoading && mNextPageUrl != null) {
loadMore();
}
}
};
private final TextView.OnEditorActionListener mSearchEditorActionListener = new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (!TextUtils.isEmpty(mUserSearch.getText())) {
mSearchQuery = mUserSearch.getText().toString();
loadData();
}
return true;
}
};
private final TextWatcher mTextWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (TextUtils.isEmpty(s)) {
mClearView.animate()
.alpha(0.0f)
.withEndAction(new HideRunnable(mClearView));
} else {
mClearView.setVisibility(View.VISIBLE);
mClearView.animate().alpha(1.0f);
}
}
@Override
public void afterTextChanged(Editable s) {
}
};
private final UsersAdapter.Listener mUserClickListener = new UsersAdapter.Listener() {
@Override
public void onUserClicked(UserBasic user, UserViewHolder userViewHolder) {
mSelectedUser = user;
mAccessDialog.show();
}
};
private final AccessDialog.OnAccessAppliedListener mOnAccessAppliedListener = new AccessDialog.OnAccessAppliedListener() {
@Override
public void onAccessApplied(int accessLevel) {
mAccessDialog.showLoading();
if (mGroup == null) {
App.instance().getGitLab().addProjectMember(
mProjectId,
mSelectedUser.getId(),
accessLevel).enqueue(mAddGroupMemeberCallback);
} else {
App.instance().getGitLab().addGroupMember(mGroup.getId(),
mSelectedUser.getId(),
accessLevel).enqueue(mAddGroupMemeberCallback);
}
}
};
private final Callback<List<UserBasic>> mUserCallback = new EasyCallback<List<UserBasic>>() {
@Override
public void success(@NonNull List<UserBasic> response) {
mSwipeRefreshLayout.setRefreshing(false);
mLoading = false;
mAdapter.setData(response);
mNextPageUrl = PaginationUtil.parse(getResponse()).getNext();
Timber.d("Next page url is %s", mNextPageUrl);
}
@Override
public void failure(Throwable t) {
Timber.e(t, null);
mSwipeRefreshLayout.setRefreshing(false);
mLoading = false;
Snackbar.make(mRoot, getString(R.string.connection_error_users), Snackbar.LENGTH_SHORT)
.show();
}
};
private final Callback<List<UserBasic>> mMoreUsersCallback = new EasyCallback<List<UserBasic>>() {
@Override
public void success(@NonNull List<UserBasic> response) {
mLoading = false;
mAdapter.setLoading(false);
mAdapter.addData(response);
mNextPageUrl = PaginationUtil.parse(getResponse()).getNext();
}
@Override
public void failure(Throwable t) {
Timber.e(t, null);
mAdapter.setLoading(false);
}
};
private final Callback<Member> mAddGroupMemeberCallback = new EasyCallback<Member>() {
@Override
public void success(@NonNull Member response) {
Snackbar.make(mRoot, R.string.user_added_successfully, Snackbar.LENGTH_SHORT)
.show();
mAccessDialog.dismiss();
dismiss();
App.bus().post(new MemberAddedEvent(response));
@OnEditorAction(R.id.search)
boolean onEditorAction() {
if (!TextUtils.isEmpty(textSearch.getText())) {
query = textSearch.getText().toString();
loadData();
}
return true;
}
 
@Override
public void failure(Throwable t) {
Timber.e(t, null);
if (t instanceof HttpException) {
//Conflict
if (((HttpException) t).getCode() == 409) {
Snackbar.make(mRoot, R.string.error_user_conflict, Snackbar.LENGTH_SHORT)
.show();
}
} else {
Snackbar.make(mRoot, R.string.error_failed_to_add_user, Snackbar.LENGTH_SHORT)
.show();
}
@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);
mTeleprinter = new Teleprinter(this);
mProjectId = getIntent().getLongExtra(KEY_PROJECT_ID, -1);
mGroup = Parcels.unwrap(getIntent().getParcelableExtra(KEY_GROUP));
mAccessDialog = new AccessDialog(this, mOnAccessAppliedListener);
mToolbar.setNavigationIcon(R.drawable.ic_back_24dp);
mToolbar.setNavigationOnClickListener(mOnBackPressed);
mUserSearch.setOnEditorActionListener(mSearchEditorActionListener);
mUserSearch.addTextChangedListener(mTextWatcher);
mAdapter = new UsersAdapter(mUserClickListener);
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
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();
}
});
mRecyclerView.setAdapter(mAdapter);
mUserLinearLayoutManager = new GridLayoutManager(this, 2);
mUserLinearLayoutManager.setSpanSizeLookup(mAdapter.getSpanSizeLookup());
mRecyclerView.setLayoutManager(mUserLinearLayoutManager);
mRecyclerView.addOnScrollListener(mOnScrollListener);
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(mRoot);
morph(root);
}
 
private void loadData() {
mTeleprinter.hideKeyboard();
mSwipeRefreshLayout.setRefreshing(true);
mLoading = true;
App.instance().getGitLab().searchUsers(mSearchQuery).enqueue(mUserCallback);
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() {
mLoading = true;
mAdapter.setLoading(true);
Timber.d("loadMore " + mNextPageUrl.toString() + " " + mSearchQuery);
App.instance().getGitLab().searchUsers(mNextPageUrl.toString(), mSearchQuery).enqueue(mMoreUsersCallback);
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.animation.Animator;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
Loading
Loading
@@ -8,27 +9,31 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
 
import com.commit451.easycallback.EasyCallback;
import com.commit451.gitlab.App;
import com.commit451.gitlab.R;
import com.commit451.gitlab.model.api.FileUploadResponse;
import com.commit451.gitlab.model.api.Project;
import com.commit451.gitlab.observable.FileObservableFactory;
import com.commit451.gitlab.rx.CustomSingleObserver;
import com.commit451.gitlab.rx.FileObservableFactory;
 
import org.parceler.Parcels;
 
import java.io.File;
import java.util.List;
 
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import io.codetail.animation.ViewAnimationUtils;
import io.reactivex.SingleSource;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
import okhttp3.MultipartBody;
import pl.aprilapps.easyphotopicker.DefaultCallback;
import pl.aprilapps.easyphotopicker.EasyImage;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
import rx.schedulers.Schedulers;
import timber.log.Timber;
 
/**
Loading
Loading
@@ -47,27 +52,13 @@ public class AttachActivity extends BaseActivity {
}
 
@BindView(R.id.root_buttons)
ViewGroup mRootButtons;
ViewGroup rootButtons;
@BindView(R.id.progress)
View mProgress;
Project mProject;
private final EasyCallback<FileUploadResponse> mUploadCallback = new EasyCallback<FileUploadResponse>() {
@Override
public void success(@NonNull FileUploadResponse response) {
Intent data = new Intent();
data.putExtra(KEY_FILE_UPLOAD_RESPONSE, Parcels.wrap(response));
setResult(RESULT_OK, data);
finish();
}
@Override
public void failure(Throwable t) {
Timber.e(t);
finish();
}
};
View progress;
@BindView(R.id.attachCard)
View card;
Project project;
 
@OnClick(R.id.root)
void onRootClicked() {
Loading
Loading
@@ -76,7 +67,7 @@ public class AttachActivity extends BaseActivity {
 
@OnClick(R.id.button_choose_photo)
void onChoosePhotoClicked() {
EasyImage.openGallery(this, 0);
EasyImage.openGallery(this, 0, false);
}
 
@OnClick(R.id.button_take_photo)
Loading
Loading
@@ -94,7 +85,24 @@ public class AttachActivity extends BaseActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_attach);
ButterKnife.bind(this);
mProject = Parcels.unwrap(getIntent().getParcelableExtra(KEY_PROJECT));
//Run the runnable after the view has been measured
card.post(new Runnable() {
@Override
public void run() {
//we need the radius of the animation circle, which is the diagonal of the view
float finalRadius = (float) Math.hypot(card.getWidth(), card.getHeight());
//it's using a 3rd-party ViewAnimationUtils class for compat reasons (up to API 14)
Animator animator = ViewAnimationUtils
.createCircularReveal(card, 0, card.getHeight(), 0, finalRadius);
animator.setDuration(500);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.start();
}
});
project = Parcels.unwrap(getIntent().getParcelableExtra(KEY_PROJECT));
}
 
@Override
Loading
Loading
@@ -107,9 +115,8 @@ public class AttachActivity extends BaseActivity {
}
 
@Override
public void onImagePicked(File imageFile, EasyImage.ImageSource source, int type) {
//Handle the image
onPhotoReturned(imageFile);
public void onImagesPicked(List<File> imageFiles, EasyImage.ImageSource source, int type) {
onPhotoReturned(imageFiles.get(0));
}
 
@Override
Loading
Loading
@@ -125,16 +132,39 @@ public class AttachActivity extends BaseActivity {
});
}
 
@Override
public void finish() {
super.finish();
overridePendingTransition(R.anim.do_nothing, R.anim.fade_out);
}
private void onPhotoReturned(File photo) {
mProgress.setVisibility(View.VISIBLE);
mRootButtons.setVisibility(View.INVISIBLE);
progress.setVisibility(View.VISIBLE);
rootButtons.setVisibility(View.INVISIBLE);
FileObservableFactory.toPart(photo)
.flatMap(new Function<MultipartBody.Part, SingleSource<FileUploadResponse>>() {
@Override
public SingleSource<FileUploadResponse> apply(MultipartBody.Part part) throws Exception {
return App.get().getGitLab().uploadFile(project.getId(), part);
}
})
.compose(this.<FileUploadResponse>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<MultipartBody.Part>() {
.subscribe(new CustomSingleObserver<FileUploadResponse>() {
@Override
public void success(@NonNull FileUploadResponse fileUploadResponse) {
Intent data = new Intent();
data.putExtra(KEY_FILE_UPLOAD_RESPONSE, Parcels.wrap(fileUploadResponse));
setResult(RESULT_OK, data);
finish();
}
@Override
public void call(MultipartBody.Part part) {
App.instance().getGitLab().uploadFile(mProject.getId(), part).enqueue(mUploadCallback);
public void error(@NonNull Throwable t) {
Timber.e(t);
finish();
}
});
}
Loading
Loading
package com.commit451.gitlab.activity;
 
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.design.widget.TextInputLayout;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
 
import com.commit451.gitlab.R;
import com.trello.rxlifecycle2.components.support.RxAppCompatActivity;
 
/**
* Base activity for others to derive from
*/
public class BaseActivity extends AppCompatActivity {
public class BaseActivity extends RxAppCompatActivity {
 
public boolean hasEmptyFields(TextInputLayout... textInputLayouts) {
boolean hasEmptyField = false;
Loading
Loading
package com.commit451.gitlab.activity;
 
import android.Manifest;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TabLayout;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
 
import com.commit451.easycallback.EasyCallback;
import com.commit451.gitlab.App;
import com.commit451.gitlab.R;
import com.commit451.gitlab.adapter.BuildSectionsPagerAdapter;
Loading
Loading
@@ -20,6 +23,7 @@ import com.commit451.gitlab.event.BuildChangedEvent;
import com.commit451.gitlab.model.Account;
import com.commit451.gitlab.model.api.Build;
import com.commit451.gitlab.model.api.Project;
import com.commit451.gitlab.rx.CustomSingleObserver;
import com.commit451.gitlab.util.BuildUtil;
import com.commit451.gitlab.util.DownloadUtil;
 
Loading
Loading
@@ -27,6 +31,8 @@ import org.parceler.Parcels;
 
import butterknife.BindView;
import butterknife.ButterKnife;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
 
/**
Loading
Loading
@@ -34,6 +40,8 @@ import timber.log.Timber;
*/
public class BuildActivity extends BaseActivity {
 
private static final int REQUEST_PERMISSION_WRITE_STORAGE = 1337;
private static final String KEY_PROJECT = "key_project";
private static final String KEY_BUILD = "key_merge_request";
 
Loading
Loading
@@ -45,96 +53,102 @@ public class BuildActivity extends BaseActivity {
}
 
@BindView(R.id.root)
ViewGroup mRoot;
ViewGroup root;
@BindView(R.id.toolbar)
Toolbar mToolbar;
Toolbar toolbar;
@BindView(R.id.tabs)
TabLayout mTabLayout;
TabLayout tabLayout;
@BindView(R.id.pager)
ViewPager mViewPager;
ViewPager viewPager;
@BindView(R.id.progress)
View mProgress;
MenuItem mMenuItemDownload;
Project mProject;
Build mBuild;
private final EasyCallback<Build> mRetryCallback = new EasyCallback<Build>() {
@Override
public void success(@NonNull Build response) {
mProgress.setVisibility(View.GONE);
Snackbar.make(mRoot, R.string.build_started, Snackbar.LENGTH_LONG)
.show();
App.bus().post(new BuildChangedEvent(response));
}
@Override
public void failure(Throwable t) {
Timber.e(t, null);
mProgress.setVisibility(View.GONE);
Snackbar.make(mRoot, R.string.unable_to_retry_build, Snackbar.LENGTH_LONG)
.show();
}
};
private final EasyCallback<Build> mEraseCallback = new EasyCallback<Build>() {
@Override
public void success(@NonNull Build response) {
mProgress.setVisibility(View.GONE);
Snackbar.make(mRoot, R.string.build_erased, Snackbar.LENGTH_LONG)
.show();
App.bus().post(new BuildChangedEvent(response));
}
@Override
public void failure(Throwable t) {
Timber.e(t, null);
mProgress.setVisibility(View.GONE);
Snackbar.make(mRoot, R.string.unable_to_erase_build, Snackbar.LENGTH_LONG)
.show();
}
};
View progress;
 
private final EasyCallback<Build> mCancelCallback = new EasyCallback<Build>() {
@Override
public void success(@NonNull Build response) {
mProgress.setVisibility(View.GONE);
Snackbar.make(mRoot, R.string.build_canceled, Snackbar.LENGTH_LONG)
.show();
App.bus().post(new BuildChangedEvent(response));
}
MenuItem menuItemDownload;
 
@Override
public void failure(Throwable t) {
Timber.e(t, null);
mProgress.setVisibility(View.GONE);
Snackbar.make(mRoot, R.string.unable_to_cancel_build, Snackbar.LENGTH_LONG)
.show();
}
};
Project project;
Build build;
 
private final Toolbar.OnMenuItemClickListener mOnMenuItemClickListener = new Toolbar.OnMenuItemClickListener() {
private final Toolbar.OnMenuItemClickListener onMenuItemClickListener = new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_retry:
mProgress.setVisibility(View.VISIBLE);
App.instance().getGitLab().retryBuild(mProject.getId(), mBuild.getId()).enqueue(mRetryCallback);
progress.setVisibility(View.VISIBLE);
App.get().getGitLab().retryBuild(project.getId(), build.getId())
.compose(BuildActivity.this.<Build>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomSingleObserver<Build>() {
@Override
public void error(@NonNull Throwable t) {
Timber.e(t);
progress.setVisibility(View.GONE);
Snackbar.make(root, R.string.unable_to_retry_build, Snackbar.LENGTH_LONG)
.show();
}
@Override
public void success(@NonNull Build build) {
progress.setVisibility(View.GONE);
Snackbar.make(root, R.string.build_started, Snackbar.LENGTH_LONG)
.show();
App.bus().post(new BuildChangedEvent(build));
}
});
return true;
case R.id.action_erase:
mProgress.setVisibility(View.VISIBLE);
App.instance().getGitLab().eraseBuild(mProject.getId(), mBuild.getId()).enqueue(mEraseCallback);
progress.setVisibility(View.VISIBLE);
App.get().getGitLab().eraseBuild(project.getId(), build.getId())
.compose(BuildActivity.this.<Build>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomSingleObserver<Build>() {
@Override
public void error(@NonNull Throwable t) {
Timber.e(t);
progress.setVisibility(View.GONE);
Snackbar.make(root, R.string.unable_to_erase_build, Snackbar.LENGTH_LONG)
.show();
}
@Override
public void success(@NonNull Build build) {
progress.setVisibility(View.GONE);
Snackbar.make(root, R.string.build_erased, Snackbar.LENGTH_LONG)
.show();
App.bus().post(new BuildChangedEvent(build));
}
});
return true;
case R.id.action_cancel:
mProgress.setVisibility(View.VISIBLE);
App.instance().getGitLab().cancelBuild(mProject.getId(), mBuild.getId()).enqueue(mCancelCallback);
progress.setVisibility(View.VISIBLE);
App.get().getGitLab().cancelBuild(project.getId(), build.getId())
.compose(BuildActivity.this.<Build>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomSingleObserver<Build>() {
@Override
public void error(@NonNull Throwable t) {
Timber.e(t);
progress.setVisibility(View.GONE);
Snackbar.make(root, R.string.unable_to_cancel_build, Snackbar.LENGTH_LONG)
.show();
}
@Override
public void success(@NonNull Build build) {
progress.setVisibility(View.GONE);
Snackbar.make(root, R.string.build_canceled, Snackbar.LENGTH_LONG)
.show();
App.bus().post(new BuildChangedEvent(build));
}
});
return true;
case R.id.action_download:
Account account = App.instance().getAccount();
String downloadUrl = BuildUtil.getDownloadBuildUrl(App.instance().getAccount().getServerUrl(), mProject, mBuild);
Timber.d("Downloading build: " + downloadUrl);
DownloadUtil.download(BuildActivity.this, account, downloadUrl, mBuild.getArtifactsFile().getFileName());
checkDownloadBuild();
return true;
}
return false;
Loading
Loading
@@ -147,33 +161,60 @@ public class BuildActivity extends BaseActivity {
setContentView(R.layout.activity_build);
ButterKnife.bind(this);
 
mProject = Parcels.unwrap(getIntent().getParcelableExtra(KEY_PROJECT));
mBuild = Parcels.unwrap(getIntent().getParcelableExtra(KEY_BUILD));
project = Parcels.unwrap(getIntent().getParcelableExtra(KEY_PROJECT));
build = Parcels.unwrap(getIntent().getParcelableExtra(KEY_BUILD));
 
mToolbar.setTitle(getString(R.string.build_number) + mBuild.getId());
mToolbar.setNavigationIcon(R.drawable.ic_back_24dp);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
toolbar.setTitle(getString(R.string.build_number) + build.getId());
toolbar.setNavigationIcon(R.drawable.ic_back_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
mToolbar.setSubtitle(mProject.getNameWithNamespace());
mToolbar.inflateMenu(R.menu.menu_build);
mToolbar.setOnMenuItemClickListener(mOnMenuItemClickListener);
mMenuItemDownload = mToolbar.getMenu().findItem(R.id.action_download);
mMenuItemDownload.setVisible(mBuild.getArtifactsFile() != null);
toolbar.setSubtitle(project.getNameWithNamespace());
toolbar.inflateMenu(R.menu.menu_build);
toolbar.setOnMenuItemClickListener(onMenuItemClickListener);
menuItemDownload = toolbar.getMenu().findItem(R.id.action_download);
menuItemDownload.setVisible(build.getArtifactsFile() != null);
setupTabs();
}
 
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
switch (requestCode) {
case REQUEST_PERMISSION_WRITE_STORAGE: {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
downloadBuild();
}
}
}
}
private void setupTabs() {
BuildSectionsPagerAdapter sectionsPagerAdapter = new BuildSectionsPagerAdapter(
this,
getSupportFragmentManager(),
mProject,
mBuild);
project,
build);
viewPager.setAdapter(sectionsPagerAdapter);
tabLayout.setupWithViewPager(viewPager);
}
@TargetApi(23)
private void checkDownloadBuild() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
downloadBuild();
} else {
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_PERMISSION_WRITE_STORAGE);
}
}
 
mViewPager.setAdapter(sectionsPagerAdapter);
mTabLayout.setupWithViewPager(mViewPager);
private void downloadBuild() {
Account account = App.get().getAccount();
String downloadUrl = BuildUtil.getDownloadBuildUrl(App.get().getAccount().getServerUrl(), project, build);
Timber.d("Downloading build: " + downloadUrl);
DownloadUtil.download(BuildActivity.this, account, downloadUrl, build.getArtifactsFile().getFileName());
}
}
Loading
Loading
@@ -12,13 +12,13 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
 
import com.commit451.easycallback.EasyCallback;
import com.commit451.gitlab.App;
import com.commit451.gitlab.R;
import com.commit451.gitlab.adapter.DiffAdapter;
import com.commit451.gitlab.model.api.Diff;
import com.commit451.gitlab.model.api.Project;
import com.commit451.gitlab.model.api.RepositoryCommit;
import com.commit451.gitlab.rx.CustomSingleObserver;
 
import org.parceler.Parcels;
 
Loading
Loading
@@ -26,7 +26,8 @@ import java.util.List;
 
import butterknife.BindView;
import butterknife.ButterKnife;
import retrofit2.Callback;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
 
/**
Loading
Loading
@@ -44,31 +45,21 @@ public class DiffActivity extends BaseActivity {
return intent;
}
 
@BindView(R.id.root) ViewGroup mRoot;
@BindView(R.id.toolbar) Toolbar mToolbar;
@BindView(R.id.swipe_layout) SwipeRefreshLayout mSwipeRefreshLayout;
@BindView(R.id.list) RecyclerView mDiffRecyclerView;
DiffAdapter mDiffAdapter;
@BindView(R.id.message_text) TextView mMessageText;
private Project mProject;
private RepositoryCommit mCommit;
private Callback<List<Diff>> mDiffCallback = new EasyCallback<List<Diff>>() {
@Override
public void success(@NonNull List<Diff> response) {
mSwipeRefreshLayout.setRefreshing(false);
mDiffAdapter.setData(response);
}
@Override
public void failure(Throwable t) {
mSwipeRefreshLayout.setRefreshing(false);
Timber.e(t, null);
mMessageText.setText(R.string.connection_error);
mMessageText.setVisibility(View.VISIBLE);
}
};
@BindView(R.id.root)
ViewGroup root;
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.swipe_layout)
SwipeRefreshLayout swipeRefreshLayout;
@BindView(R.id.list)
RecyclerView listDiff;
@BindView(R.id.message_text)
TextView textMessage;
DiffAdapter adapterDiff;
private Project project;
private RepositoryCommit commit;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
Loading
Loading
@@ -76,27 +67,27 @@ public class DiffActivity extends BaseActivity {
setContentView(R.layout.activity_diff);
ButterKnife.bind(this);
 
mProject = Parcels.unwrap(getIntent().getParcelableExtra(EXTRA_PROJECT));
mCommit = Parcels.unwrap(getIntent().getParcelableExtra(EXTRA_COMMIT));
project = Parcels.unwrap(getIntent().getParcelableExtra(EXTRA_PROJECT));
commit = Parcels.unwrap(getIntent().getParcelableExtra(EXTRA_COMMIT));
 
mToolbar.setNavigationIcon(R.drawable.ic_back_24dp);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
toolbar.setNavigationIcon(R.drawable.ic_back_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
mToolbar.setTitle(mCommit.getShortId());
toolbar.setTitle(commit.getShortId());
 
mDiffAdapter = new DiffAdapter(mCommit, new DiffAdapter.Listener() {
adapterDiff = new DiffAdapter(commit, new DiffAdapter.Listener() {
@Override
public void onDiffClicked(Diff diff) {
 
}
});
mDiffRecyclerView.setAdapter(mDiffAdapter);
mDiffRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
listDiff.setAdapter(adapterDiff);
listDiff.setLayoutManager(new LinearLayoutManager(this));
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
loadData();
Loading
Loading
@@ -107,15 +98,34 @@ public class DiffActivity extends BaseActivity {
}
 
private void loadData() {
mMessageText.setVisibility(View.GONE);
mSwipeRefreshLayout.post(new Runnable() {
textMessage.setVisibility(View.GONE);
swipeRefreshLayout.post(new Runnable() {
@Override
public void run() {
if (mSwipeRefreshLayout != null) {
mSwipeRefreshLayout.setRefreshing(true);
}
if (swipeRefreshLayout != null) {
swipeRefreshLayout.setRefreshing(true);
}
}
});
App.instance().getGitLab().getCommitDiff(mProject.getId(), mCommit.getId()).enqueue(mDiffCallback);
App.get().getGitLab().getCommitDiff(project.getId(), commit.getId())
.compose(this.<List<Diff>>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomSingleObserver<List<Diff>>() {
@Override
public void error(@NonNull Throwable t) {
swipeRefreshLayout.setRefreshing(false);
Timber.e(t);
textMessage.setText(R.string.connection_error);
textMessage.setVisibility(View.VISIBLE);
}
@Override
public void success(@NonNull List<Diff> diffs) {
swipeRefreshLayout.setRefreshing(false);
adapterDiff.setData(diffs);
}
});
}
}
\ No newline at end of file
Loading
Loading
@@ -21,11 +21,11 @@ import android.view.ViewGroup;
import android.webkit.MimeTypeMap;
import android.webkit.WebView;
 
import com.commit451.easycallback.EasyCallback;
import com.commit451.gitlab.App;
import com.commit451.gitlab.R;
import com.commit451.gitlab.model.api.RepositoryFile;
import com.commit451.gitlab.observable.DecodeObservableFactory;
import com.commit451.gitlab.rx.CustomSingleObserver;
import com.commit451.gitlab.rx.DecodeObservableFactory;
 
import java.io.File;
import java.io.FileOutputStream;
Loading
Loading
@@ -36,15 +36,13 @@ import java.nio.charset.Charset;
 
import butterknife.BindView;
import butterknife.ButterKnife;
import retrofit2.Callback;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
 
public class FileActivity extends BaseActivity {
 
private static final int PERMISSION_REQUEST_WRITE_STORAGE = 1337;
private static final int REQUEST_PERMISSION_WRITE_STORAGE = 1337;
 
private static final long MAX_FILE_SIZE = 1024 * 1024;
private static final String EXTRA_PROJECT_ID = "extra_project_id";
Loading
Loading
@@ -53,7 +51,9 @@ public class FileActivity extends BaseActivity {
 
@Retention(RetentionPolicy.SOURCE)
@IntDef({OPTION_SAVE, OPTION_OPEN})
public @interface Option {}
public @interface Option {
}
public static final int OPTION_SAVE = 0;
public static final int OPTION_OPEN = 1;
 
Loading
Loading
@@ -65,34 +65,23 @@ public class FileActivity extends BaseActivity {
return intent;
}
 
@BindView(R.id.root) ViewGroup mRoot;
@BindView(R.id.toolbar) Toolbar mToolbar;
@BindView(R.id.file_blob) WebView mFileBlobView;
@BindView(R.id.progress) View mProgressView;
private long mProjectId;
private String mPath;
private String mRef;
private RepositoryFile mRepositoryFile;
private String mFileName;
private byte[] mBlob;
private @Option int mOption;
private final Callback<RepositoryFile> mRepositoryFileCallback = new EasyCallback<RepositoryFile>() {
@Override
public void success(@NonNull RepositoryFile response) {
mProgressView.setVisibility(View.GONE);
bindFile(response);
}
@Override
public void failure(Throwable t) {
Timber.e(t, null);
mProgressView.setVisibility(View.GONE);
Snackbar.make(mRoot, R.string.file_load_error, Snackbar.LENGTH_SHORT)
.show();
}
};
@BindView(R.id.root)
ViewGroup root;
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.file_blob)
WebView webViewFileBlob;
@BindView(R.id.progress)
View progress;
private long projectId;
private String path;
private String ref;
private RepositoryFile repositoryFile;
private String fileName;
private byte[] blob;
@Option
private int option;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
Loading
Loading
@@ -100,27 +89,27 @@ public class FileActivity extends BaseActivity {
setContentView(R.layout.activity_file);
ButterKnife.bind(this);
 
mProjectId = getIntent().getLongExtra(EXTRA_PROJECT_ID, -1);
mPath = getIntent().getStringExtra(EXTRA_PATH);
mRef = getIntent().getStringExtra(EXTRA_REF);
projectId = getIntent().getLongExtra(EXTRA_PROJECT_ID, -1);
path = getIntent().getStringExtra(EXTRA_PATH);
ref = getIntent().getStringExtra(EXTRA_REF);
 
mToolbar.setNavigationIcon(R.drawable.ic_back_24dp);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
toolbar.setNavigationIcon(R.drawable.ic_back_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
mToolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch(item.getItemId()) {
switch (item.getItemId()) {
case R.id.action_open:
mOption = OPTION_OPEN;
option = OPTION_OPEN;
checkAccountPermission();
return true;
case R.id.action_save:
mOption = OPTION_SAVE;
option = OPTION_SAVE;
checkAccountPermission();
return true;
}
Loading
Loading
@@ -132,16 +121,35 @@ public class FileActivity extends BaseActivity {
}
 
private void loadData() {
mProgressView.setVisibility(View.VISIBLE);
App.instance().getGitLab().getFile(mProjectId, mPath, mRef).enqueue(mRepositoryFileCallback);
progress.setVisibility(View.VISIBLE);
App.get().getGitLab().getFile(projectId, path, ref)
.compose(this.<RepositoryFile>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomSingleObserver<RepositoryFile>() {
@Override
public void error(@NonNull Throwable t) {
Timber.e(t);
progress.setVisibility(View.GONE);
Snackbar.make(root, R.string.file_load_error, Snackbar.LENGTH_SHORT)
.show();
}
@Override
public void success(@NonNull RepositoryFile repositoryFile) {
progress.setVisibility(View.GONE);
bindFile(repositoryFile);
}
});
}
 
private void bindFile(RepositoryFile repositoryFile) {
mRepositoryFile = repositoryFile;
mFileName = repositoryFile.getFileName();
mToolbar.setTitle(mFileName);
this.repositoryFile = repositoryFile;
fileName = repositoryFile.getFileName();
toolbar.setTitle(fileName);
if (repositoryFile.getSize() > MAX_FILE_SIZE) {
Snackbar.make(mRoot, R.string.file_too_big, Snackbar.LENGTH_SHORT)
Snackbar.make(root, R.string.file_too_big, Snackbar.LENGTH_SHORT)
.show();
} else {
loadBlob(repositoryFile);
Loading
Loading
@@ -150,30 +158,29 @@ public class FileActivity extends BaseActivity {
 
private void loadBlob(RepositoryFile repositoryFile) {
DecodeObservableFactory.newDecode(repositoryFile.getContent())
.compose(this.<byte[]>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<byte[]>() {
@Override
public void onCompleted() {}
.subscribe(new CustomSingleObserver<byte[]>() {
 
@Override
public void onError(Throwable e) {
Snackbar.make(mRoot, R.string.failed_to_load, Snackbar.LENGTH_SHORT)
public void error(@NonNull Throwable t) {
Snackbar.make(root, R.string.failed_to_load, Snackbar.LENGTH_SHORT)
.show();
}
 
@Override
public void onNext(byte[] bytes) {
public void success(@NonNull byte[] bytes) {
bindBlob(bytes);
}
});
}
 
private void bindBlob(byte[] blob) {
mBlob = blob;
this.blob = blob;
String content;
String mimeType = null;
String extension = fileExt(mFileName);
String extension = fileExt(fileName);
if (extension != null) {
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mimeType != null) {
Loading
Loading
@@ -182,7 +189,7 @@ public class FileActivity extends BaseActivity {
}
 
if (mimeType != null && mimeType.startsWith("image/")) {
String imageURL = "data:" + mimeType + ";base64," + mRepositoryFile.getContent();
String imageURL = "data:" + mimeType + ";base64," + repositoryFile.getContent();
 
content = "<!DOCTYPE html>" +
"<html>" +
Loading
Loading
@@ -191,7 +198,7 @@ public class FileActivity extends BaseActivity {
"</body>" +
"</html>";
} else {
String text = new String(mBlob, Charset.forName("UTF-8"));
String text = new String(this.blob, Charset.forName("UTF-8"));
 
content = "<!DOCTYPE html>" +
"<html>" +
Loading
Loading
@@ -208,29 +215,29 @@ public class FileActivity extends BaseActivity {
"</html>";
}
 
mFileBlobView.loadDataWithBaseURL("file:///android_asset/", content, "text/html", "utf8", null);
mToolbar.inflateMenu(R.menu.menu_file);
webViewFileBlob.loadDataWithBaseURL("file:///android_asset/", content, "text/html", "utf8", null);
toolbar.inflateMenu(R.menu.menu_file);
}
 
@TargetApi(23)
private void checkAccountPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
if (mOption == OPTION_SAVE) {
if (option == OPTION_SAVE) {
saveBlob();
} else {
openFile();
}
} else {
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_WRITE_STORAGE);
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_PERMISSION_WRITE_STORAGE);
}
}
 
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
switch (requestCode) {
case PERMISSION_REQUEST_WRITE_STORAGE: {
case REQUEST_PERMISSION_WRITE_STORAGE: {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if (mOption == OPTION_SAVE) {
if (option == OPTION_SAVE) {
saveBlob();
} else {
openFile();
Loading
Loading
@@ -243,33 +250,33 @@ public class FileActivity extends BaseActivity {
private File saveBlob() {
String state = Environment.getExternalStorageState();
 
if (Environment.MEDIA_MOUNTED.equals(state) && mBlob != null) {
File targetFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), mFileName);
if (Environment.MEDIA_MOUNTED.equals(state) && blob != null) {
File targetFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName);
 
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(targetFile);
outputStream.write(mBlob);
outputStream.write(blob);
 
Snackbar.make(mRoot, getString(R.string.file_saved), Snackbar.LENGTH_SHORT)
Snackbar.make(root, getString(R.string.file_saved), Snackbar.LENGTH_SHORT)
.show();
 
return targetFile;
} catch (IOException e) {
Timber.e(e, null);
Snackbar.make(mRoot, getString(R.string.save_error), Snackbar.LENGTH_SHORT)
Timber.e(e);
Snackbar.make(root, getString(R.string.save_error), Snackbar.LENGTH_SHORT)
.show();
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
Timber.e(e, null);
Timber.e(e);
}
}
}
} else {
Snackbar.make(mRoot, getString(R.string.save_error), Snackbar.LENGTH_SHORT)
Snackbar.make(root, getString(R.string.save_error), Snackbar.LENGTH_SHORT)
.show();
}
 
Loading
Loading
@@ -279,7 +286,7 @@ public class FileActivity extends BaseActivity {
private void openFile() {
File file = saveBlob();
if (file == null) {
Snackbar.make(mRoot, getString(R.string.open_error), Snackbar.LENGTH_SHORT)
Snackbar.make(root, getString(R.string.open_error), Snackbar.LENGTH_SHORT)
.show();
return;
}
Loading
Loading
@@ -296,8 +303,8 @@ public class FileActivity extends BaseActivity {
try {
startActivity(intent);
} catch (ActivityNotFoundException | SecurityException e) {
Timber.e(e, null);
Snackbar.make(mRoot, getString(R.string.open_error), Snackbar.LENGTH_SHORT)
Timber.e(e);
Snackbar.make(root, getString(R.string.open_error), Snackbar.LENGTH_SHORT)
.show();
}
}
Loading
Loading
Loading
Loading
@@ -17,20 +17,22 @@ import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.ImageView;
 
import com.commit451.alakazam.Alakazam;
import com.commit451.easel.Easel;
import com.commit451.easycallback.EasyCallback;
import com.commit451.gitlab.App;
import com.commit451.gitlab.R;
import com.commit451.gitlab.adapter.GroupPagerAdapter;
import com.commit451.gitlab.model.api.Group;
import com.commit451.gitlab.model.api.GroupDetail;
import com.commit451.gitlab.rx.CustomSingleObserver;
import com.commit451.gitlab.transformation.PaletteTransformation;
 
import org.parceler.Parcels;
 
import butterknife.BindView;
import butterknife.ButterKnife;
import retrofit2.Callback;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
 
/**
Loading
Loading
@@ -53,28 +55,20 @@ public class GroupActivity extends BaseActivity {
return intent;
}
 
@BindView(R.id.root) View mRoot;
@BindView(R.id.toolbar) Toolbar mToolbar;
@BindView(R.id.collapsing_toolbar) CollapsingToolbarLayout mCollapsingToolbarLayout;
@BindView(R.id.viewpager) ViewPager mViewPager;
@BindView(R.id.tabs) TabLayout mTabLayout;
@BindView(R.id.backdrop) ImageView mBackdrop;
@BindView(R.id.progress) View mProgress;
private final Callback<GroupDetail> mGroupCallback = new EasyCallback<GroupDetail>() {
@Override
public void success(@NonNull GroupDetail response) {
mProgress.setVisibility(View.GONE);
bind(response);
}
@Override
public void failure(Throwable t) {
Timber.e(t, null);
mProgress.setVisibility(View.GONE);
showError();
}
};
@BindView(R.id.root)
View root;
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.collapsing_toolbar)
CollapsingToolbarLayout collapsingToolbarLayout;
@BindView(R.id.viewpager)
ViewPager viewPager;
@BindView(R.id.tabs)
TabLayout tabLayout;
@BindView(R.id.backdrop)
ImageView backdrop;
@BindView(R.id.progress)
View progress;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
Loading
Loading
@@ -84,8 +78,8 @@ public class GroupActivity extends BaseActivity {
 
// Default content and scrim colors
 
mToolbar.setNavigationIcon(R.drawable.ic_back_24dp);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
toolbar.setNavigationIcon(R.drawable.ic_back_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
Loading
Loading
@@ -96,9 +90,27 @@ public class GroupActivity extends BaseActivity {
Group group = Parcels.unwrap(getIntent().getParcelableExtra(KEY_GROUP));
bind(group);
} else {
mProgress.setVisibility(View.VISIBLE);
progress.setVisibility(View.VISIBLE);
long groupId = getIntent().getLongExtra(KEY_GROUP_ID, -1);
App.instance().getGitLab().getGroup(groupId).enqueue(mGroupCallback);
App.get().getGitLab().getGroup(groupId)
.compose(this.<GroupDetail>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomSingleObserver<GroupDetail>() {
@Override
public void error(@NonNull Throwable t) {
Timber.e(t);
progress.setVisibility(View.GONE);
showError();
}
@Override
public void success(@NonNull GroupDetail groupDetail) {
progress.setVisibility(View.GONE);
bind(groupDetail);
}
});
}
}
 
Loading
Loading
@@ -108,21 +120,22 @@ public class GroupActivity extends BaseActivity {
}
 
private void bind(Group group) {
App.instance().getPicasso()
App.get().getPicasso()
.load(group.getAvatarUrl())
.transform(PaletteTransformation.instance())
.into(mBackdrop, new PaletteTransformation.PaletteCallback(mBackdrop) {
.into(backdrop, new PaletteTransformation.PaletteCallback(backdrop) {
@Override
protected void onSuccess(Palette palette) {
bindPalette(palette);
}
 
@Override
public void onError() {}
public void onError() {
}
});
 
mViewPager.setAdapter(new GroupPagerAdapter(this, getSupportFragmentManager(), group));
mTabLayout.setupWithViewPager(mViewPager);
viewPager.setAdapter(new GroupPagerAdapter(this, getSupportFragmentManager(), group));
tabLayout.setupWithViewPager(viewPager);
}
 
private void bindPalette(Palette palette) {
Loading
Loading
@@ -131,29 +144,29 @@ public class GroupActivity extends BaseActivity {
int darkerColor = Easel.getDarkerColor(vibrantColor);
 
if (Build.VERSION.SDK_INT >= 21) {
Easel.getNavigationBarColorAnimator(getWindow(), darkerColor)
Alakazam.navigationBarColorAnimator(getWindow(), darkerColor)
.setDuration(animationTime)
.start();
}
 
ObjectAnimator.ofObject(mCollapsingToolbarLayout, "contentScrimColor", new ArgbEvaluator(),
ObjectAnimator.ofObject(collapsingToolbarLayout, "contentScrimColor", new ArgbEvaluator(),
Easel.getThemeAttrColor(this, R.attr.colorPrimary), vibrantColor)
.setDuration(animationTime)
.start();
 
ObjectAnimator.ofObject(mCollapsingToolbarLayout, "statusBarScrimColor", new ArgbEvaluator(),
ObjectAnimator.ofObject(collapsingToolbarLayout, "statusBarScrimColor", new ArgbEvaluator(),
Easel.getThemeAttrColor(this, R.attr.colorPrimaryDark), darkerColor)
.setDuration(animationTime)
.start();
 
ObjectAnimator.ofObject(mToolbar, "titleTextColor", new ArgbEvaluator(),
ObjectAnimator.ofObject(toolbar, "titleTextColor", new ArgbEvaluator(),
Color.WHITE, palette.getDarkMutedColor(Color.BLACK))
.setDuration(animationTime)
.start();
}
 
private void showError() {
Snackbar.make(mRoot, R.string.connection_error, Snackbar.LENGTH_SHORT)
Snackbar.make(root, R.string.connection_error, Snackbar.LENGTH_SHORT)
.show();
}
}
Loading
Loading
@@ -13,15 +13,16 @@ import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.TextView;
 
import com.commit451.easycallback.EasyCallback;
import com.alexgwyn.recyclerviewsquire.DynamicGridLayoutManager;
import com.commit451.gitlab.App;
import com.commit451.gitlab.R;
import com.commit451.gitlab.adapter.GroupAdapter;
import com.commit451.gitlab.event.CloseDrawerEvent;
import com.commit451.gitlab.event.ReloadDataEvent;
import com.commit451.gitlab.model.api.Group;
import com.commit451.gitlab.navigation.Navigator;
import com.commit451.gitlab.util.DynamicGridLayoutManager;
import com.commit451.gitlab.util.PaginationUtil;
import com.commit451.gitlab.rx.CustomResponseSingleObserver;
import com.commit451.gitlab.util.LinkHeaderParser;
import com.commit451.gitlab.viewHolder.GroupViewHolder;
 
import org.greenrobot.eventbus.Subscribe;
Loading
Loading
@@ -30,7 +31,9 @@ import java.util.List;
 
import butterknife.BindView;
import butterknife.ButterKnife;
import retrofit2.Callback;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import retrofit2.Response;
import timber.log.Timber;
 
/**
Loading
Loading
@@ -43,76 +46,40 @@ public class GroupsActivity extends BaseActivity {
return intent;
}
 
@BindView(R.id.drawer_layout) DrawerLayout mDrawerLayout;
@BindView(R.id.toolbar) Toolbar mToolbar;
@BindView(R.id.swipe_layout) SwipeRefreshLayout mSwipeRefreshLayout;
@BindView(R.id.list) RecyclerView mGroupRecyclerView;
@BindView(R.id.message_text) TextView mMessageText;
GroupAdapter mGroupAdapter;
DynamicGridLayoutManager mGroupLayoutManager;
@BindView(R.id.drawer_layout)
DrawerLayout drawerLayout;
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.swipe_layout)
SwipeRefreshLayout swipeRefreshLayout;
@BindView(R.id.list)
RecyclerView listGroups;
@BindView(R.id.message_text)
TextView textMessage;
 
private Uri mNextPageUrl;
private boolean mLoading = false;
EventReceiver mEventReceiver;
GroupAdapter adapterGroup;
DynamicGridLayoutManager layoutManager;
 
private final RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() {
private Uri nextPageUrl;
private boolean loading = false;
private final RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
int visibleItemCount = mGroupLayoutManager.getChildCount();
int totalItemCount = mGroupLayoutManager.getItemCount();
int firstVisibleItem = mGroupLayoutManager.findFirstVisibleItemPosition();
if (firstVisibleItem + visibleItemCount >= totalItemCount && !mLoading && mNextPageUrl != null) {
int visibleItemCount = layoutManager.getChildCount();
int totalItemCount = layoutManager.getItemCount();
int firstVisibleItem = layoutManager.findFirstVisibleItemPosition();
if (firstVisibleItem + visibleItemCount >= totalItemCount && !loading && nextPageUrl != null) {
loadMore();
}
}
};
 
private final Callback<List<Group>> mGroupsCallback = new EasyCallback<List<Group>>() {
@Override
public void success(@NonNull List<Group> response) {
mLoading = false;
mSwipeRefreshLayout.setRefreshing(false);
if (response.isEmpty()) {
mMessageText.setText(R.string.no_groups);
mMessageText.setVisibility(View.VISIBLE);
mGroupRecyclerView.setVisibility(View.GONE);
} else {
mGroupAdapter.setGroups(response);
mMessageText.setVisibility(View.GONE);
mGroupRecyclerView.setVisibility(View.VISIBLE);
}
}
@Override
public void failure(Throwable t) {
Timber.e(t, null);
mSwipeRefreshLayout.setRefreshing(false);
mLoading = false;
mMessageText.setVisibility(View.VISIBLE);
mMessageText.setText(R.string.connection_error);
}
};
private final Callback<List<Group>> mMoreGroupsCallback = new EasyCallback<List<Group>>() {
@Override
public void success(@NonNull List<Group> response) {
mLoading = false;
mGroupAdapter.addGroups(response);
mNextPageUrl = PaginationUtil.parse(getResponse()).getNext();
}
@Override
public void failure(Throwable t) {
Timber.e(t, null);
mLoading = false;
}
};
private final GroupAdapter.Listener mGroupAdapterListener = new GroupAdapter.Listener() {
private final GroupAdapter.Listener groupAdapterListener = new GroupAdapter.Listener() {
@Override
public void onGroupClicked(Group group, GroupViewHolder groupViewHolder) {
Navigator.navigateToGroup(GroupsActivity.this, groupViewHolder.mImageView, group);
Navigator.navigateToGroup(GroupsActivity.this, groupViewHolder.image, group);
}
};
 
Loading
Loading
@@ -121,86 +88,136 @@ public class GroupsActivity extends BaseActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_groups);
ButterKnife.bind(this);
mEventReceiver = new EventReceiver();
App.bus().register(mEventReceiver);
App.bus().register(this);
 
mToolbar.setTitle(R.string.nav_groups);
mToolbar.setNavigationIcon(R.drawable.ic_menu_24dp);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
toolbar.setTitle(R.string.nav_groups);
toolbar.setNavigationIcon(R.drawable.ic_menu_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDrawerLayout.openDrawer(GravityCompat.START);
drawerLayout.openDrawer(GravityCompat.START);
}
});
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
load();
}
});
mMessageText.setOnClickListener(new View.OnClickListener() {
textMessage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
load();
}
});
mGroupLayoutManager = new DynamicGridLayoutManager(this);
mGroupLayoutManager.setMinimumWidthDimension(R.dimen.user_list_image_size);
mGroupRecyclerView.setLayoutManager(mGroupLayoutManager);
mGroupAdapter = new GroupAdapter(mGroupAdapterListener);
mGroupRecyclerView.setAdapter(mGroupAdapter);
mGroupRecyclerView.addOnScrollListener(mOnScrollListener);
layoutManager = new DynamicGridLayoutManager(this);
layoutManager.setMinimumWidthDimension(R.dimen.user_list_image_size);
listGroups.setLayoutManager(layoutManager);
adapterGroup = new GroupAdapter(groupAdapterListener);
listGroups.setAdapter(adapterGroup);
listGroups.addOnScrollListener(onScrollListener);
load();
}
 
@Override
protected void onDestroy() {
super.onDestroy();
App.bus().unregister(mEventReceiver);
App.bus().unregister(this);
}
 
private void load() {
mMessageText.setVisibility(View.GONE);
mSwipeRefreshLayout.post(new Runnable() {
textMessage.setVisibility(View.GONE);
swipeRefreshLayout.post(new Runnable() {
@Override
public void run() {
if (mSwipeRefreshLayout != null) {
mSwipeRefreshLayout.setRefreshing(true);
if (swipeRefreshLayout != null) {
swipeRefreshLayout.setRefreshing(true);
}
}
});
 
mNextPageUrl = null;
mLoading = true;
App.instance().getGitLab().getGroups().enqueue(mGroupsCallback);
nextPageUrl = null;
loading = true;
App.get().getGitLab().getGroups()
.compose(this.<Response<List<Group>>>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomResponseSingleObserver<List<Group>>() {
@Override
public void error(@NonNull Throwable e) {
Timber.e(e);
swipeRefreshLayout.setRefreshing(false);
loading = false;
textMessage.setVisibility(View.VISIBLE);
textMessage.setText(R.string.connection_error);
}
@Override
public void responseSuccess(@NonNull List<Group> groups) {
loading = false;
swipeRefreshLayout.setRefreshing(false);
if (groups.isEmpty()) {
textMessage.setText(R.string.no_groups);
textMessage.setVisibility(View.VISIBLE);
listGroups.setVisibility(View.GONE);
} else {
adapterGroup.setGroups(groups);
textMessage.setVisibility(View.GONE);
listGroups.setVisibility(View.VISIBLE);
nextPageUrl = LinkHeaderParser.parse(response()).getNext();
}
}
});
}
 
private void loadMore() {
if (mNextPageUrl == null) {
if (nextPageUrl == null) {
return;
}
 
mSwipeRefreshLayout.post(new Runnable() {
swipeRefreshLayout.post(new Runnable() {
@Override
public void run() {
if (mSwipeRefreshLayout != null) {
mSwipeRefreshLayout.setRefreshing(true);
if (swipeRefreshLayout != null) {
swipeRefreshLayout.setRefreshing(true);
}
}
});
 
mLoading = true;
Timber.d("loadMore called for %s", mNextPageUrl);
App.instance().getGitLab().getGroups(mNextPageUrl.toString()).enqueue(mMoreGroupsCallback);
loading = true;
Timber.d("loadMore called for %s", nextPageUrl);
App.get().getGitLab().getGroups(nextPageUrl.toString())
.compose(this.<Response<List<Group>>>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomResponseSingleObserver<List<Group>>() {
@Override
public void error(@NonNull Throwable e) {
Timber.e(e);
loading = false;
}
@Override
public void responseSuccess(@NonNull List<Group> groups) {
loading = false;
adapterGroup.addGroups(groups);
nextPageUrl = LinkHeaderParser.parse(response()).getNext();
}
});
}
 
private class EventReceiver {
@Subscribe
public void onCloseDrawerEvent(CloseDrawerEvent event) {
drawerLayout.closeDrawers();
}
 
@Subscribe
public void onCloseDrawerEvent(CloseDrawerEvent event) {
mDrawerLayout.closeDrawers();
}
@Subscribe
public void onReloadData(ReloadDataEvent event) {
load();
}
}
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