...
 
Commits (89)
......@@ -89,7 +89,7 @@ dependencies {
val moxyVersion = "1.7.0"
val toothpickVersion = "2.1.0"
val retrofitVersion = "2.2.0"
val markwonVersion = "2.0.0"
val markwonVersion = extra["markwonVersion"] as String
val glideVersion = "4.8.0"
//Support
......@@ -143,6 +143,9 @@ dependencies {
//Crashlytics
implementation("com.crashlytics.sdk.android:crashlytics:2.10.0")
//Custom GitLab markdown parsing tools
implementation(project(":markwonx"))
//JUnit
testImplementation("junit:junit:4.12")
//Mockito
......
......@@ -8,10 +8,13 @@ import ru.terrakok.gitlabclient.di.provider.ApiProvider
import ru.terrakok.gitlabclient.di.provider.MarkDownConverterProvider
import ru.terrakok.gitlabclient.di.provider.OkHttpClientProvider
import ru.terrakok.gitlabclient.di.provider.OkHttpClientWithErrorHandlerProvider
import ru.terrakok.gitlabclient.di.provider.LabelSpanConfigProvider
import ru.terrakok.gitlabclient.entity.app.session.AuthHolder
import ru.terrakok.gitlabclient.entity.app.session.OAuthParams
import ru.terrakok.gitlabclient.entity.app.session.UserAccount
import ru.terrakok.gitlabclient.model.data.cache.ProjectCache
import ru.terrakok.gitlabclient.model.data.cache.ProjectLabelCache
import ru.terrakok.gitlabclient.markwonx.label.LabelSpanConfig
import ru.terrakok.gitlabclient.model.data.server.GitlabApi
import ru.terrakok.gitlabclient.model.data.server.MarkDownUrlResolver
import ru.terrakok.gitlabclient.model.interactor.event.EventInteractor
......@@ -54,6 +57,8 @@ class ServerModule(userAccount: UserAccount?) : Module() {
.toProvider(OkHttpClientWithErrorHandlerProvider::class.java)
.providesSingletonInScope()
bind(ProjectCache::class.java).singletonInScope()
bind(ProjectLabelCache::class.java).singletonInScope()
bind(LabelSpanConfig::class.java).toProvider(LabelSpanConfigProvider::class.java).providesSingletonInScope()
bind(GitlabApi::class.java).toProvider(ApiProvider::class.java).providesSingletonInScope()
bind(MarkDownConverter::class.java).toProvider(MarkDownConverterProvider::class.java).providesSingletonInScope()
bind(MarkDownUrlResolver::class.java)
......
package ru.terrakok.gitlabclient.di.provider
import android.content.Context
import ru.terrakok.gitlabclient.R
import ru.terrakok.gitlabclient.markwonx.label.LabelSpanConfig
import javax.inject.Inject
import javax.inject.Provider
class LabelSpanConfigProvider @Inject constructor(
val context: Context
) : Provider<LabelSpanConfig> {
override fun get(): LabelSpanConfig = LabelSpanConfig(
padding = context.resources.getDimensionPixelSize(R.dimen.label_padding)
)
}
\ No newline at end of file
package ru.terrakok.gitlabclient.di.provider
import android.content.Context
import io.reactivex.Single
import io.reactivex.functions.BiFunction
import okhttp3.OkHttpClient
import org.commonmark.ext.gfm.strikethrough.StrikethroughExtension
import org.commonmark.ext.gfm.tables.TablesExtension
import org.commonmark.node.Visitor
import org.commonmark.parser.Parser
import ru.noties.markwon.SpannableBuilder
import ru.noties.markwon.SpannableConfiguration
import ru.noties.markwon.UrlProcessorRelativeToAbsolute
import ru.noties.markwon.il.AsyncDrawableLoader
import ru.noties.markwon.spans.SpannableTheme
import ru.noties.markwon.tasklist.TaskListExtension
import ru.terrakok.gitlabclient.R
import ru.terrakok.gitlabclient.di.DefaultServerPath
import ru.terrakok.gitlabclient.entity.Label
import ru.terrakok.gitlabclient.entity.milestone.Milestone
import ru.terrakok.gitlabclient.extension.color
import ru.terrakok.gitlabclient.markwonx.*
import ru.terrakok.gitlabclient.markwonx.label.*
import ru.terrakok.gitlabclient.markwonx.milestone.MilestoneDescription
import ru.terrakok.gitlabclient.markwonx.milestone.SimpleMilestoneVisitor
import ru.terrakok.gitlabclient.model.interactor.label.LabelInteractor
import ru.terrakok.gitlabclient.model.interactor.milestone.MilestoneInteractor
import ru.terrakok.gitlabclient.model.system.SchedulersProvider
import ru.terrakok.gitlabclient.presentation.global.MarkDownConverter
import java.util.concurrent.Executors
......@@ -22,33 +38,108 @@ class MarkDownConverterProvider @Inject constructor(
private val context: Context,
private val httpClient: OkHttpClient,
private val schedulers: SchedulersProvider,
private val labelInteractor: LabelInteractor,
private val milestoneInteractor: MilestoneInteractor,
private val labelSpanConfig: LabelSpanConfig,
private val markdownClickMediator: MarkdownClickMediator,
@DefaultServerPath private val defaultServerPath: String
) : Provider<MarkDownConverter> {
private val spannableTheme
get() = SpannableTheme
private val spannableTheme by lazy {
SpannableTheme
.builderWithDefaults(context)
.codeBackgroundColor(context.color(R.color.beige))
.build()
}
private val asyncDrawableLoader
get() = AsyncDrawableLoader.builder()
private val asyncDrawableLoader by lazy {
AsyncDrawableLoader.builder()
.client(httpClient)
.executorService(Executors.newCachedThreadPool())
.resources(context.resources)
.build()
}
private val urlProcessor = UrlProcessorRelativeToAbsolute(defaultServerPath)
private val urlProcessor by lazy { UrlProcessorRelativeToAbsolute(defaultServerPath) }
private val spannableConfig
get() = SpannableConfiguration.builder(context)
private val spannableConfig by lazy {
SpannableConfiguration.builder(context)
.asyncDrawableLoader(asyncDrawableLoader)
.urlProcessor(urlProcessor)
.theme(spannableTheme)
.build()
}
private val markdownDecorator: MarkdownDecorator by lazy {
CompositeMarkdownDecorator(
SimpleMarkdownDecorator()
)
}
private val parser: Parser by lazy {
Parser.Builder().apply {
extensions(
listOf(
StrikethroughExtension.create(),
TablesExtension.create(),
TaskListExtension.create()
)
)
val processor = SimpleExtensionProcessor()
customDelimiterProcessor(
GitlabExtensionsDelimiterProcessor(
mapOf(
GitlabMarkdownExtension.LABEL to processor,
GitlabMarkdownExtension.MILESTONE to processor
)
)
)
}.build()
}
private fun getCustomVisitor(labels: List<Label>, milestones: List<Milestone>, spannableBuilder: SpannableBuilder): Visitor {
val labelDescriptions = labels.map {
LabelDescription(
id = it.id,
name = it.name,
color = it.color.name
)
}
val milestoneDescriptions = milestones.map {
MilestoneDescription(
id = it.iid,
name = it.title ?: ""
)
}
return CompositeVisitor(
spannableConfig,
spannableBuilder,
SimpleVisitor(
spannableConfig,
spannableBuilder,
mapOf(
GitlabMarkdownExtension.LABEL to SimpleLabelVisitor(labelDescriptions, labelSpanConfig, markdownClickMediator),
GitlabMarkdownExtension.MILESTONE to SimpleMilestoneVisitor(milestoneDescriptions, markdownClickMediator)
)
)
)
}
override fun get(): MarkDownConverter {
return MarkDownConverter(
parser,
markdownDecorator,
{ projectId, builder ->
val allLabels = labelInteractor.getAllProjectLabels(projectId)
val allMilestones = milestoneInteractor.getAllProjectMilestones(projectId)
Single
.zip(allLabels, allMilestones, BiFunction { labels, milestones ->
getCustomVisitor(labels, milestones, builder)
})
},
schedulers
)
}
}
override fun get() = MarkDownConverter(
spannableConfig,
schedulers
)
}
\ No newline at end of file
......@@ -11,7 +11,7 @@ sealed class TargetHeader {
val author: Author,
val icon: TargetHeaderIcon,
val title: TargetHeaderTitle,
val body: CharSequence,
val body: String,
val date: LocalDateTime,
val target: AppTarget,
val targetId: Long,
......
package ru.terrakok.gitlabclient.model.data.cache
import timber.log.Timber
import java.util.concurrent.ConcurrentHashMap
abstract class ExpirableCache<Key, Value> constructor(
private val lifetime: Long
) {
abstract val itemType: String
private data class CacheItem<Value>(val time: Long, val data: Value)
private val cache = ConcurrentHashMap<Key, CacheItem<Value>>()
fun clear() {
Timber.d("Clear $itemType cache")
cache.clear()
}
fun put(key: Key, data: Value) {
Timber.d("Put $itemType")
cache.put(key, CacheItem(System.currentTimeMillis(), data))
}
fun get(key: Key): Value? {
val item = cache[key]
if (item == null || System.currentTimeMillis() - item.time > lifetime) {
Timber.d("Get NULL project($key) $itemType")
return null
} else {
Timber.d("Get CACHED project($key) $itemType")
return item.data
}
}
}
\ No newline at end of file
......@@ -4,7 +4,6 @@ import ru.terrakok.gitlabclient.di.CacheLifetime
import ru.terrakok.gitlabclient.di.PrimitiveWrapper
import ru.terrakok.gitlabclient.entity.Project
import timber.log.Timber
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
/**
......@@ -12,36 +11,14 @@ import javax.inject.Inject
*/
class ProjectCache @Inject constructor(
@CacheLifetime lifetimeWrapper: PrimitiveWrapper<Long>
) {
private val lifetime = lifetimeWrapper.value
) : ExpirableCache<Long, Project>(lifetimeWrapper.value) {
private data class ProjectCacheItem(val time: Long, val data: Project)
private val cache = ConcurrentHashMap<Long, ProjectCacheItem>()
fun clear() {
Timber.d("Clear cache")
cache.clear()
}
override val itemType = "project"
fun put(data: List<Project>) {
Timber.d("Put projects")
cache.putAll(
data
.asSequence()
.map { ProjectCacheItem(System.currentTimeMillis(), it) }
.associateBy { it.data.id }
)
}
fun get(id: Long): Project? {
val item = cache[id]
if (item == null || System.currentTimeMillis() - item.time > lifetime) {
Timber.d("Get NULL project($id)")
return null
} else {
Timber.d("Get CACHED project($id)")
return item.data
data.forEach {
put(it.id, it)
}
}
......
package ru.terrakok.gitlabclient.model.data.cache
import ru.terrakok.gitlabclient.di.CacheLifetime
import ru.terrakok.gitlabclient.di.PrimitiveWrapper
import ru.terrakok.gitlabclient.entity.Label
import javax.inject.Inject
class ProjectLabelCache @Inject constructor(
@CacheLifetime lifetimeWrapper: PrimitiveWrapper<Long>
) : ExpirableCache<Long, List<Label>>(lifetimeWrapper.value) {
override val itemType = "project label"
}
\ No newline at end of file
package ru.terrakok.gitlabclient.model.data.cache
import ru.terrakok.gitlabclient.di.CacheLifetime
import ru.terrakok.gitlabclient.di.PrimitiveWrapper
import ru.terrakok.gitlabclient.entity.milestone.Milestone
import javax.inject.Inject
class ProjectMilestoneCache @Inject constructor(
@CacheLifetime lifetimeWrapper: PrimitiveWrapper<Long>
) : ExpirableCache<Long, List<Milestone>>(lifetimeWrapper.value) {
override val itemType = "project milestone"
}
\ No newline at end of file
package ru.terrakok.gitlabclient.model.interactor.label
import io.reactivex.Single
import ru.terrakok.gitlabclient.entity.Label
import ru.terrakok.gitlabclient.model.repository.label.LabelRepository
import javax.inject.Inject
......@@ -15,6 +17,17 @@ class LabelInteractor @Inject constructor(
page: Int
) = labelRepository.getLabelList(projectId, page)
fun getAllProjectLabels(
projectId: Long?
): Single<List<Label>> =
Single.defer {
if (projectId != null) {
labelRepository.getAllProjectLabels(projectId)
} else {
Single.just(emptyList())
}
}
fun subscribeToLabel(
projectId: Long,
labelId: Long
......
package ru.terrakok.gitlabclient.model.interactor.milestone
import io.reactivex.Single
import ru.terrakok.gitlabclient.entity.milestone.Milestone
import ru.terrakok.gitlabclient.entity.milestone.MilestoneState
import ru.terrakok.gitlabclient.model.repository.issue.IssueRepository
import ru.terrakok.gitlabclient.model.repository.mergerequest.MergeRequestRepository
......@@ -19,6 +21,17 @@ class MilestoneInteractor @Inject constructor(
) = milestoneRepository
.getMilestones(projectId, milestoneState, page)
fun getAllProjectMilestones(
projectId: Long?
): Single<List<Milestone>> =
Single.defer {
if (projectId != null) {
milestoneRepository.getAllProjectMilestones(projectId)
} else {
Single.just(emptyList())
}
}
fun getMilestone(
projectId: Long,
milestoneId: Long
......
package ru.terrakok.gitlabclient.model.repository.label
import io.reactivex.Observable
import io.reactivex.Single
import ru.terrakok.gitlabclient.di.DefaultPageSize
import ru.terrakok.gitlabclient.di.PrimitiveWrapper
import ru.terrakok.gitlabclient.entity.Label
import ru.terrakok.gitlabclient.model.data.cache.ProjectLabelCache
import ru.terrakok.gitlabclient.model.data.server.GitlabApi
import ru.terrakok.gitlabclient.model.system.SchedulersProvider
import javax.inject.Inject
......@@ -12,7 +16,8 @@ import javax.inject.Inject
class LabelRepository @Inject constructor(
private val api: GitlabApi,
@DefaultPageSize defaultPageSizeWrapper: PrimitiveWrapper<Int>,
private val schedulers: SchedulersProvider
private val schedulers: SchedulersProvider,
private val projectLabelCache: ProjectLabelCache
) {
private val defaultPageSize: Int = defaultPageSizeWrapper.value
......@@ -25,6 +30,31 @@ class LabelRepository @Inject constructor(
.subscribeOn(schedulers.io())
.observeOn(schedulers.ui())
fun getAllProjectLabels(projectId: Long): Single<List<Label>> =
Single
.defer {
val labels = projectLabelCache.get(projectId)
if (labels != null) {
Single.just(labels)
} else {
getAllProjectLabelsFromServer(projectId)
.doOnSuccess { labels -> projectLabelCache.put(projectId, labels) }
}
}
.subscribeOn(schedulers.io())
.observeOn(schedulers.ui())
private fun getAllProjectLabelsFromServer(
projectId: Long
): Single<List<Label>> = Observable.range(1, Integer.MAX_VALUE)
.concatMapSingle { page -> api.getProjectLabels(projectId, page, defaultPageSize) }
.takeWhile { labels -> labels.isNotEmpty() }
.reduce { allLabels, currentLabels -> allLabels + currentLabels }
.toSingle()
.subscribeOn(schedulers.io())
.observeOn(schedulers.ui())
fun createLabel(
projectId: Long,
name: String,
......
package ru.terrakok.gitlabclient.model.repository.milestone
import io.reactivex.Completable
import io.reactivex.Observable
import io.reactivex.Single
import ru.terrakok.gitlabclient.di.DefaultPageSize
import ru.terrakok.gitlabclient.di.PrimitiveWrapper
import ru.terrakok.gitlabclient.entity.milestone.Milestone
import ru.terrakok.gitlabclient.entity.milestone.MilestoneState
import ru.terrakok.gitlabclient.model.data.cache.ProjectMilestoneCache
import ru.terrakok.gitlabclient.model.data.server.GitlabApi
import ru.terrakok.gitlabclient.model.system.SchedulersProvider
import javax.inject.Inject
......@@ -13,7 +15,8 @@ import javax.inject.Inject
class MilestoneRepository @Inject constructor(
private val api: GitlabApi,
private val schedulers: SchedulersProvider,
@DefaultPageSize private val defaultPageSizeWrapper: PrimitiveWrapper<Int>
@DefaultPageSize private val defaultPageSizeWrapper: PrimitiveWrapper<Int>,
private val projectMilestoneCache: ProjectMilestoneCache
) {
private val defaultPageSize = defaultPageSizeWrapper.value
......@@ -27,6 +30,32 @@ class MilestoneRepository @Inject constructor(
.subscribeOn(schedulers.io())
.observeOn(schedulers.ui())
fun getAllProjectMilestones(projectId: Long): Single<List<Milestone>> =
Single
.defer {
val milestones = projectMilestoneCache.get(projectId)
if (milestones != null) {
Single.just(milestones)
} else {
getAllProjectMilestonesFromServer(projectId)
.doOnSuccess { milestones -> projectMilestoneCache.put(projectId, milestones) }
}
}
.subscribeOn(schedulers.io())
.observeOn(schedulers.ui())
private fun getAllProjectMilestonesFromServer(
projectId: Long
): Single<List<Milestone>> =
Observable
.range(1, Integer.MAX_VALUE)
.concatMapSingle { page -> api.getMilestones(projectId, null, page, defaultPageSize) }
.takeWhile { milestones -> milestones.isNotEmpty() }
.reduce { allMilestones, currentMilestones -> allMilestones + currentMilestones }
.toSingle()
.subscribeOn(schedulers.io())
.observeOn(schedulers.ui())
fun getMilestone(
projectId: Long,
milestoneId: Long
......
package ru.terrakok.gitlabclient.presentation.global
import io.reactivex.Single
import ru.noties.markwon.Markwon
import ru.noties.markwon.SpannableConfiguration
import org.commonmark.node.Visitor
import org.commonmark.parser.Parser
import ru.noties.markwon.SpannableBuilder
import ru.terrakok.gitlabclient.markwonx.MarkdownDecorator
import ru.terrakok.gitlabclient.model.system.SchedulersProvider
/**
* @author Konstantin Tskhovrebov (aka terrakok). Date: 28.05.17
*/
class MarkDownConverter(
private val config: SpannableConfiguration,
private val parser: Parser,
private val decorator: MarkdownDecorator,
private val visitorFactory: (projectId: Long?, builder: SpannableBuilder) -> Single<Visitor>,
private val schedulers: SchedulersProvider
) {
fun markdownToSpannable(raw: String): Single<CharSequence> =
Single
.fromCallable { Markwon.markdown(config, raw) }
fun markdownToSpannable(raw: String, projectId: Long?): Single<CharSequence> = Single.defer {
val builder = SpannableBuilder()
visitorFactory(projectId, builder)
.map { visitor ->
val decorated = decorator.decorate(raw)
val node = parser.parse(decorated)
node.accept(visitor)
builder.text()
}
.subscribeOn(schedulers.computation())
.observeOn(schedulers.ui())
}
}
\ No newline at end of file
......@@ -2,4 +2,4 @@ package ru.terrakok.gitlabclient.presentation.global
import ru.terrakok.gitlabclient.entity.Note
data class NoteWithFormattedBody(val note: Note, val body: CharSequence)
\ No newline at end of file
data class NoteWithProjectId(val note: Note, val projectId: Long)
\ No newline at end of file
......@@ -7,7 +7,6 @@ import ru.terrakok.gitlabclient.di.ProjectId
import ru.terrakok.gitlabclient.model.interactor.issue.IssueInteractor
import ru.terrakok.gitlabclient.presentation.global.BasePresenter
import ru.terrakok.gitlabclient.presentation.global.ErrorHandler
import ru.terrakok.gitlabclient.presentation.global.MarkDownConverter
import javax.inject.Inject
/**
......@@ -18,7 +17,6 @@ class IssueInfoPresenter @Inject constructor(
@ProjectId private val projectIdWrapper: PrimitiveWrapper<Long>,
@IssueId private val issueIdWrapper: PrimitiveWrapper<Long>,
private val issueInteractor: IssueInteractor,
private val mdConverter: MarkDownConverter,
private val errorHandler: ErrorHandler
) : BasePresenter<IssueInfoView>() {
......@@ -30,15 +28,10 @@ class IssueInfoPresenter @Inject constructor(
issueInteractor
.getIssue(projectId, issueId)
.flatMap { issue ->
mdConverter
.markdownToSpannable(issue.description)
.map { Pair(issue, it) }
}
.doOnSubscribe { viewState.showEmptyProgress(true) }
.doAfterTerminate { viewState.showEmptyProgress(false) }
.subscribe(
{ (issue, mdDescription) -> viewState.showInfo(issue, mdDescription) },
{ issue -> viewState.showInfo(issue) },
{ errorHandler.proceed(it, { viewState.showMessage(it) }) }
)
.connect()
......
......@@ -11,7 +11,7 @@ import ru.terrakok.gitlabclient.entity.issue.Issue
*/
@StateStrategyType(AddToEndSingleStrategy::class)
interface IssueInfoView : MvpView {
fun showInfo(issue: Issue, mdDescription: CharSequence)
fun showInfo(issue: Issue)
fun showEmptyProgress(show: Boolean)
@StateStrategyType(OneExecutionStateStrategy::class)
......
......@@ -5,10 +5,9 @@ import ru.terrakok.gitlabclient.di.IssueId
import ru.terrakok.gitlabclient.di.PrimitiveWrapper
import ru.terrakok.gitlabclient.di.ProjectId
import ru.terrakok.gitlabclient.model.interactor.issue.IssueInteractor
import ru.terrakok.gitlabclient.presentation.global.NoteWithProjectId
import ru.terrakok.gitlabclient.presentation.global.BasePresenter
import ru.terrakok.gitlabclient.presentation.global.ErrorHandler
import ru.terrakok.gitlabclient.presentation.global.MarkDownConverter
import ru.terrakok.gitlabclient.presentation.global.NoteWithFormattedBody
import javax.inject.Inject
/**
......@@ -19,7 +18,6 @@ class IssueNotesPresenter @Inject constructor(
@ProjectId projectIdWrapper: PrimitiveWrapper<Long>,
@IssueId issueIdWrapper: PrimitiveWrapper<Long>,
private val issueInteractor: IssueInteractor,
private val mdConverter: MarkDownConverter,
private val errorHandler: ErrorHandler
) : BasePresenter<IssueNotesView>() {
......@@ -33,15 +31,13 @@ class IssueNotesPresenter @Inject constructor(
.getAllIssueNotes(projectId, issueId)
.doOnSubscribe { viewState.showEmptyProgress(true) }
.doAfterTerminate { viewState.showEmptyProgress(false) }
.flattenAsObservable { it }
.concatMap { note ->
mdConverter.markdownToSpannable(note.body)
.map { NoteWithFormattedBody(note, it) }
.toObservable()
}
.toList()
.subscribe(
{ viewState.showNotes(it, false) },
{ viewState.showNotes(it.map {
NoteWithProjectId(
it,
projectId
)
}, false) },
{ errorHandler.proceed(it, { viewState.showMessage(it) }) }
)
.connect()
......@@ -51,18 +47,16 @@ class IssueNotesPresenter @Inject constructor(
issueInteractor.createIssueNote(projectId, issueId, body)
.flatMap {
issueInteractor.getAllIssueNotes(projectId, issueId)
.flattenAsObservable { it }
.concatMap { note ->
mdConverter.markdownToSpannable(note.body)
.map { NoteWithFormattedBody(note, it) }
.toObservable()
}
.toList()
}
.doOnSubscribe { viewState.showBlockingProgress(true) }
.doAfterTerminate { viewState.showBlockingProgress(false) }
.subscribe(
{ viewState.showNotes(it, true) },
{ viewState.showNotes(it.map {
NoteWithProjectId(
it,
projectId
)
}, true) },
{ errorHandler.proceed(it, { viewState.showMessage(it) }) }
)
.connect()
......
......@@ -4,7 +4,7 @@ import com.arellomobile.mvp.MvpView
import com.arellomobile.mvp.viewstate.strategy.AddToEndSingleStrategy
import com.arellomobile.mvp.viewstate.strategy.OneExecutionStateStrategy
import com.arellomobile.mvp.viewstate.strategy.StateStrategyType
import ru.terrakok.gitlabclient.presentation.global.NoteWithFormattedBody
import ru.terrakok.gitlabclient.presentation.global.NoteWithProjectId
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 12.02.18.
......@@ -13,7 +13,7 @@ import ru.terrakok.gitlabclient.presentation.global.NoteWithFormattedBody
interface IssueNotesView : MvpView {
fun showEmptyProgress(show: Boolean)
fun showNotes(notes: List<NoteWithFormattedBody>, scrollToEnd: Boolean)
fun showNotes(notes: List<NoteWithProjectId>, scrollToEnd: Boolean)
fun showBlockingProgress(show: Boolean)
@StateStrategyType(OneExecutionStateStrategy::class)
......
package ru.terrakok.gitlabclient.presentation.markdown
import com.arellomobile.mvp.InjectViewState
import io.reactivex.disposables.Disposable
import ru.terrakok.gitlabclient.markwonx.GitlabMarkdownExtension
import ru.terrakok.gitlabclient.markwonx.MarkdownClickMediator
import ru.terrakok.gitlabclient.markwonx.label.LabelDescription
import ru.terrakok.gitlabclient.markwonx.milestone.MilestoneDescription
import ru.terrakok.gitlabclient.presentation.global.BasePresenter
import ru.terrakok.gitlabclient.presentation.global.ErrorHandler
import timber.log.Timber
import ru.terrakok.gitlabclient.presentation.global.MarkDownConverter
import javax.inject.Inject
@InjectViewState
class MarkdownPresenter @Inject constructor(
private val mdConverter: MarkDownConverter,
private val errorHandler: ErrorHandler,
private val markdownClickMediator: MarkdownClickMediator
) : BasePresenter<MarkdownView>() {
private var conversionDisposable: Disposable? = null
fun setMarkdown(markdown: String, projectId: Long?) {
conversionDisposable?.dispose()
conversionDisposable = mdConverter
.markdownToSpannable(markdown, projectId)
.subscribe(
{ viewState.setMarkdownText(it) },
{ errorHandler.proceed(it) }
)
}
override fun onFirstViewAttach() {
markdownClickMediator
.getClickEvents()
.subscribe {
viewState.markdownClicked(it.extension, it.value)
}
.connect()
}
override fun detachView(view: MarkdownView?) {
super.detachView(view)
conversionDisposable?.dispose()
}
fun markdownClicked(extension: GitlabMarkdownExtension, value: Any) {
when (extension) {
GitlabMarkdownExtension.LABEL -> Timber.d("Label clicked: ${value as LabelDescription}")
GitlabMarkdownExtension.MILESTONE -> Timber.d("Milestione clicked: ${value as MilestoneDescription}")
}
}
override fun onDestroy() {
super.onDestroy()
conversionDisposable?.dispose()
}
}
\ No newline at end of file
package ru.terrakok.gitlabclient.presentation.markdown
import com.arellomobile.mvp.MvpView
import com.arellomobile.mvp.viewstate.strategy.OneExecutionStateStrategy
import com.arellomobile.mvp.viewstate.strategy.StateStrategyType
import ru.terrakok.gitlabclient.markwonx.GitlabMarkdownExtension
@StateStrategyType(OneExecutionStateStrategy::class)
interface MarkdownView : MvpView {
fun setMarkdownText(text: CharSequence)
fun markdownClicked(extension: GitlabMarkdownExtension, value: Any)
}
\ No newline at end of file
......@@ -8,7 +8,6 @@ import ru.terrakok.gitlabclient.entity.app.CommitWithAvatarUrl
import ru.terrakok.gitlabclient.model.interactor.mergerequest.MergeRequestInteractor
import ru.terrakok.gitlabclient.presentation.global.BasePresenter
import ru.terrakok.gitlabclient.presentation.global.ErrorHandler
import ru.terrakok.gitlabclient.presentation.global.MarkDownConverter
import ru.terrakok.gitlabclient.presentation.global.Paginator
import javax.inject.Inject
......@@ -20,7 +19,6 @@ class MergeRequestCommitsPresenter @Inject constructor(
@ProjectId projectIdWrapper: PrimitiveWrapper<Long>,
@MergeRequestId mrIdWrapper: PrimitiveWrapper<Long>,
private val mrInteractor: MergeRequestInteractor,
private val mdConverter: MarkDownConverter,
private val errorHandler: ErrorHandler
) : BasePresenter<MergeRequestCommitsView>() {
......
......@@ -7,7 +7,6 @@ import ru.terrakok.gitlabclient.di.ProjectId
import ru.terrakok.gitlabclient.model.interactor.mergerequest.MergeRequestInteractor
import ru.terrakok.gitlabclient.presentation.global.BasePresenter
import ru.terrakok.gitlabclient.presentation.global.ErrorHandler
import ru.terrakok.gitlabclient.presentation.global.MarkDownConverter
import javax.inject.Inject
/**
......@@ -18,7 +17,6 @@ class MergeRequestInfoPresenter @Inject constructor(
@ProjectId projectIdWrapper: PrimitiveWrapper<Long>,
@MergeRequestId mrIdWrapper: PrimitiveWrapper<Long>,
private val mrInteractor: MergeRequestInteractor,
private val mdConverter: MarkDownConverter,
private val errorHandler: ErrorHandler
) : BasePresenter<MergeRequestInfoView>() {
......@@ -30,15 +28,10 @@ class MergeRequestInfoPresenter @Inject constructor(
mrInteractor
.getMergeRequest(projectId, mrId)
.flatMap { mr ->
mdConverter
.markdownToSpannable(mr.description)
.map { Pair(mr, it) }
}
.doOnSubscribe { viewState.showEmptyProgress(true) }
.doAfterTerminate { viewState.showEmptyProgress(false) }
.subscribe(
{ (mr, mdDescription) -> viewState.showInfo(mr, mdDescription) },
{ mr -> viewState.showInfo(mr) },
{ errorHandler.proceed(it, { viewState.showMessage(it) }) }
)
.connect()
......
......@@ -11,7 +11,7 @@ import ru.terrakok.gitlabclient.entity.mergerequest.MergeRequest
*/
@StateStrategyType(AddToEndSingleStrategy::class)
interface MergeRequestInfoView : MvpView {
fun showInfo(mr: MergeRequest, mdDescription: CharSequence)
fun showInfo(mr: MergeRequest)
fun showEmptyProgress(show: Boolean)
@StateStrategyType(OneExecutionStateStrategy::class)
......
......@@ -7,8 +7,7 @@ import ru.terrakok.gitlabclient.di.ProjectId
import ru.terrakok.gitlabclient.model.interactor.mergerequest.MergeRequestInteractor
import ru.terrakok.gitlabclient.presentation.global.BasePresenter
import ru.terrakok.gitlabclient.presentation.global.ErrorHandler
import ru.terrakok.gitlabclient.presentation.global.MarkDownConverter
import ru.terrakok.gitlabclient.presentation.global.NoteWithFormattedBody
import ru.terrakok.gitlabclient.presentation.global.NoteWithProjectId
import javax.inject.Inject
/**
......@@ -19,7 +18,6 @@ class MergeRequestNotesPresenter @Inject constructor(
@ProjectId projectIdWrapper: PrimitiveWrapper<Long>,
@MergeRequestId mrIdWrapper: PrimitiveWrapper<Long>,
private val mrInteractor: MergeRequestInteractor,
private val mdConverter: MarkDownConverter,
private val errorHandler: ErrorHandler
) : BasePresenter<MergeRequestNotesView>() {
......@@ -33,15 +31,8 @@ class MergeRequestNotesPresenter @Inject constructor(
.getAllMergeRequestNotes(projectId, mrId)
.doOnSubscribe { viewState.showEmptyProgress(true) }
.doAfterTerminate { viewState.showEmptyProgress(false) }
.flattenAsObservable { it }
.concatMap { note ->
mdConverter.markdownToSpannable(note.body)
.map { NoteWithFormattedBody(note, it) }
.toObservable()
}
.toList()
.subscribe(
{ viewState.showNotes(it, false) },
{ viewState.showNotes(it.map { NoteWithProjectId(it, projectId) }, false) },
{ errorHandler.proceed(it, { viewState.showMessage(it) }) }
)
.connect()
......@@ -49,20 +40,11 @@ class MergeRequestNotesPresenter @Inject constructor(
fun onSendClicked(body: String) =
mrInteractor.createMergeRequestNote(projectId, mrId, body)
.flatMap {
mrInteractor.getAllMergeRequestNotes(projectId, mrId)
.flattenAsObservable { it }
.concatMap { note ->
mdConverter.markdownToSpannable(note.body)
.map { NoteWithFormattedBody(note, it) }
.toObservable()
}
.toList()
}
.flatMap { mrInteractor.getAllMergeRequestNotes(projectId, mrId) }
.doOnSubscribe { viewState.showBlockingProgress(true) }
.doAfterTerminate { viewState.showBlockingProgress(false) }
.subscribe(
{ viewState.showNotes(it, true) },
{ viewState.showNotes(it.map { NoteWithProjectId(it, projectId) }, true) },
{ errorHandler.proceed(it, { viewState.showMessage(it) }) }
)
.connect()
......
......@@ -4,7 +4,7 @@ import com.arellomobile.mvp.MvpView
import com.arellomobile.mvp.viewstate.strategy.AddToEndSingleStrategy
import com.arellomobile.mvp.viewstate.strategy.OneExecutionStateStrategy
import com.arellomobile.mvp.viewstate.strategy.StateStrategyType
import ru.terrakok.gitlabclient.presentation.global.NoteWithFormattedBody
import ru.terrakok.gitlabclient.presentation.global.NoteWithProjectId
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 12.02.18.
......@@ -13,7 +13,7 @@ import ru.terrakok.gitlabclient.presentation.global.NoteWithFormattedBody
interface MergeRequestNotesView : MvpView {
fun showEmptyProgress(show: Boolean)
fun showNotes(notes: List<NoteWithFormattedBody>, scrollToEnd: Boolean)
fun showNotes(notes: List<NoteWithProjectId>, scrollToEnd: Boolean)
fun showBlockingProgress(show: Boolean)
@StateStrategyType(OneExecutionStateStrategy::class)
......
package ru.terrakok.gitlabclient.presentation.my.events
import com.arellomobile.mvp.InjectViewState
import io.reactivex.Observable
import ru.terrakok.gitlabclient.Screens
import ru.terrakok.gitlabclient.entity.app.target.TargetHeader
import ru.terrakok.gitlabclient.extension.openInfo
import ru.terrakok.gitlabclient.model.interactor.event.EventInteractor
import ru.terrakok.gitlabclient.model.system.flow.FlowRouter
import ru.terrakok.gitlabclient.presentation.global.*
import ru.terrakok.gitlabclient.presentation.global.BasePresenter
import ru.terrakok.gitlabclient.presentation.global.ErrorHandler
import ru.terrakok.gitlabclient.presentation.global.GlobalMenuController
import ru.terrakok.gitlabclient.presentation.global.Paginator
import javax.inject.Inject
/**
......@@ -16,7 +18,6 @@ import javax.inject.Inject
@InjectViewState
class MyEventsPresenter @Inject constructor(
private val eventInteractor: EventInteractor,
private val mdConverter: MarkDownConverter,
private val menuController: GlobalMenuController,
private val errorHandler: ErrorHandler,
private val router: FlowRouter
......@@ -29,21 +30,7 @@ class MyEventsPresenter @Inject constructor(
}
private val paginator = Paginator(
{
eventInteractor.getEvents(it)
.flattenAsObservable { it }
.concatMap { item ->
when (item) {
is TargetHeader.Public -> {
mdConverter.markdownToSpannable(item.body.toString())
.map { md -> item.copy(body = md) }
.toObservable()
}
is TargetHeader.Confidential -> Observable.just(item)
}
}
.toList()
},
{ eventInteractor.getEvents(it) },
object : Paginator.ViewController<TargetHeader> {
override fun showEmptyProgress(show: Boolean) {
viewState.showEmptyProgress(show)
......
package ru.terrakok.gitlabclient.presentation.my.issues
import com.arellomobile.mvp.InjectViewState
import io.reactivex.Observable
import ru.terrakok.gitlabclient.Screens
import ru.terrakok.gitlabclient.entity.app.target.TargetHeader
import ru.terrakok.gitlabclient.extension.openInfo
......@@ -9,7 +8,6 @@ import ru.terrakok.gitlabclient.model.interactor.issue.IssueInteractor
import ru.terrakok.gitlabclient.model.system.flow.FlowRouter
import ru.terrakok.gitlabclient.presentation.global.BasePresenter
import ru.terrakok.gitlabclient.presentation.global.ErrorHandler
import ru.terrakok.gitlabclient.presentation.global.MarkDownConverter
import ru.terrakok.gitlabclient.presentation.global.Paginator
import javax.inject.Inject
......@@ -20,7 +18,6 @@ import javax.inject.Inject
class MyIssuesPresenter @Inject constructor(
initFilter: Filter,
private val issueInteractor: IssueInteractor,
private val mdConverter: MarkDownConverter,
private val errorHandler: ErrorHandler,
private val router: FlowRouter
) : BasePresenter<MyIssuesView>() {
......@@ -35,21 +32,7 @@ class MyIssuesPresenter @Inject constructor(
}
private val paginator = Paginator(
{
issueInteractor.getMyIssues(filter.createdByMe, filter.onlyOpened, it)
.flattenAsObservable { it }
.concatMap { item ->
when (item) {
is TargetHeader.Public -> {
mdConverter.markdownToSpannable(item.body.toString())
.map { md -> item.copy(body = md) }
.toObservable()
}
is TargetHeader.Confidential -> Observable.just(item)
}
}
.toList()
},
{ issueInteractor.getMyIssues(filter.createdByMe, filter.onlyOpened, it) },
object : Paginator.ViewController<TargetHeader> {
override fun showEmptyProgress(show: Boolean) {
viewState.showEmptyProgress(show)
......
package ru.terrakok.gitlabclient.presentation.my.mergerequests
import com.arellomobile.mvp.InjectViewState
import io.reactivex.Observable
import ru.terrakok.gitlabclient.Screens
import ru.terrakok.gitlabclient.entity.app.target.TargetHeader
import ru.terrakok.gitlabclient.extension.openInfo
......@@ -9,7 +8,6 @@ import ru.terrakok.gitlabclient.model.interactor.mergerequest.MergeRequestIntera
import ru.terrakok.gitlabclient.model.system.flow.FlowRouter
import ru.terrakok.gitlabclient.presentation.global.BasePresenter
import ru.terrakok.gitlabclient.presentation.global.ErrorHandler
import ru.terrakok.gitlabclient.presentation.global.MarkDownConverter
import ru.terrakok.gitlabclient.presentation.global.Paginator
import javax.inject.Inject
......@@ -17,7 +15,6 @@ import javax.inject.Inject
class MyMergeRequestsPresenter @Inject constructor(
initFilter: Filter,
private val interactor: MergeRequestInteractor,
private val mdConverter: MarkDownConverter,
private val errorHandler: ErrorHandler,
private val router: FlowRouter
) : BasePresenter<MyMergeRequestListView>() {
......@@ -32,21 +29,7 @@ class MyMergeRequestsPresenter @Inject constructor(
}
private val paginator = Paginator(
{
interactor.getMyMergeRequests(filter.createdByMe, filter.onlyOpened, it)
.flattenAsObservable { it }
.concatMap { item ->
when (item) {
is TargetHeader.Public -> {
mdConverter.markdownToSpannable(item.body.toString())
.map { md -> item.copy(body = md) }
.toObservable()
}
is TargetHeader.Confidential -> Observable.just(item)
}
}
.toList()
},
{ interactor.getMyMergeRequests(filter.createdByMe, filter.onlyOpened, it) },
object : Paginator.ViewController<TargetHeader> {
override fun showEmptyProgress(show: Boolean) {
viewState.showEmptyProgress(show)
......
package ru.terrakok.gitlabclient.presentation.my.todos
import com.arellomobile.mvp.InjectViewState
import io.reactivex.Observable
import ru.terrakok.gitlabclient.Screens
import ru.terrakok.gitlabclient.di.PrimitiveWrapper
import ru.terrakok.gitlabclient.di.TodoListPendingState
......@@ -11,7 +10,6 @@ import ru.terrakok.gitlabclient.model.interactor.todo.TodoListInteractor
import ru.terrakok.gitlabclient.model.system.flow.FlowRouter
import ru.terrakok.gitlabclient.presentation.global.BasePresenter
import ru.terrakok.gitlabclient.presentation.global.ErrorHandler
import ru.terrakok.gitlabclient.presentation.global.MarkDownConverter
import ru.terrakok.gitlabclient.presentation.global.Paginator
import javax.inject.Inject
......@@ -22,7 +20,6 @@ import javax.inject.Inject
class MyTodosPresenter @Inject constructor(
@TodoListPendingState private val pendingStateWrapper: PrimitiveWrapper<Boolean>,
private val todoListInteractor: TodoListInteractor,
private val mdConverter: MarkDownConverter,
private val errorHandler: ErrorHandler,
private val router: FlowRouter
) : BasePresenter<MyTodoListView>() {
......@@ -36,21 +33,7 @@ class MyTodosPresenter @Inject constructor(
}
private val paginator = Paginator(
{
todoListInteractor.getMyTodos(isPending, it)
.flattenAsObservable { it }
.concatMap { item ->
when (item) {
is TargetHeader.Public -> {
mdConverter.markdownToSpannable(item.body.toString())
.map { md -> item.copy(body = md) }
.toObservable()
}
is TargetHeader.Confidential -> Observable.just(item)
}
}
.toList()
},
{ todoListInteractor.getMyTodos(isPending, it) },
object : Paginator.ViewController<TargetHeader> {
override fun showEmptyProgress(show: Boolean) {
viewState.showEmptyProgress(show)
......
package ru.terrakok.gitlabclient.presentation.project.events
import com.arellomobile.mvp.InjectViewState
import io.reactivex.Observable
import ru.terrakok.gitlabclient.Screens
import ru.terrakok.gitlabclient.di.PrimitiveWrapper
import ru.terrakok.gitlabclient.di.ProjectId
......@@ -11,7 +10,6 @@ import ru.terrakok.gitlabclient.model.interactor.event.EventInteractor
import ru.terrakok.gitlabclient.model.system.flow.FlowRouter
import ru.terrakok.gitlabclient.presentation.global.BasePresenter
import ru.terrakok.gitlabclient.presentation.global.ErrorHandler
import ru.terrakok.gitlabclient.presentation.global.MarkDownConverter
import ru.terrakok.gitlabclient.presentation.global.Paginator
import javax.inject.Inject