Skip to content

Commit

Permalink
Merge branch 'release/1.68'
Browse files Browse the repository at this point in the history
  • Loading branch information
eadm committed Sep 22, 2018
2 parents fa0b1cb + 783b4e8 commit 7ad7cbf
Show file tree
Hide file tree
Showing 58 changed files with 1,225 additions and 29 deletions.
4 changes: 4 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ dependencies {
implementation libraries.MPAndroidChart

implementation libraries.shortcutBadger
implementation (libraries.StoriesKit) {
exclude group: 'com.android.support'
changing = true
}

debugImplementation libraries.leakCanary
releaseImplementation libraries.leakCanaryNoOp
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,11 @@
<activity
android:name=".features.achievements.ui.activity.AchievementsListActivity" />

<activity
android:name=".features.stories.ui.activity.StoriesActivity"
android:theme="@style/StoryActivityTheme"
android:screenOrientation="portrait" />

<receiver
android:name="org.stepic.droid.receivers.DownloadClickReceiver"
android:exported="true">
Expand Down
19 changes: 19 additions & 0 deletions app/src/main/java/org/stepic/droid/analytic/AmplitudeAnalytic.kt
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,23 @@ interface AmplitudeAnalytic {

const val PARAM_SUGGESTION = "suggestion"
}

object Stories {
const val STORY_OPENED = "Story opened"
const val STORY_PART_OPENED = "Story part opened"
const val BUTTON_PRESSED = "Button pressed"
const val STORY_CLOSED = "Story closed"

object Values {
const val STORY_ID = "id"
const val POSITION = "position"
const val CLOSE_TYPE = "type"

object CloseTypes {
const val AUTO = "automatic"
const val SWIPE = "swipe"
const val CROSS = "cross"
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import org.stepic.droid.features.achievements.repository.AchievementsRepository
import org.stepic.droid.features.achievements.repository.AchievementsRepositoryImpl
import org.stepic.droid.features.deadlines.repository.DeadlinesRepository
import org.stepic.droid.features.deadlines.repository.DeadlinesRepositoryImpl
import org.stepic.droid.features.stories.repository.StoryTemplatesRepository
import org.stepic.droid.features.stories.repository.StoryTemplatesRepositoryImpl
import org.stepic.droid.web.ApiImpl
import org.stepic.droid.web.StepicRestLoggedService
import org.stepic.droid.web.achievements.AchievementsService
Expand All @@ -23,6 +25,9 @@ abstract class NetworkModule {
@AppSingleton
abstract fun bindAchievementsRepository(achievementsRepositoryImpl: AchievementsRepositoryImpl): AchievementsRepository

@Binds
abstract fun bindStoryTemplatesRepository(storyTemplatesRepositoryImpl: StoryTemplatesRepositoryImpl): StoryTemplatesRepository

@Module
companion object {
@Provides
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import dagger.BindsInstance
import dagger.Component
import org.stepic.droid.features.deadlines.storage.dao.DeadlinesBannerDao
import org.stepic.droid.features.deadlines.storage.operations.DeadlinesRecordOperations
import org.stepic.droid.features.stories.model.ViewedStoryTemplate
import org.stepic.droid.persistence.storage.dao.PersistentItemDao
import org.stepic.droid.persistence.storage.dao.PersistentStateDao
import org.stepic.droid.storage.dao.IDao
import org.stepic.droid.storage.operations.DatabaseFacade

@Component(modules = [StorageModule::class])
Expand All @@ -27,4 +29,6 @@ interface StorageComponent {
val deadlinesBannerDao: DeadlinesBannerDao
val persistentItemDao: PersistentItemDao
val persistentStateDao: PersistentStateDao

val viewedStoryTemplatesDao: IDao<ViewedStoryTemplate>
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import org.stepic.droid.features.deadlines.storage.operations.DeadlinesRecordOpe
import org.stepic.droid.features.deadlines.storage.operations.DeadlinesRecordOperationsImpl
import org.stepic.droid.features.deadlines.storage.dao.PersonalDeadlinesDao
import org.stepic.droid.features.deadlines.storage.dao.PersonalDeadlinesDaoImpl
import org.stepic.droid.features.stories.model.ViewedStoryTemplate
import org.stepic.droid.features.stories.storage.dao.ViewedStoryTemplatesDaoImpl
import org.stepic.droid.jsonHelpers.adapters.UTCDateAdapter
import org.stepic.droid.model.*
import org.stepic.droid.model.code.CodeSubmission
Expand Down Expand Up @@ -136,6 +138,10 @@ abstract class StorageModule {
@Binds
internal abstract fun providePersistentStateDao(persistentStateDaoImpl: PersistentStateDaoImpl): PersistentStateDao

@StorageSingleton
@Binds
internal abstract fun provideViewedStoryTemplatesDao(viewedStoryTemplatesDaoImpl: ViewedStoryTemplatesDaoImpl): IDao<ViewedStoryTemplate>

@Module
companion object {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.stepic.droid.features.stories.mapper

import org.stepic.droid.features.stories.model.PlainTextWithButtonStoryPart
import org.stepik.android.model.StoryTemplate
import ru.nobird.android.stories.model.Story
import ru.nobird.android.stories.model.StoryPart

fun StoryTemplate.toStory(): Story =
Story(
id,
title,
cover,
parts.map(StoryTemplate.Part::toStoryPart)
)

fun StoryTemplate.Part.toStoryPart(): StoryPart =
PlainTextWithButtonStoryPart(
duration * 1000, // convert seconds to ms
image,
button,
text
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.stepic.droid.features.stories.model

import android.os.Parcel
import android.os.Parcelable
import org.stepik.android.model.StoryTemplate
import ru.nobird.android.stories.model.StoryPart

class PlainTextWithButtonStoryPart(
duration: Long,
image: String,

val button: StoryTemplate.Button?,
val text: StoryTemplate.Text?
): StoryPart(duration, image) {
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeLong(duration)
parcel.writeString(cover)
parcel.writeParcelable(button, flags)
parcel.writeParcelable(text, flags)
}

override fun describeContents(): Int = 0

companion object CREATOR : Parcelable.Creator<PlainTextWithButtonStoryPart> {
override fun createFromParcel(parcel: Parcel) = PlainTextWithButtonStoryPart(
parcel.readLong(),
parcel.readString(),
parcel.readParcelable(StoryTemplate.Button::class.java.classLoader),
parcel.readParcelable(StoryTemplate.Text::class.java.classLoader)
)

override fun newArray(size: Int) =
arrayOfNulls<PlainTextWithButtonStoryPart>(size)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.stepic.droid.features.stories.model

class ViewedStoryTemplate(val storyTemplateId: Long)
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package org.stepic.droid.features.stories.presentation

import io.reactivex.Scheduler
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.Singles.zip
import io.reactivex.rxkotlin.subscribeBy
import org.stepic.droid.core.presenters.PresenterBase
import org.stepic.droid.di.qualifiers.BackgroundScheduler
import org.stepic.droid.di.qualifiers.MainScheduler
import org.stepic.droid.features.stories.mapper.toStory
import org.stepic.droid.features.stories.repository.StoryTemplatesRepository
import org.stepic.droid.util.addDisposable
import org.stepik.android.model.StoryTemplate
import javax.inject.Inject

class StoriesPresenter
@Inject
constructor(
private val storiesRepository: StoryTemplatesRepository,

@BackgroundScheduler
private val backgroundScheduler: Scheduler,
@MainScheduler
private val mainScheduler: Scheduler
) : PresenterBase<StoriesView>() {
private var state : StoriesView.State = StoriesView.State.Idle
set(value) {
field = value
view?.setState(value)
}

private val compositeDisposable = CompositeDisposable()

init {
fetchStories()
}

private fun fetchStories() {
if (state != StoriesView.State.Idle) return
state = StoriesView.State.Loading

compositeDisposable addDisposable
zip(storiesRepository.getStoryTemplates().map { it.map(StoryTemplate::toStory) }, storiesRepository.getViewedStoriesIds())
.subscribeOn(backgroundScheduler)
.observeOn(mainScheduler)
.subscribeBy({
state = StoriesView.State.Empty
}) { (stories, viewedIds) ->
state = if (stories.isNotEmpty()) {
StoriesView.State.Success(stories, viewedIds)
} else {
StoriesView.State.Empty
}
}
}

override fun attachView(view: StoriesView) {
super.attachView(view)
view.setState(state)
}

fun onStoryViewed(storyId: Long) {
compositeDisposable addDisposable storiesRepository
.markStoryAsViewed(storyId)
.subscribeBy({ /* ignore */ }) {
val state = this.state
if (state is StoriesView.State.Success) {
this.state = state.copy(viewedStoriesIds = state.viewedStoriesIds + storyId)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.stepic.droid.features.stories.presentation

import ru.nobird.android.stories.model.Story

interface StoriesView {
fun setState(state: State)

sealed class State {
object Idle : State()
object Loading : State()
object Empty : State()
data class Success(val stories: List<Story>, val viewedStoriesIds: Set<Long>) : State()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.stepic.droid.features.stories.repository

import io.reactivex.Completable
import io.reactivex.Single
import org.stepik.android.model.StoryTemplate

interface StoryTemplatesRepository {
fun getStoryTemplates(): Single<List<StoryTemplate>>

fun getViewedStoriesIds(): Single<Set<Long>>

fun markStoryAsViewed(storyTemplateId: Long): Completable
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.stepic.droid.features.stories.repository

import io.reactivex.Completable
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.rxkotlin.toObservable
import org.stepic.droid.di.AppSingleton
import org.stepic.droid.features.stories.model.ViewedStoryTemplate
import org.stepic.droid.storage.dao.IDao
import org.stepic.droid.web.Api
import org.stepic.droid.web.model.story_templates.StoryTemplatesResponse
import org.stepik.android.model.StoryTemplate
import javax.inject.Inject

@AppSingleton
class StoryTemplatesRepositoryImpl
@Inject
constructor(
private val api: Api,
private val viewedStoryTemplateDao: IDao<ViewedStoryTemplate>
) : StoryTemplatesRepository {
companion object {
const val STORY_TEMPLATES_VERSION = 1
}

override fun getStoryTemplates(): Single<List<StoryTemplate>> =
getStoryTemplatesByPage(1)
.concatMap { it.storyTemplates.toObservable() }
.toList()
.map {
it.asSequence()
.filter { template -> template.version <= STORY_TEMPLATES_VERSION }
.sortedBy(StoryTemplate::position)
.toList()
}

private fun getStoryTemplatesByPage(page: Int): Observable<StoryTemplatesResponse> =
api.getStoryTemplates(page)
.concatMap {
val templatesObservable = Observable.just(it)
if (it.meta.hasNext) {
templatesObservable.concatWith(getStoryTemplatesByPage(it.meta.page + 1))
} else {
templatesObservable
}
}

override fun getViewedStoriesIds(): Single<Set<Long>> = Single.create { emitter ->
emitter.onSuccess(
viewedStoryTemplateDao
.getAll()
.asSequence()
.map { it.storyTemplateId }
.toSet()
)
}

override fun markStoryAsViewed(storyTemplateId: Long): Completable = Completable.fromAction {
viewedStoryTemplateDao.insertOrReplace(ViewedStoryTemplate(storyTemplateId))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.stepic.droid.features.stories.storage.dao

import android.content.ContentValues
import android.database.Cursor
import org.stepic.droid.di.storage.StorageSingleton
import org.stepic.droid.features.stories.model.ViewedStoryTemplate
import org.stepic.droid.features.stories.storage.structure.DbStructureViewedStoryTemplates
import org.stepic.droid.storage.dao.DaoBase
import org.stepic.droid.storage.operations.DatabaseOperations
import javax.inject.Inject

@StorageSingleton
class ViewedStoryTemplatesDaoImpl
@Inject
constructor(databaseOperations: DatabaseOperations): DaoBase<ViewedStoryTemplate>(databaseOperations) {
override fun getDbName(): String =
DbStructureViewedStoryTemplates.VIEWED_STORY_TEMPLATES

override fun getDefaultPrimaryColumn(): String =
DbStructureViewedStoryTemplates.Columns.ID

override fun getDefaultPrimaryValue(persistentObject: ViewedStoryTemplate): String =
persistentObject.storyTemplateId.toString()

override fun getContentValues(persistentObject: ViewedStoryTemplate): ContentValues = ContentValues(1).apply {
put(DbStructureViewedStoryTemplates.Columns.ID, persistentObject.storyTemplateId)
}

override fun parsePersistentObject(cursor: Cursor): ViewedStoryTemplate =
ViewedStoryTemplate(cursor.getLong(cursor.getColumnIndex(DbStructureViewedStoryTemplates.Columns.ID)))

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.stepic.droid.features.stories.storage.structure

import android.database.sqlite.SQLiteDatabase

object DbStructureViewedStoryTemplates {
const val VIEWED_STORY_TEMPLATES = "viewed_story_templates"

object Columns {
const val ID = "id"
}

fun createTable(db: SQLiteDatabase) {
val sql = """
CREATE TABLE IF NOT EXISTS $VIEWED_STORY_TEMPLATES (
${Columns.ID} LONG,
PRIMARY KEY(${Columns.ID})
)""".trimIndent()

db.execSQL(sql)
}
}
Loading

0 comments on commit 7ad7cbf

Please sign in to comment.