Commit b2ba9edf authored by Konstantin Tskhovrebov's avatar Konstantin Tskhovrebov 🤖

Merge branch 'develop'

parents e4999e3d 0ecd9edc
......@@ -16,8 +16,8 @@ android {
minSdkVersion(19)
targetSdkVersion(28)
versionName = "1.4.0"
versionCode = 12
versionName = "1.4.1"
versionCode = 13
buildToolsVersion = "28.0.3"
......
......@@ -14,7 +14,6 @@ import toothpick.Toothpick
import toothpick.configuration.Configuration
import toothpick.registries.FactoryRegistryLocator
import toothpick.registries.MemberInjectorRegistryLocator
import java.util.*
/**
* @author Konstantin Tskhovrebov (aka terrakok) on 26.03.17.
......@@ -23,7 +22,6 @@ class App : Application() {
override fun onCreate() {
super.onCreate()
appCode = UUID.randomUUID().toString()
initLogger()
initFabric()
......@@ -77,9 +75,4 @@ class App : Application() {
private fun initThreetenABP() {
AndroidThreeTen.init(this)
}
companion object {
lateinit var appCode: String
private set
}
}
\ No newline at end of file
package ru.terrakok.gitlabclient.entity
/**
* Created by Eugene Shapovalov (@CraggyHaggy) on 04.01.19.
*/
data class Color(
val name: String,
val value: Int
)
\ No newline at end of file
......@@ -5,11 +5,10 @@ import com.google.gson.annotations.SerializedName
/**
* @author Maxim Myalkin (MaxMyalkin) on 29.10.2018.
*/
data class Label(
@SerializedName("id") val id: Long,
@SerializedName("name") val name: String,
@SerializedName("color") val color: String,
@SerializedName("color") val color: Color,
@SerializedName("description") val description: String?,
@SerializedName("open_issues_count") val openIssuesCount: Int,
@SerializedName("closed_issues_count") val closedIssuesCount: Int,
......
......@@ -6,7 +6,7 @@ import org.threeten.bp.LocalDateTime
data class Project(
@SerializedName("id") val id: Long,
@SerializedName("description") val description: String?,
@SerializedName("default_branch") val defaultBranch: String,
@SerializedName("default_branch") val defaultBranch: String?,
@SerializedName("visibility") val visibility: Visibility,
@SerializedName("ssh_url_to_repo") val sshUrlToRepo: String?,
@SerializedName("http_url_to_repo") val httpUrlToRepo: String?,
......
package ru.terrakok.gitlabclient.model.data.server.deserializer
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import ru.terrakok.gitlabclient.entity.Color
import java.lang.reflect.Type
/**
* Created by Eugene Shapovalov (@CraggyHaggy) on 04.01.19.
*/
class ColorDeserializer : JsonDeserializer<Color> {
override fun deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): Color {
// The color of the label given in 6-digit hex notation with leading ‘#’ sign (e.g. #FFAABB)
// or one of the CSS color names. So according to CSS color names it can be named differently on Android.
val colorString = json.asJsonPrimitive.asString
val colorInt = try {
android.graphics.Color.parseColor(colorString)
} catch (e: IllegalArgumentException) {
android.graphics.Color.GREEN
}
return Color(colorString, colorInt)
}
}
\ No newline at end of file
......@@ -46,7 +46,7 @@ class ProjectInteractor @Inject constructor(
fun getProjectReadme(project: Project) =
Single
.defer {
if (project.readmeUrl != null) {
if (project.defaultBranch != null && project.readmeUrl != null) {
val readmePath = project.readmeUrl.substringAfter(
"${project.webUrl}/blob/${project.defaultBranch}/"
)
......
package ru.terrakok.gitlabclient.presentation.project.files
/**
* Created by Eugene Shapovalov (@CraggyHaggy) on 04.01.19.
*/
class NoBranchesError : RuntimeException()
\ No newline at end of file
......@@ -114,11 +114,15 @@ class ProjectFilesPresenter @Inject constructor(
.doAfterTerminate { viewState.showBlockingProgress(false) }
.subscribe(
{ (project, branches) ->
viewState.showBranchSelection(true)
if (project.defaultBranch != null) {
viewState.showBranchSelection(true)
projectBranches.addAll(branches)
projectFileDestination.init(project.path, project.defaultBranch)
projectFileDestination.moveToRoot()
projectBranches.addAll(branches)
projectFileDestination.init(project.path, project.defaultBranch)
projectFileDestination.moveToRoot()
} else {
handleLoadingProjectDetailsError(NoBranchesError())
}
},
{ handleLoadingProjectDetailsError(it) }
)
......@@ -128,7 +132,11 @@ class ProjectFilesPresenter @Inject constructor(
private fun handleLoadingProjectDetailsError(error: Throwable) {
viewState.setPath(resourceManager.getString(R.string.project_files_default_path))
viewState.showBranchSelection(false)
errorHandler.proceed(error, { viewState.showEmptyError(true, it) })
if (error is NoBranchesError) {
viewState.showEmptyError(true, resourceManager.getString(R.string.project_files_no_branches))
} else {
errorHandler.proceed(error, { viewState.showEmptyError(true, it) })
}
}
private val paginator = Paginator(
......
......@@ -3,7 +3,9 @@ package ru.terrakok.gitlabclient.toothpick.provider
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import org.threeten.bp.LocalDateTime
import ru.terrakok.gitlabclient.entity.Color
import ru.terrakok.gitlabclient.entity.todo.Todo
import ru.terrakok.gitlabclient.model.data.server.deserializer.ColorDeserializer
import ru.terrakok.gitlabclient.model.data.server.deserializer.DateDeserializer
import ru.terrakok.gitlabclient.model.data.server.deserializer.TodoDeserializer
import javax.inject.Inject
......@@ -18,5 +20,6 @@ class GsonProvider @Inject constructor() : Provider<Gson> {
GsonBuilder()
.registerTypeAdapter(LocalDateTime::class.java, DateDeserializer())
.registerTypeAdapter(Todo::class.java, TodoDeserializer())
.registerTypeAdapter(Color::class.java, ColorDeserializer())
.create()
}
\ No newline at end of file
......@@ -10,7 +10,6 @@ import ru.terrakok.cicerone.Navigator
import ru.terrakok.cicerone.NavigatorHolder
import ru.terrakok.cicerone.android.support.SupportAppNavigator
import ru.terrakok.cicerone.commands.Command
import ru.terrakok.gitlabclient.App
import ru.terrakok.gitlabclient.R
import ru.terrakok.gitlabclient.model.system.message.SystemMessageNotifier
import ru.terrakok.gitlabclient.model.system.message.SystemMessageType
......@@ -25,8 +24,6 @@ import javax.inject.Inject
* Created by Konstantin Tskhovrebov (aka @terrakok) on 03.09.18.
*/
private const val STATE_LAUNCH_FLAG = "state_launch_flag"
class AppActivity : MvpAppCompatActivity() {
@Inject
......@@ -79,11 +76,6 @@ class AppActivity : MvpAppCompatActivity() {
super.onPause()
}
override fun onSaveInstanceState(outState: Bundle?) {
super.onSaveInstanceState(outState)
outState?.putString(STATE_LAUNCH_FLAG, App.appCode)
}
override fun onBackPressed() {
currentFragment?.onBackPressed() ?: super.onBackPressed()
}
......
......@@ -5,14 +5,14 @@ import android.os.Handler
import android.view.LayoutInflater
import android.view.ViewGroup
import com.arellomobile.mvp.MvpAppCompatFragment
import ru.terrakok.gitlabclient.App
import ru.terrakok.gitlabclient.extension.objectScopeName
import timber.log.Timber
import toothpick.Scope
import toothpick.Toothpick
private const val PROGRESS_TAG = "bf_progress"
private const val STATE_LAUNCH_FLAG = "state_launch_flag"
private const val STATE_SCOPE_NAME = "state_scope_name"
private const val STATE_SCOPE_WAS_CLOSED = "state_scope_was_closed"
/**
* @author Konstantin Tskhovrebov (aka terrakok) on 26.03.17.
......@@ -32,18 +32,22 @@ abstract class BaseFragment : MvpAppCompatFragment() {
protected open val scopeModuleInstaller: (Scope) -> Unit = {}
private lateinit var fragmentScopeName: String
private var scopeIsNotInit: Boolean = true
protected lateinit var scope: Scope
private set
override fun onCreate(savedInstanceState: Bundle?) {
val savedAppCode = savedInstanceState?.getString(STATE_LAUNCH_FLAG)
//False - if fragment was restored without new app process (for example: activity rotation)
val isNewInAppProcess = savedAppCode != App.appCode
scopeIsNotInit = savedInstanceState?.getBoolean(STATE_SCOPE_WAS_CLOSED) ?: true
fragmentScopeName = savedInstanceState?.getString(STATE_SCOPE_NAME) ?: objectScopeName()
scope = Toothpick.openScopes(parentScopeName, fragmentScopeName)
.apply {
if (isNewInAppProcess) scopeModuleInstaller(this)
if (scopeIsNotInit) {
Timber.d("Init new UI scope: $fragmentScopeName")
scopeModuleInstaller(this)
} else {
Timber.d("Get exist UI scope: $fragmentScopeName")
}
}
super.onCreate(savedInstanceState)
......@@ -70,8 +74,8 @@ abstract class BaseFragment : MvpAppCompatFragment() {
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString(STATE_LAUNCH_FLAG, App.appCode)
outState.putString(STATE_SCOPE_NAME, fragmentScopeName)
outState.putBoolean(STATE_SCOPE_WAS_CLOSED, activity?.isChangingConfigurations == false)
instanceStateSaved = true
}
......@@ -79,6 +83,7 @@ abstract class BaseFragment : MvpAppCompatFragment() {
super.onDestroy()
if (activity?.isChangingConfigurations == false) {
//destroy this fragment with scope
Timber.d("Destroy UI scope: $fragmentScopeName")
Toothpick.closeScope(scope.name)
}
}
......
......@@ -11,6 +11,7 @@ import ru.terrakok.gitlabclient.entity.app.develop.AppLibrary
import ru.terrakok.gitlabclient.extension.tryOpenLink
import ru.terrakok.gitlabclient.presentation.libraries.LibrariesPresenter
import ru.terrakok.gitlabclient.presentation.libraries.LibrariesView
import ru.terrakok.gitlabclient.toothpick.DI
import ru.terrakok.gitlabclient.ui.global.BaseFragment
import ru.terrakok.gitlabclient.ui.global.list.AppLibraryAdapterDelegate
......@@ -20,6 +21,8 @@ import ru.terrakok.gitlabclient.ui.global.list.AppLibraryAdapterDelegate
class LibrariesFragment : BaseFragment(), LibrariesView {
override val layoutRes = R.layout.fragment_libraries
override val parentScopeName = DI.APP_SCOPE
private val adapter: LibraryAdapter by lazy { LibraryAdapter() }
@InjectPresenter
......
package ru.terrakok.gitlabclient.ui.project.labels
import android.annotation.SuppressLint
import android.graphics.Color
import android.support.v4.content.ContextCompat
import android.support.v4.graphics.ColorUtils
import android.support.v7.widget.RecyclerView
......@@ -10,6 +9,7 @@ import android.view.ViewGroup
import com.hannesdorfmann.adapterdelegates3.AdapterDelegate
import kotlinx.android.synthetic.main.item_label.view.*
import ru.terrakok.gitlabclient.R
import ru.terrakok.gitlabclient.entity.Color
import ru.terrakok.gitlabclient.entity.Label
import ru.terrakok.gitlabclient.extension.inflate
import ru.terrakok.gitlabclient.extension.setBackgroundTintByColor
......@@ -55,14 +55,13 @@ class ProjectLabelAdapterDelegate : AdapterDelegate<MutableList<Any>>() {
setLabelColor(item.color)
}
private fun setLabelColor(color: String) = with(itemView.labelTitleTextView) {
val labelColor = Color.parseColor(color)
private fun setLabelColor(color: Color) = with(itemView.labelTitleTextView) {
val textColor = when {
isColorDark(labelColor) -> ContextCompat.getColor(context, R.color.white)
isColorDark(color.value) -> ContextCompat.getColor(context, R.color.white)
else -> ContextCompat.getColor(context, R.color.primary_text)
}
setBackgroundTintByColor(labelColor)
setBackgroundTintByColor(color.value)
setTextColor(textColor)
}
......
package ru.terrakok.gitlabclient.ui.projects
import android.support.v7.util.DiffUtil
import android.support.v7.widget.RecyclerView
import com.hannesdorfmann.adapterdelegates3.ListDelegationAdapter
import ru.terrakok.gitlabclient.entity.Project
import ru.terrakok.gitlabclient.ui.global.list.ProgressAdapterDelegate
import ru.terrakok.gitlabclient.ui.global.list.ProgressItem
import ru.terrakok.gitlabclient.ui.global.list.ProjectAdapterDelegate
class ProjectsAdapter(
private val nextPageListener: () -> Unit,
private val onClickListener: (Long) -> Unit
) : ListDelegationAdapter<MutableList<Any>>() {
init {
items = mutableListOf()
delegatesManager.addDelegate(ProjectAdapterDelegate { onClickListener(it.id) })
delegatesManager.addDelegate(ProgressAdapterDelegate())
}
fun setData(projects: List<Project>) {
val progress = isProgress()
val oldItems = items.toList()
items.clear()
items.addAll(projects)
if (progress) items.add(ProgressItem())
DiffUtil
.calculateDiff(DiffCallback(items, oldItems), false)
.dispatchUpdatesTo(this)
}
fun showProgress(isVisible: Boolean) {
val progress = isProgress()
if (isVisible && !progress) {
items.add(ProgressItem())
notifyItemInserted(items.lastIndex)
} else if (!isVisible && progress) {
items.remove(items.last())
notifyItemRemoved(items.size)
}
}
private fun isProgress() = items.isNotEmpty() && items.last() is ProgressItem
override fun onBindViewHolder(
holder: RecyclerView.ViewHolder,
position: Int,
payloads: MutableList<Any?>
) {
super.onBindViewHolder(holder, position, payloads)
if (position == items.size - 10) nextPageListener()
}
private class DiffCallback(
private val newItems: List<Any>,
private val oldItems: List<Any>
) : DiffUtil.Callback() {
override fun getOldListSize() = oldItems.size
override fun getNewListSize() = newItems.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldItems[oldItemPosition]
val newItem = newItems[newItemPosition]
return if (newItem is Project && oldItem is Project) {
newItem.id == oldItem.id
} else {
newItem is ProgressItem && oldItem is ProgressItem
}
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldItems[oldItemPosition]
val newItem = newItems[newItemPosition]
return if (newItem is Project && oldItem is Project) {
newItem == oldItem
} else {
true
}
}
}
}
\ No newline at end of file
......@@ -2,10 +2,8 @@ package ru.terrakok.gitlabclient.ui.projects
import android.os.Bundle
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import com.arellomobile.mvp.presenter.InjectPresenter
import com.arellomobile.mvp.presenter.ProvidePresenter
import com.hannesdorfmann.adapterdelegates3.ListDelegationAdapter
import kotlinx.android.synthetic.main.layout_base_list.*
import kotlinx.android.synthetic.main.layout_zero.*
import ru.terrakok.gitlabclient.R
......@@ -18,9 +16,6 @@ import ru.terrakok.gitlabclient.toothpick.PrimitiveWrapper
import ru.terrakok.gitlabclient.toothpick.qualifier.ProjectListMode
import ru.terrakok.gitlabclient.ui.global.BaseFragment
import ru.terrakok.gitlabclient.ui.global.ZeroViewHolder
import ru.terrakok.gitlabclient.ui.global.list.ProgressAdapterDelegate
import ru.terrakok.gitlabclient.ui.global.list.ProgressItem
import ru.terrakok.gitlabclient.ui.global.list.ProjectAdapterDelegate
import toothpick.Scope
import toothpick.config.Module
......@@ -29,7 +24,7 @@ import toothpick.config.Module
*/
class ProjectsListFragment : BaseFragment(), ProjectsListView {
private val adapter = ProjectsAdapter()
private val adapter = ProjectsAdapter({ presenter.loadNextProjectsPage() }, { presenter.onProjectClicked(it) })
private var zeroViewHolder: ZeroViewHolder? = null
override val layoutRes = R.layout.fragment_projects
......@@ -99,41 +94,6 @@ class ProjectsListFragment : BaseFragment(), ProjectsListView {
override fun onBackPressed() = presenter.onBackPressed()
inner class ProjectsAdapter : ListDelegationAdapter<MutableList<Any>>() {
init {
items = mutableListOf()
delegatesManager.addDelegate(ProjectAdapterDelegate({ presenter.onProjectClicked(it.id) }))
delegatesManager.addDelegate(ProgressAdapterDelegate())
}
fun setData(projects: List<Project>) {
val progress = isProgress()
items.clear()
items.addAll(projects)
if (progress) items.add(ProgressItem())
notifyDataSetChanged()
}
fun showProgress(isVisible: Boolean) {
val currentProgress = isProgress()
if (isVisible && !currentProgress) items.add(ProgressItem())
else if (!isVisible && currentProgress) items.remove(items.last())
notifyDataSetChanged()
}
private fun isProgress() = items.isNotEmpty() && items.last() is ProgressItem
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: MutableList<Any?>) {
super.onBindViewHolder(holder, position, payloads)
if (position == items.size - 10) presenter.loadNextProjectsPage()
}
}
companion object {
private const val ARG_MODE = "plf_mode"
......
......@@ -181,7 +181,8 @@
<!--Project files screen-->
<string name="project_files_show_branches">Branches</string>
<string name="project_files_default_path">Project Files</string>
<string name="project_files_no_branches">You have no branches</string>
<!--Privacy policy screen-->
<string name="privacy_policy">Privacy policy</string>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment