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

Merged branch develop into master

parents 5212b54f 747021e0
No related branches found
No related tags found
No related merge requests found
Pipeline #
Showing
with 1544 additions and 1791 deletions
This diff is collapsed.
package com.commit451.gitlab.activity
import android.app.Activity
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.security.KeyChain
import android.support.design.widget.Snackbar
import android.support.design.widget.TextInputLayout
import android.support.v7.widget.Toolbar
import android.text.method.LinkMovementMethod
import android.view.View
import android.widget.EditText
import android.widget.TextView
import butterknife.BindView
import butterknife.ButterKnife
import butterknife.OnClick
import butterknife.OnEditorAction
import com.bluelinelabs.logansquare.LoganSquare
import com.commit451.gitlab.App
import com.commit451.gitlab.BuildConfig
import com.commit451.gitlab.R
import com.commit451.gitlab.api.GitLabFactory
import com.commit451.gitlab.api.OkHttpClientFactory
import com.commit451.gitlab.data.Prefs
import com.commit451.gitlab.dialog.HttpLoginDialog
import com.commit451.gitlab.event.LoginEvent
import com.commit451.gitlab.event.ReloadDataEvent
import com.commit451.gitlab.extension.checkValid
import com.commit451.gitlab.extension.setup
import com.commit451.gitlab.model.Account
import com.commit451.gitlab.model.api.Message
import com.commit451.gitlab.model.api.UserFull
import com.commit451.gitlab.model.api.UserLogin
import com.commit451.gitlab.navigation.Navigator
import com.commit451.gitlab.rx.CustomResponseSingleObserver
import com.commit451.gitlab.ssl.CustomHostnameVerifier
import com.commit451.gitlab.ssl.CustomKeyManager
import com.commit451.gitlab.ssl.X509CertificateException
import com.commit451.gitlab.ssl.X509Util
import com.commit451.teleprinter.Teleprinter
import com.jakewharton.retrofit2.adapter.rxjava2.HttpException
import io.reactivex.Single
import okhttp3.Credentials
import okhttp3.HttpUrl
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Response
import timber.log.Timber
import java.io.IOException
import java.net.ConnectException
import java.security.cert.CertificateEncodingException
import java.util.*
import java.util.regex.Pattern
import javax.net.ssl.SSLHandshakeException
import javax.net.ssl.SSLPeerUnverifiedException
class LoginActivity : BaseActivity() {
companion object {
private val EXTRA_SHOW_CLOSE = "show_close"
private val REQUEST_PRIVATE_TOKEN = 123
private val sTokenPattern = Pattern.compile("^[A-Za-z0-9-_]*$")
@JvmOverloads fun newIntent(context: Context, showClose: Boolean = false): Intent {
val intent = Intent(context, LoginActivity::class.java)
intent.putExtra(EXTRA_SHOW_CLOSE, showClose)
return intent
}
}
@BindView(R.id.root) lateinit var root: View
@BindView(R.id.toolbar) lateinit var toolbar: Toolbar
@BindView(R.id.url_hint) lateinit var textInputLayoutUrl: TextInputLayout
@BindView(R.id.url_input) lateinit var textUrl: TextView
@BindView(R.id.user_input_hint) lateinit var textInputLayoutUser: TextInputLayout
@BindView(R.id.user_input) lateinit var textUser: EditText
@BindView(R.id.password_hint) lateinit var textInputLayoutPassword: TextInputLayout
@BindView(R.id.password_input) lateinit var textPassword: TextView
@BindView(R.id.token_hint) lateinit var textInputLayoutToken: TextInputLayout
@BindView(R.id.token_input) lateinit var textToken: TextView
@BindView(R.id.normal_login) lateinit var rootNormalLogin: View
@BindView(R.id.token_login) lateinit var rootTokenLogin: View
@BindView(R.id.progress) lateinit var progress: View
lateinit var teleprinter: Teleprinter
var isNormalLogin = true
var account: Account = Account()
@OnEditorAction(R.id.password_input, R.id.token_input)
fun onPasswordEditorAction(): Boolean {
onLoginClick()
return true
}
@OnClick(R.id.login_button)
fun onLoginClick() {
teleprinter.hideKeyboard()
if (!textInputLayoutUrl.checkValid()) {
return
}
if (!verifyUrl()) {
return
}
val uri = Uri.parse(textInputLayoutUrl.editText!!.text.toString())
if (isNormalLogin) {
val valid = textInputLayoutUser.checkValid() and textInputLayoutPassword.checkValid()
if (!valid) {
return
}
} else {
if (!textInputLayoutToken.checkValid()) {
return
}
if (!sTokenPattern.matcher(textToken.text).matches()) {
textInputLayoutToken.error = getString(R.string.not_a_valid_private_token)
return
} else {
textInputLayoutToken.error = null
}
}
if (isAlreadySignedIn(uri.toString(), if (isNormalLogin) textUser.text.toString() else textToken.text.toString())) {
Snackbar.make(root, getString(R.string.already_logged_in), Snackbar.LENGTH_LONG)
.show()
return
}
account = Account()
account.serverUrl = uri
login()
}
@OnClick(R.id.button_open_login_page)
fun onOpenLoginPageClicked() {
if (verifyUrl()) {
val url = textInputLayoutUrl.editText!!.text.toString()
Navigator.navigateToWebSignin(this, url, true, REQUEST_PRIVATE_TOKEN)
}
}
@OnClick(R.id.button_open_login_page_for_personal_access)
fun onOpenLoginPageForPersonalAccessTokenClicked() {
if (verifyUrl()) {
val url = textInputLayoutUrl.editText!!.text.toString()
Navigator.navigateToWebSignin(this, url, false, REQUEST_PRIVATE_TOKEN)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
ButterKnife.bind(this)
teleprinter = Teleprinter(this)
val showClose = intent.getBooleanExtra(EXTRA_SHOW_CLOSE, false)
toolbar.inflateMenu(R.menu.advanced_login)
toolbar.setOnMenuItemClickListener(Toolbar.OnMenuItemClickListener { item ->
when (item.itemId) {
R.id.action_advanced_login -> {
val isNormalLogin = rootNormalLogin.visibility == View.VISIBLE
if (isNormalLogin) {
rootNormalLogin.visibility = View.GONE
rootTokenLogin.visibility = View.VISIBLE
item.setTitle(R.string.normal_link)
this@LoginActivity.isNormalLogin = false
} else {
rootNormalLogin.visibility = View.VISIBLE
rootTokenLogin.visibility = View.GONE
item.setTitle(R.string.advanced_login)
this@LoginActivity.isNormalLogin = true
}
return@OnMenuItemClickListener true
}
}
false
})
if (showClose) {
toolbar.setNavigationIcon(R.drawable.ic_close_24dp)
toolbar.setNavigationOnClickListener { onBackPressed() }
}
textUrl.setText(R.string.url_gitlab)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_PRIVATE_TOKEN -> if (resultCode == Activity.RESULT_OK) {
val token = data?.getStringExtra(WebLoginActivity.EXTRA_TOKEN)
textInputLayoutToken.editText!!.setText(token)
}
}
}
fun connect(byAuth: Boolean) {
progress.visibility = View.VISIBLE
progress.alpha = 0.0f
progress.animate().alpha(1.0f)
if (byAuth) {
connectByAuth()
} else {
connectByToken()
}
}
fun connectByAuth() {
val gitlabClientBuilder = OkHttpClientFactory.create(account)
if (BuildConfig.DEBUG) {
gitlabClientBuilder.addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
}
val gitLab = GitLabFactory.create(account, gitlabClientBuilder.build())
if (textUser.text.toString().contains("@")) {
attemptLogin(gitLab.loginWithEmail(textUser.text.toString(), textPassword.text.toString()))
} else {
attemptLogin(gitLab.loginWithEmail(textUser.text.toString(), textPassword.text.toString()))
}
}
fun attemptLogin(observable: Single<Response<UserLogin>>) {
observable
.setup(bindToLifecycle())
.subscribe(object : CustomResponseSingleObserver<UserLogin>() {
override fun error(e: Throwable) {
Timber.e(e)
if (e is HttpException) {
handleConnectionResponse(response())
} else {
handleConnectionError(e)
}
}
override fun responseSuccess(userLogin: UserLogin) {
account.privateToken = userLogin.privateToken
loadUser()
}
})
}
fun connectByToken() {
account.privateToken = textToken.text.toString()
loadUser()
}
fun loginWithPrivateToken() {
KeyChain.choosePrivateKeyAlias(this, { alias ->
account.privateKeyAlias = alias
if (alias != null) {
if (!CustomKeyManager.isCached(alias)) {
CustomKeyManager.cache(this@LoginActivity, alias, object : CustomKeyManager.KeyCallback {
override fun onSuccess(entry: CustomKeyManager.KeyEntry) {
runOnUiThread { login() }
}
override fun onError(e: Exception) {
account.privateKeyAlias = null
Timber.e(e, "Failed to load private key")
}
})
} else {
runOnUiThread { login() }
}
}
}, null, null, account.serverUrl.host, account.serverUrl.port, null)
}
fun verifyUrl(): Boolean {
val url = textUrl.text.toString()
var uri: Uri? = null
try {
if (HttpUrl.parse(url) != null) {
uri = Uri.parse(url)
}
} catch (e: Exception) {
Timber.e(e)
}
if (uri == null) {
textInputLayoutUrl.error = getString(R.string.not_a_valid_url)
return false
} else {
textInputLayoutUrl.error = null
}
if (url[url.length - 1] != '/') {
textInputLayoutUrl.error = getString(R.string.please_end_your_url_with_a_slash)
return false
} else {
textInputLayoutUrl.error = null
}
return true
}
fun login() {
// This seems useless - But believe me, it makes everything work! Don't remove it.
// (OkHttpClientFactory caches the clients and needs a new account to recreate them)
val newAccount = Account()
newAccount.serverUrl = account.serverUrl
newAccount.trustedCertificate = account.trustedCertificate
newAccount.trustedHostname = account.trustedHostname
newAccount.privateKeyAlias = account.privateKeyAlias
newAccount.authorizationHeader = account.authorizationHeader
account = newAccount
if (isNormalLogin) {
connect(true)
} else {
connect(false)
}
}
fun loadUser() {
val gitlabClientBuilder = OkHttpClientFactory.create(account, false)
if (BuildConfig.DEBUG) {
gitlabClientBuilder.addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
}
val gitLab = GitLabFactory.create(account, gitlabClientBuilder.build())
gitLab.getThisUser()
.setup(bindToLifecycle())
.subscribe(object : CustomResponseSingleObserver<UserFull>() {
override fun error(e: Throwable) {
Timber.e(e)
if (e is HttpException) {
handleConnectionResponse(response())
} else {
handleConnectionError(e)
}
}
override fun responseSuccess(userFull: UserFull) {
progress.visibility = View.GONE
account.user = userFull
account.lastUsed = Date()
Prefs.addAccount(account)
App.get().setAccount(account)
App.bus().post(LoginEvent(account))
//This is mostly for if projects already exists, then we will reload the data
App.bus().post(ReloadDataEvent())
Navigator.navigateToStartingActivity(this@LoginActivity)
finish()
}
})
}
fun handleConnectionError(t: Throwable) {
progress.visibility = View.GONE
if (t is SSLHandshakeException && t.cause is X509CertificateException) {
account.trustedCertificate = null
var fingerprint: String? = null
try {
fingerprint = X509Util.getFingerPrint((t.cause as X509CertificateException).chain[0])
} catch (e: CertificateEncodingException) {
Timber.e(e)
}
val finalFingerprint = fingerprint
val d = AlertDialog.Builder(this)
.setTitle(R.string.certificate_title)
.setMessage(String.format(resources.getString(R.string.certificate_message), finalFingerprint))
.setPositiveButton(R.string.ok_button) { dialog, which ->
if (finalFingerprint != null) {
account.trustedCertificate = finalFingerprint
login()
}
dialog.dismiss()
}
.setNegativeButton(R.string.cancel_button) { dialog, which -> dialog.dismiss() }
.show()
(d.findViewById(android.R.id.message) as TextView).movementMethod = LinkMovementMethod.getInstance()
} else if (t is SSLPeerUnverifiedException && t.message?.toLowerCase()!!.contains("hostname")) {
account.trustedHostname = null
val finalHostname = CustomHostnameVerifier.lastFailedHostname
val d = AlertDialog.Builder(this)
.setTitle(R.string.hostname_title)
.setMessage(R.string.hostname_message)
.setPositiveButton(R.string.ok_button) { dialog, which ->
if (finalHostname != null) {
account.trustedHostname = finalHostname
login()
}
dialog.dismiss()
}
.setNegativeButton(R.string.cancel_button) { dialog, which -> dialog.dismiss() }
.show()
(d.findViewById(android.R.id.message) as TextView).movementMethod = LinkMovementMethod.getInstance()
} else if (t is ConnectException) {
Snackbar.make(root, t.message!!, Snackbar.LENGTH_LONG)
.show()
} else {
Snackbar.make(root, getString(R.string.login_error), Snackbar.LENGTH_LONG)
.show()
}
}
fun handleConnectionResponse(response: Response<*>) {
progress.visibility = View.GONE
when (response.code()) {
401 -> {
account.authorizationHeader = null
val header = response.headers().get("WWW-Authenticate")
if (header != null) {
handleBasicAuthentication(response)
return
}
var errorMessage = getString(R.string.login_unauthorized)
try {
val message = LoganSquare.parse(response.errorBody().byteStream(), Message::class.java)
if (message != null && message.message != null) {
errorMessage = message.message
}
} catch (e: IOException) {
Timber.e(e)
}
Snackbar.make(root, errorMessage, Snackbar.LENGTH_LONG)
.show()
return
}
404 -> {
Snackbar.make(root, getString(R.string.login_404_error), Snackbar.LENGTH_LONG)
.show()
Snackbar.make(root, getString(R.string.login_error), Snackbar.LENGTH_LONG)
.show()
}
else -> Snackbar.make(root, getString(R.string.login_error), Snackbar.LENGTH_LONG).show()
}
}
fun handleBasicAuthentication(response: Response<*>) {
val header = response.headers().get("WWW-Authenticate").trim { it <= ' ' }
if (!header.startsWith("Basic")) {
Snackbar.make(root, getString(R.string.login_unsupported_authentication), Snackbar.LENGTH_LONG)
.show()
return
}
val realmStart = header.indexOf('"') + 1
val realmEnd = header.lastIndexOf('"')
var realm = ""
if (realmStart > 0 && realmEnd > -1) {
realm = header.substring(realmStart, realmEnd)
}
val dialog = HttpLoginDialog(this, realm, object : HttpLoginDialog.LoginListener {
override fun onLogin(username: String, password: String) {
account.authorizationHeader = Credentials.basic(username, password)
login()
}
override fun onCancel() {}
})
dialog.show()
}
fun isAlreadySignedIn(url: String, usernameOrEmailOrPrivateToken: String): Boolean {
val accounts = Prefs.getAccounts()
for (account in accounts) {
if (account.serverUrl == Uri.parse(url)) {
if (usernameOrEmailOrPrivateToken == account.user.username
|| usernameOrEmailOrPrivateToken.equals(account.user.email, ignoreCase = true)
|| usernameOrEmailOrPrivateToken.equals(account.privateToken, ignoreCase = true)) {
return true
}
}
}
return false
}
}
package com.commit451.gitlab.activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TabLayout;
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.gitlab.App;
import com.commit451.gitlab.R;
import com.commit451.gitlab.adapter.MergeRequestSectionsPagerAdapter;
import com.commit451.gitlab.event.MergeRequestChangedEvent;
import com.commit451.gitlab.model.api.MergeRequest;
import com.commit451.gitlab.model.api.Project;
import com.commit451.gitlab.rx.CustomResponseSingleObserver;
import com.jakewharton.retrofit2.adapter.rxjava2.HttpException;
import org.parceler.Parcels;
import butterknife.BindView;
import butterknife.ButterKnife;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import retrofit2.Response;
import timber.log.Timber;
/**
* Shows the details of a merge request
*/
public class MergeRequestActivity extends BaseActivity {
private static final String KEY_PROJECT = "key_project";
private static final String KEY_MERGE_REQUEST = "key_merge_request";
public static Intent newIntent(Context context, Project project, MergeRequest mergeRequest) {
Intent intent = new Intent(context, MergeRequestActivity.class);
intent.putExtra(KEY_PROJECT, Parcels.wrap(project));
intent.putExtra(KEY_MERGE_REQUEST, Parcels.wrap(mergeRequest));
return intent;
}
@BindView(R.id.root)
ViewGroup root;
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.tabs)
TabLayout tabLayout;
@BindView(R.id.pager)
ViewPager viewPager;
@BindView(R.id.progress)
View progress;
Project project;
MergeRequest mergeRequest;
private final Toolbar.OnMenuItemClickListener onMenuItemClickListener = new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_merge:
progress.setVisibility(View.VISIBLE);
App.get().getGitLab().acceptMergeRequest(project.getId(), mergeRequest.getId())
.compose(MergeRequestActivity.this.<Response<MergeRequest>>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomResponseSingleObserver<MergeRequest>() {
@Override
public void error(@NonNull Throwable e) {
Timber.e(e);
progress.setVisibility(View.GONE);
String message = getString(R.string.unable_to_merge);
if (e instanceof HttpException) {
int code = ((HttpException) e).response().code();
if (code == 406) {
message = getString(R.string.merge_request_already_merged_or_closed);
}
}
Snackbar.make(root, message, Snackbar.LENGTH_LONG)
.show();
}
@Override
public void responseSuccess(@NonNull MergeRequest mergeRequest) {
progress.setVisibility(View.GONE);
Snackbar.make(root, R.string.merge_request_accepted, Snackbar.LENGTH_LONG)
.show();
App.bus().post(new MergeRequestChangedEvent(mergeRequest));
}
});
return true;
}
return false;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_merge_request);
ButterKnife.bind(this);
project = Parcels.unwrap(getIntent().getParcelableExtra(KEY_PROJECT));
mergeRequest = Parcels.unwrap(getIntent().getParcelableExtra(KEY_MERGE_REQUEST));
toolbar.setTitle(getString(R.string.merge_request_number) + mergeRequest.getIid());
toolbar.setNavigationIcon(R.drawable.ic_back_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
toolbar.setSubtitle(project.getNameWithNamespace());
toolbar.inflateMenu(R.menu.menu_merge_request);
toolbar.setOnMenuItemClickListener(onMenuItemClickListener);
setupTabs();
}
private void setupTabs() {
MergeRequestSectionsPagerAdapter sectionsPagerAdapter = new MergeRequestSectionsPagerAdapter(
this,
getSupportFragmentManager(),
project,
mergeRequest);
viewPager.setAdapter(sectionsPagerAdapter);
tabLayout.setupWithViewPager(viewPager);
}
}
package com.commit451.gitlab.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.support.design.widget.Snackbar
import android.support.design.widget.TabLayout
import android.support.v4.view.ViewPager
import android.support.v7.widget.Toolbar
import android.view.View
import android.view.ViewGroup
import butterknife.BindView
import butterknife.ButterKnife
import com.commit451.gitlab.App
import com.commit451.gitlab.R
import com.commit451.gitlab.adapter.MergeRequestSectionsPagerAdapter
import com.commit451.gitlab.event.MergeRequestChangedEvent
import com.commit451.gitlab.extension.setup
import com.commit451.gitlab.model.api.MergeRequest
import com.commit451.gitlab.model.api.Project
import com.commit451.gitlab.rx.CustomResponseSingleObserver
import com.jakewharton.retrofit2.adapter.rxjava2.HttpException
import org.parceler.Parcels
import timber.log.Timber
/**
* Shows the details of a merge request
*/
class MergeRequestActivity : BaseActivity() {
companion object {
private val KEY_PROJECT = "key_project"
private val KEY_MERGE_REQUEST = "key_merge_request"
fun newIntent(context: Context, project: Project, mergeRequest: MergeRequest): Intent {
val intent = Intent(context, MergeRequestActivity::class.java)
intent.putExtra(KEY_PROJECT, Parcels.wrap(project))
intent.putExtra(KEY_MERGE_REQUEST, Parcels.wrap(mergeRequest))
return intent
}
}
@BindView(R.id.root) lateinit var root: ViewGroup
@BindView(R.id.toolbar) lateinit var toolbar: Toolbar
@BindView(R.id.tabs) lateinit var tabLayout: TabLayout
@BindView(R.id.pager) lateinit var viewPager: ViewPager
@BindView(R.id.progress) lateinit var progress: View
lateinit var project: Project
lateinit var mergeRequest: MergeRequest
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_merge_request)
ButterKnife.bind(this)
project = Parcels.unwrap<Project>(intent.getParcelableExtra<Parcelable>(KEY_PROJECT))
mergeRequest = Parcels.unwrap<MergeRequest>(intent.getParcelableExtra<Parcelable>(KEY_MERGE_REQUEST))
toolbar.title = getString(R.string.merge_request_number) + mergeRequest.iid
toolbar.setNavigationIcon(R.drawable.ic_back_24dp)
toolbar.setNavigationOnClickListener { onBackPressed() }
toolbar.subtitle = project.nameWithNamespace
if (mergeRequest.state == MergeRequest.STATE_OPENED) {
toolbar.inflateMenu(R.menu.merge)
}
toolbar.setOnMenuItemClickListener(Toolbar.OnMenuItemClickListener { item ->
when (item.itemId) {
R.id.action_merge -> {
merge()
return@OnMenuItemClickListener true
}
}
false
})
val sectionsPagerAdapter = MergeRequestSectionsPagerAdapter(
this,
supportFragmentManager,
project,
mergeRequest)
viewPager.adapter = sectionsPagerAdapter
tabLayout.setupWithViewPager(viewPager)
}
fun merge() {
progress.visibility = View.VISIBLE
App.get().gitLab.acceptMergeRequest(project.id, mergeRequest.id)
.setup(bindToLifecycle())
.subscribe(object : CustomResponseSingleObserver<MergeRequest>() {
override fun error(e: Throwable) {
Timber.e(e)
progress.visibility = View.GONE
var message = getString(R.string.unable_to_merge)
if (e is HttpException) {
val code = e.response().code()
if (code == 406) {
message = getString(R.string.merge_request_already_merged_or_closed)
}
}
Snackbar.make(root, message, Snackbar.LENGTH_LONG)
.show()
}
override fun responseSuccess(mergeRequest: MergeRequest) {
progress.visibility = View.GONE
Snackbar.make(root, R.string.merge_request_accepted, Snackbar.LENGTH_LONG)
.show()
App.bus().post(MergeRequestChangedEvent(mergeRequest))
}
})
}
}
package com.commit451.gitlab.activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import com.commit451.gitlab.App;
import com.commit451.gitlab.R;
import com.commit451.gitlab.adapter.DividerItemDecoration;
import com.commit451.gitlab.adapter.MilestoneIssueAdapter;
import com.commit451.gitlab.event.MilestoneChangedEvent;
import com.commit451.gitlab.model.api.Issue;
import com.commit451.gitlab.model.api.Milestone;
import com.commit451.gitlab.model.api.Project;
import com.commit451.gitlab.navigation.Navigator;
import com.commit451.gitlab.rx.CustomResponseSingleObserver;
import com.commit451.gitlab.rx.CustomSingleObserver;
import com.commit451.gitlab.util.LinkHeaderParser;
import org.greenrobot.eventbus.Subscribe;
import org.parceler.Parcels;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import retrofit2.Response;
import timber.log.Timber;
public class MilestoneActivity extends BaseActivity {
private static final String EXTRA_PROJECT = "extra_project";
private static final String EXTRA_MILESTONE = "extra_milestone";
public static Intent newIntent(Context context, Project project, Milestone milestone) {
Intent intent = new Intent(context, MilestoneActivity.class);
intent.putExtra(EXTRA_PROJECT, Parcels.wrap(project));
intent.putExtra(EXTRA_MILESTONE, Parcels.wrap(milestone));
return intent;
}
@BindView(R.id.root)
View root;
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.swipe_layout)
SwipeRefreshLayout swipeRefreshLayout;
@BindView(R.id.list)
RecyclerView listIssues;
@BindView(R.id.message_text)
TextView textMessage;
@BindView(R.id.progress)
View progress;
MilestoneIssueAdapter adapterMilestoneIssues;
LinearLayoutManager layoutManagerIssues;
MenuItem menuItemOpenClose;
Project project;
Milestone milestone;
Uri nextPageUrl;
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 = layoutManagerIssues.getChildCount();
int totalItemCount = layoutManagerIssues.getItemCount();
int firstVisibleItem = layoutManagerIssues.findFirstVisibleItemPosition();
if (firstVisibleItem + visibleItemCount >= totalItemCount && !loading && nextPageUrl != null) {
loadMore();
}
}
};
@OnClick(R.id.add)
void onAddClick(View fab) {
Navigator.navigateToAddIssue(MilestoneActivity.this, fab, project);
}
@OnClick(R.id.edit)
void onEditClicked(View fab) {
Navigator.navigateToEditMilestone(MilestoneActivity.this, fab, project, milestone);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_milestone);
ButterKnife.bind(this);
App.bus().register(this);
project = Parcels.unwrap(getIntent().getParcelableExtra(EXTRA_PROJECT));
milestone = Parcels.unwrap(getIntent().getParcelableExtra(EXTRA_MILESTONE));
toolbar.setNavigationIcon(R.drawable.ic_back_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackPressed();
}
});
toolbar.inflateMenu(R.menu.menu_milestone);
menuItemOpenClose = toolbar.getMenu().findItem(R.id.action_close);
toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_close:
closeOrOpenIssue();
return true;
}
return false;
}
});
adapterMilestoneIssues = new MilestoneIssueAdapter(new MilestoneIssueAdapter.Listener() {
@Override
public void onIssueClicked(Issue issue) {
Navigator.navigateToIssue(MilestoneActivity.this, project, issue);
}
});
bind(milestone);
listIssues.setAdapter(adapterMilestoneIssues);
layoutManagerIssues = new LinearLayoutManager(this);
listIssues.setLayoutManager(layoutManagerIssues);
listIssues.addItemDecoration(new DividerItemDecoration(this));
listIssues.addOnScrollListener(onScrollListener);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
loadData();
}
});
loadData();
}
@Override
protected void onDestroy() {
super.onDestroy();
App.bus().unregister(this);
}
private void bind(Milestone milestone) {
toolbar.setTitle(milestone.getTitle());
adapterMilestoneIssues.setMilestone(milestone);
setOpenCloseMenuStatus();
}
private void loadData() {
textMessage.setVisibility(View.GONE);
loading = true;
swipeRefreshLayout.post(new Runnable() {
@Override
public void run() {
if (swipeRefreshLayout != null) {
swipeRefreshLayout.setRefreshing(true);
}
}
});
App.get().getGitLab().getMilestoneIssues(project.getId(), milestone.getId())
.compose(this.<Response<List<Issue>>>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomResponseSingleObserver<List<Issue>>() {
@Override
public void error(@NonNull Throwable t) {
Timber.e(t);
loading = false;
swipeRefreshLayout.setRefreshing(false);
textMessage.setVisibility(View.VISIBLE);
textMessage.setText(R.string.connection_error_issues);
adapterMilestoneIssues.setIssues(null);
}
@Override
public void responseSuccess(@NonNull List<Issue> issues) {
swipeRefreshLayout.setRefreshing(false);
loading = false;
if (!issues.isEmpty()) {
textMessage.setVisibility(View.GONE);
} else {
Timber.d("No issues found");
textMessage.setVisibility(View.VISIBLE);
textMessage.setText(R.string.no_issues);
}
nextPageUrl = LinkHeaderParser.parse(response()).getNext();
adapterMilestoneIssues.setIssues(issues);
}
});
}
private void loadMore() {
if (nextPageUrl == null) {
return;
}
loading = true;
Timber.d("loadMore called for %s", nextPageUrl);
App.get().getGitLab().getMilestoneIssues(nextPageUrl.toString())
.compose(this.<Response<List<Issue>>>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomResponseSingleObserver<List<Issue>>() {
@Override
public void error(@NonNull Throwable e) {
Timber.e(e);
loading = false;
}
@Override
public void responseSuccess(@NonNull List<Issue> issues) {
loading = false;
nextPageUrl = LinkHeaderParser.parse(response()).getNext();
adapterMilestoneIssues.addIssues(issues);
}
});
}
private void closeOrOpenIssue() {
progress.setVisibility(View.VISIBLE);
if (milestone.getState().equals(Milestone.STATE_ACTIVE)) {
updateMilestoneStatus(App.get().getGitLab().updateMilestoneStatus(project.getId(), milestone.getId(), Milestone.STATE_EVENT_CLOSE));
} else {
updateMilestoneStatus(App.get().getGitLab().updateMilestoneStatus(project.getId(), milestone.getId(), Milestone.STATE_EVENT_ACTIVATE));
}
}
private void updateMilestoneStatus(Single<Milestone> observable) {
observable.compose(this.<Milestone>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomSingleObserver<Milestone>() {
@Override
public void error(@NonNull Throwable e) {
Timber.e(e);
progress.setVisibility(View.GONE);
Snackbar.make(root, getString(R.string.failed_to_create_milestone), Snackbar.LENGTH_SHORT)
.show();
}
@Override
public void success(@NonNull Milestone milestone) {
progress.setVisibility(View.GONE);
MilestoneActivity.this.milestone = milestone;
App.bus().post(new MilestoneChangedEvent(MilestoneActivity.this.milestone));
setOpenCloseMenuStatus();
}
});
}
private void setOpenCloseMenuStatus() {
menuItemOpenClose.setTitle(milestone.getState().equals(Milestone.STATE_CLOSED) ? R.string.reopen : R.string.close);
}
@Subscribe
public void onMilestoneChanged(MilestoneChangedEvent event) {
if (milestone.getId() == event.milestone.getId()) {
milestone = event.milestone;
bind(milestone);
}
}
}
package com.commit451.gitlab.activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
import android.support.design.widget.Snackbar
import android.support.v4.widget.SwipeRefreshLayout
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.Toolbar
import android.view.MenuItem
import android.view.View
import android.widget.TextView
import butterknife.BindView
import butterknife.ButterKnife
import butterknife.OnClick
import com.commit451.gitlab.App
import com.commit451.gitlab.R
import com.commit451.gitlab.adapter.DividerItemDecoration
import com.commit451.gitlab.adapter.MilestoneIssueAdapter
import com.commit451.gitlab.event.MilestoneChangedEvent
import com.commit451.gitlab.extension.setup
import com.commit451.gitlab.model.api.Issue
import com.commit451.gitlab.model.api.Milestone
import com.commit451.gitlab.model.api.Project
import com.commit451.gitlab.navigation.Navigator
import com.commit451.gitlab.rx.CustomResponseSingleObserver
import com.commit451.gitlab.rx.CustomSingleObserver
import com.commit451.gitlab.util.LinkHeaderParser
import io.reactivex.Single
import org.greenrobot.eventbus.Subscribe
import org.parceler.Parcels
import timber.log.Timber
class MilestoneActivity : BaseActivity() {
companion object {
private val EXTRA_PROJECT = "extra_project"
private val EXTRA_MILESTONE = "extra_milestone"
fun newIntent(context: Context, project: Project, milestone: Milestone): Intent {
val intent = Intent(context, MilestoneActivity::class.java)
intent.putExtra(EXTRA_PROJECT, Parcels.wrap(project))
intent.putExtra(EXTRA_MILESTONE, Parcels.wrap(milestone))
return intent
}
}
@BindView(R.id.root) lateinit var root: View
@BindView(R.id.toolbar) lateinit var toolbar: Toolbar
@BindView(R.id.swipe_layout) lateinit var swipeRefreshLayout: SwipeRefreshLayout
@BindView(R.id.list) lateinit var listIssues: RecyclerView
@BindView(R.id.message_text) lateinit var textMessage: TextView
@BindView(R.id.progress) lateinit var progress: View
lateinit var adapterMilestoneIssues: MilestoneIssueAdapter
lateinit var layoutManagerIssues: LinearLayoutManager
lateinit var menuItemOpenClose: MenuItem
lateinit var project: Project
lateinit var milestone: Milestone
var nextPageUrl: Uri? = null
var loading = false
val onScrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView?, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val visibleItemCount = layoutManagerIssues.childCount
val totalItemCount = layoutManagerIssues.itemCount
val firstVisibleItem = layoutManagerIssues.findFirstVisibleItemPosition()
if (firstVisibleItem + visibleItemCount >= totalItemCount && !loading && nextPageUrl != null) {
loadMore()
}
}
}
@OnClick(R.id.add)
fun onAddClick(fab: View) {
Navigator.navigateToAddIssue(this@MilestoneActivity, fab, project)
}
@OnClick(R.id.edit)
fun onEditClicked(fab: View) {
Navigator.navigateToEditMilestone(this@MilestoneActivity, fab, project, milestone)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_milestone)
ButterKnife.bind(this)
App.bus().register(this)
project = Parcels.unwrap<Project>(intent.getParcelableExtra<Parcelable>(EXTRA_PROJECT))
milestone = Parcels.unwrap<Milestone>(intent.getParcelableExtra<Parcelable>(EXTRA_MILESTONE))
toolbar.setNavigationIcon(R.drawable.ic_back_24dp)
toolbar.setNavigationOnClickListener { onBackPressed() }
toolbar.inflateMenu(R.menu.close)
menuItemOpenClose = toolbar.menu.findItem(R.id.action_close)
toolbar.setOnMenuItemClickListener(Toolbar.OnMenuItemClickListener { item ->
when (item.itemId) {
R.id.action_close -> {
closeOrOpenIssue()
return@OnMenuItemClickListener true
}
}
false
})
adapterMilestoneIssues = MilestoneIssueAdapter(object : MilestoneIssueAdapter.Listener {
override fun onIssueClicked(issue: Issue) {
Navigator.navigateToIssue(this@MilestoneActivity, project, issue)
}
})
bind(milestone)
listIssues.adapter = adapterMilestoneIssues
layoutManagerIssues = LinearLayoutManager(this)
listIssues.layoutManager = layoutManagerIssues
listIssues.addItemDecoration(DividerItemDecoration(this))
listIssues.addOnScrollListener(onScrollListener)
swipeRefreshLayout.setOnRefreshListener { loadData() }
loadData()
}
override fun onDestroy() {
super.onDestroy()
App.bus().unregister(this)
}
fun bind(milestone: Milestone) {
toolbar.title = milestone.title
adapterMilestoneIssues.setMilestone(milestone)
setOpenCloseMenuStatus()
}
fun loadData() {
textMessage.visibility = View.GONE
loading = true
swipeRefreshLayout.isRefreshing = true
App.get().gitLab.getMilestoneIssues(project.id, milestone.id)
.setup(bindToLifecycle())
.subscribe(object : CustomResponseSingleObserver<List<Issue>>() {
override fun error(t: Throwable) {
Timber.e(t)
loading = false
swipeRefreshLayout.isRefreshing = false
textMessage.visibility = View.VISIBLE
textMessage.setText(R.string.connection_error_issues)
adapterMilestoneIssues.setIssues(null)
}
override fun responseSuccess(issues: List<Issue>) {
swipeRefreshLayout.isRefreshing = false
loading = false
if (!issues.isEmpty()) {
textMessage.visibility = View.GONE
} else {
Timber.d("No issues found")
textMessage.visibility = View.VISIBLE
textMessage.setText(R.string.no_issues)
}
nextPageUrl = LinkHeaderParser.parse(response()).next
adapterMilestoneIssues.setIssues(issues)
}
})
}
fun loadMore() {
if (nextPageUrl == null) {
return
}
loading = true
Timber.d("loadMore called for %s", nextPageUrl)
App.get().gitLab.getMilestoneIssues(nextPageUrl!!.toString())
.setup(bindToLifecycle())
.subscribe(object : CustomResponseSingleObserver<List<Issue>>() {
override fun error(e: Throwable) {
Timber.e(e)
loading = false
}
override fun responseSuccess(issues: List<Issue>) {
loading = false
nextPageUrl = LinkHeaderParser.parse(response()).next
adapterMilestoneIssues.addIssues(issues)
}
})
}
fun closeOrOpenIssue() {
progress.visibility = View.VISIBLE
if (milestone.state == Milestone.STATE_ACTIVE) {
updateMilestoneStatus(App.get().gitLab.updateMilestoneStatus(project.id, milestone.id, Milestone.STATE_EVENT_CLOSE))
} else {
updateMilestoneStatus(App.get().gitLab.updateMilestoneStatus(project.id, milestone.id, Milestone.STATE_EVENT_ACTIVATE))
}
}
fun updateMilestoneStatus(observable: Single<Milestone>) {
observable.setup(bindToLifecycle())
.subscribe(object : CustomSingleObserver<Milestone>() {
override fun error(e: Throwable) {
Timber.e(e)
progress.visibility = View.GONE
Snackbar.make(root, getString(R.string.failed_to_create_milestone), Snackbar.LENGTH_SHORT)
.show()
}
override fun success(milestone: Milestone) {
progress.visibility = View.GONE
this@MilestoneActivity.milestone = milestone
App.bus().post(MilestoneChangedEvent(this@MilestoneActivity.milestone))
setOpenCloseMenuStatus()
}
})
}
fun setOpenCloseMenuStatus() {
menuItemOpenClose.setTitle(if (milestone.state == Milestone.STATE_CLOSED) R.string.reopen else R.string.close)
}
@Subscribe
fun onEvent(event: MilestoneChangedEvent) {
if (milestone.id == event.milestone.id) {
milestone = event.milestone
bind(milestone)
}
}
}
package com.commit451.gitlab.activity;
import android.annotation.TargetApi;
import android.os.Build;
import android.view.View;
import com.commit451.morphtransitions.FabTransform;
/**
* Activity that morphs from a FAB. Make sure the view you want to morph has the view id R.id.root and
* call {@link #morph(View)} when the content view is set
*/
public class MorphActivity extends BaseActivity {
protected void morph(View root) {
if (root == null) {
throw new IllegalStateException("Cannot pass an empty view");
}
FabTransform.setup(this, root);
}
@Override
public void onBackPressed() {
dismiss();
}
@TargetApi(21)
public void dismiss() {
if (Build.VERSION.SDK_INT >= 21) {
finishAfterTransition();
} else {
finish();
}
}
}
package com.commit451.gitlab.activity
import android.os.Build
import android.view.View
import com.commit451.morphtransitions.FabTransform
/**
* Activity that morphs from a FAB. Make sure the view you want to morph has the view id R.id.root and
* call [.morph] when the content view is set
*/
open class MorphActivity : BaseActivity() {
protected fun morph(root: View?) {
if (root == null) {
throw IllegalStateException("Cannot pass an empty view")
}
FabTransform.setup(this, root)
}
override fun onBackPressed() {
dismiss()
}
fun dismiss() {
if (Build.VERSION.SDK_INT >= 21) {
finishAfterTransition()
} else {
finish()
}
}
}
package com.commit451.gitlab.activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.TabLayout;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import com.commit451.gitlab.R;
import com.commit451.gitlab.adapter.PickBranchOrTagPagerAdapter;
import com.commit451.gitlab.model.Ref;
import org.parceler.Parcels;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
/**
* Intermediate activity when deep linking to another activity and things need to load
*/
public class PickBranchOrTagActivity extends AppCompatActivity {
private static final String EXTRA_PROJECT_ID = "project_id";
private static final String EXTRA_CURRENT_REF = "current_ref";
public static final String EXTRA_REF = "ref";
public static Intent newIntent(Context context, long projectId, @Nullable Ref currentRef) {
Intent intent = new Intent(context, PickBranchOrTagActivity.class);
intent.putExtra(EXTRA_PROJECT_ID, projectId);
intent.putExtra(EXTRA_CURRENT_REF, Parcels.wrap(currentRef));
return intent;
}
@BindView(R.id.tabs)
TabLayout tabLayout;
@BindView(R.id.pager)
ViewPager viewPager;
@OnClick(R.id.root)
void onRootClicked() {
finish();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pick_branch_or_tag);
ButterKnife.bind(this);
long projectId = getIntent().getLongExtra(EXTRA_PROJECT_ID, -1);
Ref currentRef = Parcels.unwrap(getIntent().getParcelableExtra(EXTRA_CURRENT_REF));
viewPager.setAdapter(new PickBranchOrTagPagerAdapter(this, getSupportFragmentManager(), projectId, currentRef));
tabLayout.setupWithViewPager(viewPager);
if (currentRef != null) {
int position = currentRef.getType() == Ref.TYPE_BRANCH ? 0 : 1;
tabLayout.getTabAt(position).select();
viewPager.setCurrentItem(position);
}
}
@Override
public void finish() {
super.finish();
overridePendingTransition(R.anim.do_nothing, R.anim.fade_out);
}
}
package com.commit451.gitlab.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.support.design.widget.TabLayout
import android.support.v4.view.ViewPager
import android.support.v7.app.AppCompatActivity
import butterknife.BindView
import butterknife.ButterKnife
import butterknife.OnClick
import com.commit451.gitlab.R
import com.commit451.gitlab.adapter.PickBranchOrTagPagerAdapter
import com.commit451.gitlab.model.Ref
import org.parceler.Parcels
/**
* Intermediate activity when deep linking to another activity and things need to load
*/
class PickBranchOrTagActivity : AppCompatActivity() {
companion object {
private val EXTRA_PROJECT_ID = "project_id"
private val EXTRA_CURRENT_REF = "current_ref"
val EXTRA_REF = "ref"
fun newIntent(context: Context, projectId: Long, currentRef: Ref?): Intent {
val intent = Intent(context, PickBranchOrTagActivity::class.java)
intent.putExtra(EXTRA_PROJECT_ID, projectId)
intent.putExtra(EXTRA_CURRENT_REF, Parcels.wrap<Ref>(currentRef))
return intent
}
}
@BindView(R.id.tabs) lateinit var tabLayout: TabLayout
@BindView(R.id.pager) lateinit var viewPager: ViewPager
@OnClick(R.id.root)
fun onRootClicked() {
finish()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pick_branch_or_tag)
ButterKnife.bind(this)
val projectId = intent.getLongExtra(EXTRA_PROJECT_ID, -1)
val currentRef = Parcels.unwrap<Ref>(intent.getParcelableExtra<Parcelable>(EXTRA_CURRENT_REF))
viewPager.adapter = PickBranchOrTagPagerAdapter(this, supportFragmentManager, projectId, currentRef)
tabLayout.setupWithViewPager(viewPager)
if (currentRef != null) {
val position = if (currentRef.type == Ref.TYPE_BRANCH) 0 else 1
tabLayout.getTabAt(position)!!.select()
viewPager.currentItem = position
}
}
override fun finish() {
super.finish()
overridePendingTransition(R.anim.do_nothing, R.anim.fade_out)
}
}
package com.commit451.gitlab.activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
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 android.widget.Toast;
import com.commit451.alakazam.HideRunnable;
import com.commit451.gitlab.App;
import com.commit451.gitlab.R;
import com.commit451.gitlab.adapter.ProjectSectionsPagerAdapter;
import com.commit451.gitlab.data.Prefs;
import com.commit451.gitlab.event.ProjectReloadEvent;
import com.commit451.gitlab.fragment.BaseFragment;
import com.commit451.gitlab.model.Ref;
import com.commit451.gitlab.model.api.Project;
import com.commit451.gitlab.navigation.Navigator;
import com.commit451.gitlab.rx.CustomSingleObserver;
import com.commit451.gitlab.util.IntentUtil;
import org.parceler.Parcels;
import butterknife.BindView;
import butterknife.ButterKnife;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
public class ProjectActivity extends BaseActivity {
private static final String EXTRA_PROJECT = "extra_project";
private static final String EXTRA_PROJECT_ID = "extra_project_id";
private static final String EXTRA_PROJECT_NAMESPACE = "extra_project_namespace";
private static final String EXTRA_PROJECT_NAME = "extra_project_name";
private static final String STATE_REF = "ref";
private static final String STATE_PROJECT = "project";
private static final int REQUEST_BRANCH_OR_TAG = 1;
public static Intent newIntent(Context context, Project project) {
Intent intent = new Intent(context, ProjectActivity.class);
intent.putExtra(EXTRA_PROJECT, Parcels.wrap(project));
return intent;
}
public static Intent newIntent(Context context, String projectId) {
Intent intent = new Intent(context, ProjectActivity.class);
intent.putExtra(EXTRA_PROJECT_ID, projectId);
return intent;
}
public static Intent newIntent(Context context, String projectNamespace, String projectName) {
Intent intent = new Intent(context, ProjectActivity.class);
intent.putExtra(EXTRA_PROJECT_NAMESPACE, projectNamespace);
intent.putExtra(EXTRA_PROJECT_NAME, projectName);
return intent;
}
@BindView(R.id.root)
ViewGroup root;
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.tabs)
TabLayout tabLayout;
@BindView(R.id.progress)
View progress;
@BindView(R.id.pager)
ViewPager viewPager;
Project project;
Ref ref;
private final Toolbar.OnMenuItemClickListener onMenuItemClickListener = new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_branch:
if (project != null) {
Navigator.navigateToPickBranchOrTag(ProjectActivity.this, project.getId(), ref, REQUEST_BRANCH_OR_TAG);
}
return true;
case R.id.action_share:
if (project != null) {
IntentUtil.share(root, project.getWebUrl());
}
return true;
case R.id.action_copy_git_https:
if (project == null || project.getHttpUrlToRepo() == null) {
Toast.makeText(ProjectActivity.this, R.string.failed_to_copy_to_clipboard, Toast.LENGTH_SHORT)
.show();
} else {
copyToClipboard(project.getHttpUrlToRepo());
}
return true;
case R.id.action_copy_git_ssh:
if (project == null || project.getHttpUrlToRepo() == null) {
Toast.makeText(ProjectActivity.this, R.string.failed_to_copy_to_clipboard, Toast.LENGTH_SHORT)
.show();
} else {
copyToClipboard(project.getSshUrlToRepo());
}
return true;
}
return false;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
App.get().getPrefs().setStartingView(Prefs.STARTING_VIEW_PROJECTS);
setContentView(R.layout.activity_project);
ButterKnife.bind(this);
Project project = Parcels.unwrap(getIntent().getParcelableExtra(EXTRA_PROJECT));
if (savedInstanceState != null) {
project = Parcels.unwrap(savedInstanceState.getParcelable(STATE_PROJECT));
ref = Parcels.unwrap(savedInstanceState.getParcelable(STATE_REF));
}
toolbar.setNavigationIcon(R.drawable.ic_back_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
toolbar.inflateMenu(R.menu.menu_project);
toolbar.setOnMenuItemClickListener(onMenuItemClickListener);
if (project == null) {
String projectId = getIntent().getStringExtra(EXTRA_PROJECT_ID);
String projectNamespace = getIntent().getStringExtra(EXTRA_PROJECT_NAMESPACE);
if (projectId != null) {
loadProject(projectId);
} else if (projectNamespace != null) {
String projectName = getIntent().getStringExtra(EXTRA_PROJECT_NAME);
loadProject(projectNamespace, projectName);
} else {
throw new IllegalStateException("You did something wrong and now we don't know what project to load. :(");
}
} else {
bindProject(project);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case REQUEST_BRANCH_OR_TAG:
if (resultCode == RESULT_OK) {
ref = Parcels.unwrap(data.getParcelableExtra(PickBranchOrTagActivity.EXTRA_REF));
broadcastLoad();
}
break;
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(STATE_REF, Parcels.wrap(ref));
outState.putParcelable(STATE_PROJECT, Parcels.wrap(project));
}
private void loadProject(String projectId) {
showProgress();
loadProject(App.get().getGitLab().getProject(projectId));
}
private void loadProject(String projectNamespace, String projectName) {
showProgress();
loadProject(App.get().getGitLab().getProject(projectNamespace, projectName));
}
private void loadProject(Single<Project> observable) {
observable.compose(this.<Project>bindToLifecycle())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new CustomSingleObserver<Project>() {
@Override
public void error(@NonNull Throwable t) {
Timber.e(t);
progress.animate()
.alpha(0.0f)
.withEndAction(new HideRunnable(progress));
Snackbar.make(root, getString(R.string.connection_error), Snackbar.LENGTH_SHORT)
.show();
}
@Override
public void success(@NonNull Project project) {
progress.animate()
.alpha(0.0f)
.withEndAction(new HideRunnable(progress));
bindProject(project);
}
});
}
private void showProgress() {
progress.setAlpha(0.0f);
progress.setVisibility(View.VISIBLE);
progress.animate().alpha(1.0f);
}
private void broadcastLoad() {
App.bus().post(new ProjectReloadEvent(project, ref.getRef()));
}
@Override
public void onBackPressed() {
Fragment fragment = getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.pager + ":" + viewPager.getCurrentItem());
if (fragment instanceof BaseFragment) {
if (((BaseFragment) fragment).onBackPressed()) {
return;
}
}
super.onBackPressed();
}
public String getRef() {
if (ref == null) {
return null;
}
return ref.getRef();
}
public Project getProject() {
return project;
}
private void bindProject(Project project) {
this.project = project;
if (ref == null) {
ref = new Ref(Ref.TYPE_BRANCH, this.project.getDefaultBranch());
}
toolbar.setTitle(this.project.getName());
toolbar.setSubtitle(this.project.getNamespace().getName());
setupTabs();
}
private void setupTabs() {
ProjectSectionsPagerAdapter projectSectionsPagerAdapter = new ProjectSectionsPagerAdapter(this, getSupportFragmentManager());
viewPager.setAdapter(projectSectionsPagerAdapter);
tabLayout.setupWithViewPager(viewPager);
}
private void copyToClipboard(String url) {
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
// Creates a new text clip to put on the clipboard
ClipData clip = ClipData.newPlainText(project.getName(), url);
clipboard.setPrimaryClip(clip);
Snackbar.make(root, R.string.copied_to_clipboard, Snackbar.LENGTH_SHORT)
.show();
}
}
package com.commit451.gitlab.activity
import android.app.Activity
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.support.design.widget.Snackbar
import android.support.design.widget.TabLayout
import android.support.v4.view.ViewPager
import android.support.v7.widget.Toolbar
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import butterknife.BindView
import butterknife.ButterKnife
import com.commit451.alakazam.HideRunnable
import com.commit451.gitlab.App
import com.commit451.gitlab.R
import com.commit451.gitlab.adapter.ProjectSectionsPagerAdapter
import com.commit451.gitlab.data.Prefs
import com.commit451.gitlab.event.ProjectReloadEvent
import com.commit451.gitlab.extension.setup
import com.commit451.gitlab.fragment.BaseFragment
import com.commit451.gitlab.model.Ref
import com.commit451.gitlab.model.api.Project
import com.commit451.gitlab.navigation.Navigator
import com.commit451.gitlab.rx.CustomSingleObserver
import com.commit451.gitlab.util.IntentUtil
import io.reactivex.Single
import org.parceler.Parcels
import timber.log.Timber
class ProjectActivity : BaseActivity() {
companion object {
private val EXTRA_PROJECT = "extra_project"
private val EXTRA_PROJECT_ID = "extra_project_id"
private val EXTRA_PROJECT_NAMESPACE = "extra_project_namespace"
private val EXTRA_PROJECT_NAME = "extra_project_name"
private val STATE_REF = "ref"
private val STATE_PROJECT = "project"
private val REQUEST_BRANCH_OR_TAG = 1
fun newIntent(context: Context, project: Project): Intent {
val intent = Intent(context, ProjectActivity::class.java)
intent.putExtra(EXTRA_PROJECT, Parcels.wrap(project))
return intent
}
fun newIntent(context: Context, projectId: String): Intent {
val intent = Intent(context, ProjectActivity::class.java)
intent.putExtra(EXTRA_PROJECT_ID, projectId)
return intent
}
fun newIntent(context: Context, projectNamespace: String, projectName: String): Intent {
val intent = Intent(context, ProjectActivity::class.java)
intent.putExtra(EXTRA_PROJECT_NAMESPACE, projectNamespace)
intent.putExtra(EXTRA_PROJECT_NAME, projectName)
return intent
}
}
@BindView(R.id.root) lateinit var root: ViewGroup
@BindView(R.id.toolbar) lateinit var toolbar: Toolbar
@BindView(R.id.tabs) lateinit var tabLayout: TabLayout
@BindView(R.id.progress) lateinit var progress: View
@BindView(R.id.pager) lateinit var viewPager: ViewPager
var project: Project? = null
internal set
var ref: Ref? = null
internal set
val onMenuItemClickListener = Toolbar.OnMenuItemClickListener { item ->
when (item.itemId) {
R.id.action_branch -> {
if (project != null) {
Navigator.navigateToPickBranchOrTag(this@ProjectActivity, project!!.id, ref, REQUEST_BRANCH_OR_TAG)
}
return@OnMenuItemClickListener true
}
R.id.action_share -> {
if (project != null) {
IntentUtil.share(root, project!!.webUrl)
}
return@OnMenuItemClickListener true
}
R.id.action_copy_git_https -> {
if (project == null || project!!.httpUrlToRepo == null) {
Toast.makeText(this@ProjectActivity, R.string.failed_to_copy_to_clipboard, Toast.LENGTH_SHORT)
.show()
} else {
copyToClipboard(project!!.httpUrlToRepo)
}
return@OnMenuItemClickListener true
}
R.id.action_copy_git_ssh -> {
if (project == null || project!!.httpUrlToRepo == null) {
Toast.makeText(this@ProjectActivity, R.string.failed_to_copy_to_clipboard, Toast.LENGTH_SHORT)
.show()
} else {
copyToClipboard(project!!.sshUrlToRepo)
}
return@OnMenuItemClickListener true
}
}
false
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Prefs.startingView = Prefs.STARTING_VIEW_PROJECTS
setContentView(R.layout.activity_project)
ButterKnife.bind(this)
var project: Project? = Parcels.unwrap<Project>(intent.getParcelableExtra<Parcelable>(EXTRA_PROJECT))
if (savedInstanceState != null) {
project = Parcels.unwrap<Project>(savedInstanceState.getParcelable<Parcelable>(STATE_PROJECT))
ref = Parcels.unwrap<Ref>(savedInstanceState.getParcelable<Parcelable>(STATE_REF))
}
toolbar.setNavigationIcon(R.drawable.ic_back_24dp)
toolbar.setNavigationOnClickListener { finish() }
toolbar.inflateMenu(R.menu.menu_project)
toolbar.setOnMenuItemClickListener(onMenuItemClickListener)
if (project == null) {
val projectId = intent.getStringExtra(EXTRA_PROJECT_ID)
val projectNamespace = intent.getStringExtra(EXTRA_PROJECT_NAMESPACE)
if (projectId != null) {
loadProject(projectId)
} else if (projectNamespace != null) {
val projectName = intent.getStringExtra(EXTRA_PROJECT_NAME)
loadProject(projectNamespace, projectName)
} else {
throw IllegalStateException("You did something wrong and now we don't know what project to load. :(")
}
} else {
bindProject(project)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_BRANCH_OR_TAG -> if (resultCode == Activity.RESULT_OK) {
ref = Parcels.unwrap<Ref>(data?.getParcelableExtra<Parcelable>(PickBranchOrTagActivity.EXTRA_REF))
broadcastLoad()
}
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelable(STATE_REF, Parcels.wrap<Ref>(ref))
outState.putParcelable(STATE_PROJECT, Parcels.wrap<Project>(project))
}
override fun onBackPressed() {
val fragment = supportFragmentManager.findFragmentByTag("android:switcher:" + R.id.pager + ":" + viewPager.currentItem)
if (fragment is BaseFragment) {
if (fragment.onBackPressed()) {
return
}
}
super.onBackPressed()
}
override fun hasBrowsableLinks(): Boolean {
return true
}
fun loadProject(projectId: String) {
showProgress()
loadProject(App.get().gitLab.getProject(projectId))
}
fun loadProject(projectNamespace: String, projectName: String) {
showProgress()
loadProject(App.get().gitLab.getProject(projectNamespace, projectName))
}
fun loadProject(observable: Single<Project>) {
observable.setup(bindToLifecycle())
.subscribe(object : CustomSingleObserver<Project>() {
override fun error(t: Throwable) {
Timber.e(t)
progress.animate()
.alpha(0.0f)
.withEndAction(HideRunnable(progress))
Snackbar.make(root, getString(R.string.connection_error), Snackbar.LENGTH_SHORT)
.show()
}
override fun success(project: Project) {
progress.animate()
.alpha(0.0f)
.withEndAction(HideRunnable(progress))
bindProject(project)
}
})
}
fun showProgress() {
progress.alpha = 0.0f
progress.visibility = View.VISIBLE
progress.animate().alpha(1.0f)
}
fun broadcastLoad() {
App.bus().post(ProjectReloadEvent(project!!, ref!!.ref))
}
fun getRefRef(): String? {
if (ref == null) {
return null
}
return ref!!.ref
}
fun bindProject(project: Project) {
this.project = project
if (ref == null) {
ref = Ref(Ref.TYPE_BRANCH, this.project!!.defaultBranch)
}
toolbar.title = this.project!!.name
toolbar.subtitle = this.project!!.namespace.name
setupTabs()
}
fun setupTabs() {
val projectSectionsPagerAdapter = ProjectSectionsPagerAdapter(this, supportFragmentManager)
viewPager.adapter = projectSectionsPagerAdapter
tabLayout.setupWithViewPager(viewPager)
}
fun copyToClipboard(url: String) {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
// Creates a new text clip to put on the clipboard
val clip = ClipData.newPlainText(project!!.name, url)
clipboard.primaryClip = clip
Snackbar.make(root, R.string.copied_to_clipboard, Snackbar.LENGTH_SHORT)
.show()
}
}
package com.commit451.gitlab.activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.NavigationView;
import android.support.design.widget.TabLayout;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewPager;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import android.view.View;
import com.commit451.gitlab.App;
import com.commit451.gitlab.R;
import com.commit451.gitlab.adapter.ProjectPagerAdapter;
import com.commit451.gitlab.event.CloseDrawerEvent;
import com.commit451.gitlab.navigation.Navigator;
import org.greenrobot.eventbus.Subscribe;
import butterknife.BindView;
import butterknife.ButterKnife;
/**
* Shows the projects
*/
public class ProjectsActivity extends BaseActivity {
public static Intent newIntent(Context context) {
Intent intent = new Intent(context, ProjectsActivity.class);
return intent;
}
@BindView(R.id.toolbar)
Toolbar toolbar;
@BindView(R.id.tabs)
TabLayout tabLayout;
@BindView(R.id.pager)
ViewPager viewPager;
@BindView(R.id.navigation_view)
NavigationView navigationView;
@BindView(R.id.drawer_layout)
DrawerLayout drawerLayout;
private final Toolbar.OnMenuItemClickListener onMenuItemClickListener = new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_search:
Navigator.navigateToSearch(ProjectsActivity.this);
return true;
}
return false;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_projects);
ButterKnife.bind(this);
App.bus().register(this);
toolbar.setTitle(R.string.projects);
toolbar.setNavigationIcon(R.drawable.ic_menu_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
drawerLayout.openDrawer(GravityCompat.START);
}
});
toolbar.inflateMenu(R.menu.menu_search);
toolbar.setOnMenuItemClickListener(onMenuItemClickListener);
viewPager.setAdapter(new ProjectPagerAdapter(this, getSupportFragmentManager()));
tabLayout.setupWithViewPager(viewPager);
}
@Override
protected void onDestroy() {
super.onDestroy();
App.bus().unregister(this);
}
@Subscribe
public void onCloseDrawerEvent(CloseDrawerEvent event) {
drawerLayout.closeDrawers();
}
}
package com.commit451.gitlab.activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.support.design.widget.TabLayout
import android.support.v4.view.GravityCompat
import android.support.v4.view.ViewPager
import android.support.v4.widget.DrawerLayout
import android.support.v7.widget.Toolbar
import butterknife.BindView
import butterknife.ButterKnife
import com.commit451.gitlab.App
import com.commit451.gitlab.R
import com.commit451.gitlab.adapter.ProjectPagerAdapter
import com.commit451.gitlab.event.CloseDrawerEvent
import com.commit451.gitlab.navigation.Navigator
import org.greenrobot.eventbus.Subscribe
/**
* Shows the projects
*/
class ProjectsActivity : BaseActivity() {
companion object {
fun newIntent(context: Context): Intent {
val intent = Intent(context, ProjectsActivity::class.java)
return intent
}
}
@BindView(R.id.toolbar) lateinit var toolbar: Toolbar
@BindView(R.id.tabs) lateinit var tabLayout: TabLayout
@BindView(R.id.pager) lateinit var viewPager: ViewPager
@BindView(R.id.drawer_layout) lateinit var drawerLayout: DrawerLayout
val onMenuItemClickListener = Toolbar.OnMenuItemClickListener { item ->
when (item.itemId) {
R.id.action_search -> {
Navigator.navigateToSearch(this@ProjectsActivity)
return@OnMenuItemClickListener true
}
}
false
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_projects)
ButterKnife.bind(this)
App.bus().register(this)
toolbar.setTitle(R.string.projects)
toolbar.setNavigationIcon(R.drawable.ic_menu_24dp)
toolbar.setNavigationOnClickListener { drawerLayout.openDrawer(GravityCompat.START) }
toolbar.inflateMenu(R.menu.search)
toolbar.setOnMenuItemClickListener(onMenuItemClickListener)
viewPager.adapter = ProjectPagerAdapter(this, supportFragmentManager)
tabLayout.setupWithViewPager(viewPager)
}
override fun onDestroy() {
super.onDestroy()
App.bus().unregister(this)
}
@Subscribe
fun onEvent(event: CloseDrawerEvent) {
drawerLayout.closeDrawers()
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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