Skip to content

Commit d1c1c96

Browse files
Merge pull request #950 from StepicOrg/release/1.210
Release/1.210
2 parents 5b70493 + 7264420 commit d1c1c96

37 files changed

Lines changed: 926 additions & 40 deletions

app/build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@ dependencies {
181181
implementation libraries.domainRx
182182
implementation libraries.viewInjection
183183
implementation libraries.viewBindingDelegate
184-
implementation libraries.multisnaprecycler
185184

186185
implementation libraries.exoPlayerCore
187186
implementation libraries.exoPlayerUI

app/src/main/java/org/stepic/droid/configuration/RemoteConfig.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,6 @@ object RemoteConfig {
1717
const val PURCHASE_FLOW_DISCLAIMER_EN = "purchase_flow_android_disclaimer_en"
1818
const val PURCHASE_FLOW_DISCLAIMER_RU = "purchase_flow_android_disclaimer_ru"
1919
const val PURCHASE_FLOW_DISCLAIMER_BE = "purchase_flow_android_disclaimer_be"
20+
21+
const val BANNERS_ANDROID = "banners_android"
2022
}

app/src/main/java/org/stepic/droid/preferences/SharedPreferenceHelper.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ public class SharedPreferenceHelper {
110110
private final static String NIGHT_MODE = "night_mode";
111111
private final static String PERSONALIZED_COURSE_LIST = "personalized_course_list";
112112
private final static String IS_PERSONALIZED_ONBOARDING_WAS_SHOWN = "is_personalized_onboarding_was_shown";
113-
private final static String WISHLIST = "wishlist";
114113
private final static String ENDPOINT_CONFIG = "endpoint_config";
115114

116115
private OAuthResponse cachedAuthStepikResponse = null;

app/src/main/java/org/stepic/droid/ui/fragments/HomeFragment.kt

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import android.view.LayoutInflater
55
import android.view.View
66
import android.view.ViewGroup
77
import androidx.core.view.isVisible
8+
import androidx.core.view.updateLayoutParams
9+
import androidx.fragment.app.Fragment
10+
import androidx.fragment.app.findFragment
811
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
912
import kotlinx.android.synthetic.main.fragment_home.*
1013
import kotlinx.android.synthetic.main.home_streak_view.*
@@ -16,8 +19,16 @@ import org.stepic.droid.base.FragmentBase
1619
import org.stepic.droid.configuration.RemoteConfig
1720
import org.stepic.droid.core.presenters.HomeStreakPresenter
1821
import org.stepic.droid.core.presenters.contracts.HomeStreakView
22+
import org.stepic.droid.databinding.ItemBannerBinding
1923
import org.stepic.droid.util.commitNow
24+
import org.stepik.android.domain.banner.analytic.PromoBannerClickedAnalyticEvent
25+
import org.stepik.android.domain.banner.analytic.PromoBannerSeen
26+
import org.stepik.android.domain.banner.interactor.BannerInteractor
27+
import org.stepik.android.domain.banner.model.Banner
2028
import org.stepik.android.domain.home.interactor.HomeInteractor
29+
import org.stepik.android.view.banner.mapper.BannerResourcesMapper
30+
import org.stepik.android.view.banner.extension.bind
31+
import org.stepik.android.view.banner.extension.handleItemClick
2132
import org.stepik.android.view.course_list.ui.fragment.CourseListPopularFragment
2233
import org.stepik.android.view.course_list.ui.fragment.CourseListUserHorizontalFragment
2334
import org.stepik.android.view.course_list.ui.fragment.CourseListVisitedHorizontalFragment
@@ -28,6 +39,7 @@ import org.stepik.android.view.stories.ui.fragment.StoriesFragment
2839
import ru.nobird.android.stories.transition.SharedTransitionsManager
2940
import ru.nobird.android.stories.ui.delegate.SharedTransitionContainerDelegate
3041
import javax.inject.Inject
42+
import kotlin.math.min
3143

3244
class HomeFragment : FragmentBase(), HomeStreakView, FastContinueNewHomeFragment.Callback {
3345
companion object {
@@ -47,6 +59,12 @@ class HomeFragment : FragmentBase(), HomeStreakView, FastContinueNewHomeFragment
4759
@Inject
4860
lateinit var remoteConfig: FirebaseRemoteConfig
4961

62+
@Inject
63+
lateinit var bannerResourcesMapper: BannerResourcesMapper
64+
65+
@Inject
66+
lateinit var bannerInteractor: BannerInteractor
67+
5068
override fun onCreate(savedInstanceState: Bundle?) {
5169
super.onCreate(savedInstanceState)
5270
analytic.reportAmplitudeEvent(AmplitudeAnalytic.Home.HOME_SCREEN_OPENED)
@@ -75,6 +93,8 @@ class HomeFragment : FragmentBase(), HomeStreakView, FastContinueNewHomeFragment
7593

7694
homeStreakPresenter.attachView(this)
7795
homeStreakPresenter.onNeedShowStreak()
96+
97+
homeMainContainer.post { setupBanners() }
7898
}
7999

80100
override fun onStart() {
@@ -138,6 +158,50 @@ class HomeFragment : FragmentBase(), HomeStreakView, FastContinueNewHomeFragment
138158
}
139159
}
140160

161+
private fun setupBanners() {
162+
val banners = bannerInteractor
163+
.getBanners(Banner.Screen.HOME)
164+
.blockingGet()
165+
166+
/**
167+
* Account for streak view and stories
168+
*/
169+
val offset =
170+
if (remoteConfig.getBoolean(RemoteConfig.IS_NEW_HOME_SCREEN_ENABLED)) {
171+
2
172+
} else {
173+
1
174+
}
175+
176+
banners.forEach { banner ->
177+
val binding = ItemBannerBinding.inflate(layoutInflater, homeMainContainer, false)
178+
179+
binding.root.setOnClickListener {
180+
// TODO // Probably better to move into ViewTreeObserver
181+
analytic.report(PromoBannerSeen(banner))
182+
183+
analytic.report(PromoBannerClickedAnalyticEvent(banner))
184+
binding.handleItemClick(banner, childFragmentManager)
185+
}
186+
187+
binding.bind(banner, bannerResourcesMapper)
188+
189+
val insertionIndex = min(banner.position + offset, homeMainContainer.childCount)
190+
val previousFragment = homeMainContainer.getChildAt(insertionIndex - 1).findFragment<Fragment>()
191+
192+
homeMainContainer.addView(binding.root, insertionIndex)
193+
binding.root.updateLayoutParams<ViewGroup.MarginLayoutParams> {
194+
val margin =
195+
if (previousFragment is LearningActionsFragment) {
196+
resources.getDimensionPixelOffset(R.dimen.course_list_side_padding)
197+
} else {
198+
0
199+
}
200+
topMargin = margin
201+
}
202+
}
203+
}
204+
141205
override fun onFastContinueLoaded(isVisible: Boolean) {
142206
val padding = if (isVisible) {
143207
resources.getDimensionPixelOffset(R.dimen.fast_continue_widget_height)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package org.stepik.android.domain.banner.analytic
2+
3+
import org.stepik.android.domain.banner.model.Banner
4+
import org.stepik.android.domain.base.analytic.AnalyticEvent
5+
import ru.nobird.app.core.model.mapOfNotNull
6+
7+
class PromoBannerClickedAnalyticEvent(banner: Banner) : AnalyticEvent {
8+
companion object {
9+
private const val PARAM_TYPE = "type"
10+
private const val PARAM_LANG = "lang"
11+
private const val PARAM_TITLE = "title"
12+
private const val PARAM_DESCRIPTION = "description"
13+
private const val PARAM_URL = "url"
14+
private const val PARAM_SCREEN = "screen"
15+
private const val PARAM_POSITION = "position"
16+
}
17+
18+
override val name: String
19+
get() = "Promo banner clicked"
20+
21+
override val params: Map<String, Any> =
22+
mapOfNotNull(
23+
PARAM_TYPE to banner.type?.name?.lowercase(),
24+
PARAM_LANG to banner.language,
25+
PARAM_TITLE to banner.title,
26+
PARAM_DESCRIPTION to banner.description,
27+
PARAM_URL to banner.url,
28+
PARAM_SCREEN to banner.screen,
29+
PARAM_POSITION to banner.position
30+
)
31+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.stepik.android.domain.banner.analytic
2+
3+
import org.stepik.android.domain.banner.model.Banner
4+
import org.stepik.android.domain.base.analytic.AnalyticEvent
5+
import org.stepik.android.domain.base.analytic.AnalyticSource
6+
import ru.nobird.app.core.model.mapOfNotNull
7+
import java.util.EnumSet
8+
9+
class PromoBannerSeen(banner: Banner) : AnalyticEvent {
10+
companion object {
11+
private const val PARAM_TYPE = "type"
12+
private const val PARAM_LANG = "lang"
13+
private const val PARAM_TITLE = "title"
14+
private const val PARAM_DESCRIPTION = "description"
15+
private const val PARAM_URL = "url"
16+
private const val PARAM_SCREEN = "screen"
17+
private const val PARAM_POSITION = "position"
18+
}
19+
20+
override val name: String =
21+
"Promo banner seen"
22+
23+
override val params: Map<String, Any> =
24+
mapOfNotNull(
25+
PARAM_TYPE to banner.type?.name?.lowercase(),
26+
PARAM_LANG to banner.language,
27+
PARAM_TITLE to banner.title,
28+
PARAM_DESCRIPTION to banner.description,
29+
PARAM_URL to banner.url,
30+
PARAM_SCREEN to banner.screen,
31+
PARAM_POSITION to banner.position
32+
)
33+
34+
override val sources: EnumSet<AnalyticSource> =
35+
EnumSet.of(AnalyticSource.YANDEX)
36+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package org.stepik.android.domain.banner.interactor
2+
3+
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
4+
import com.google.gson.Gson
5+
import com.google.gson.reflect.TypeToken
6+
import io.reactivex.Single
7+
import org.stepic.droid.configuration.RemoteConfig
8+
import org.stepic.droid.preferences.SharedPreferenceHelper
9+
import org.stepik.android.domain.banner.model.Banner
10+
import java.lang.Exception
11+
import javax.inject.Inject
12+
13+
class BannerInteractor
14+
@Inject
15+
constructor(
16+
private val firebaseRemoteConfig: FirebaseRemoteConfig,
17+
private val sharedPreferenceHelper: SharedPreferenceHelper,
18+
private val gson: Gson
19+
) {
20+
fun getBanners(screen: Banner.Screen): Single<List<Banner>> =
21+
Single.fromCallable {
22+
val bannersJson = firebaseRemoteConfig.getString(RemoteConfig.BANNERS_ANDROID)
23+
if (bannersJson.isEmpty()) {
24+
emptyList()
25+
} else {
26+
try {
27+
val banners =
28+
gson.fromJson<List<Banner>>(
29+
bannersJson,
30+
TypeToken.getParameterized(
31+
ArrayList::class.java,
32+
Banner::class.java
33+
).type
34+
)
35+
36+
val language = sharedPreferenceHelper.languageForFeatured
37+
38+
banners.filter {
39+
it.type != null &&
40+
it.screen != null &&
41+
it.language == language &&
42+
it.screen == screen
43+
}
44+
} catch (e: Exception) {
45+
emptyList()
46+
}
47+
}
48+
}
49+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package org.stepik.android.domain.banner.model
2+
3+
import com.google.gson.annotations.SerializedName
4+
5+
data class Banner(
6+
@SerializedName("title")
7+
val title: String,
8+
@SerializedName("type")
9+
val type: ColorType?,
10+
@SerializedName("lang")
11+
val language: String,
12+
@SerializedName("description")
13+
val description: String,
14+
@SerializedName("url")
15+
val url: String,
16+
@SerializedName("screen")
17+
val screen: Screen?,
18+
@SerializedName("position")
19+
val position: Int
20+
) {
21+
enum class ColorType(val type: String) {
22+
@SerializedName("blue")
23+
BLUE("blue"),
24+
25+
@SerializedName("violet")
26+
VIOLET("violet"),
27+
28+
@SerializedName("green")
29+
GREEN("green")
30+
}
31+
32+
enum class Screen(val screen: String) {
33+
@SerializedName("catalog")
34+
CATALOG("catalog"),
35+
36+
@SerializedName("home")
37+
HOME("home")
38+
}
39+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.stepik.android.presentation.banner
2+
3+
import org.stepik.android.domain.banner.model.Banner
4+
5+
interface BannerFeature {
6+
sealed class State {
7+
object Idle : State()
8+
object Loading : State()
9+
object Empty : State()
10+
data class Content(val banners: List<Banner>) : State()
11+
}
12+
13+
sealed class Message {
14+
data class InitMessage(
15+
val screen: Banner.Screen,
16+
val forceUpdate: Boolean = false
17+
) : Message()
18+
object BannersError : Message()
19+
data class BannersResult(val banners: List<Banner>) : Message()
20+
}
21+
22+
sealed class Action {
23+
data class LoadBanners(val screen: Banner.Screen) : Action()
24+
}
25+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.stepik.android.presentation.banner.dispatcher
2+
3+
import io.reactivex.Scheduler
4+
import io.reactivex.rxkotlin.plusAssign
5+
import io.reactivex.rxkotlin.subscribeBy
6+
import org.stepic.droid.di.qualifiers.BackgroundScheduler
7+
import org.stepic.droid.di.qualifiers.MainScheduler
8+
import org.stepik.android.domain.banner.interactor.BannerInteractor
9+
import org.stepik.android.presentation.banner.BannerFeature
10+
import ru.nobird.android.presentation.redux.dispatcher.RxActionDispatcher
11+
import javax.inject.Inject
12+
13+
class BannerActionDispatcher
14+
@Inject
15+
constructor(
16+
private val bannerInteractor: BannerInteractor,
17+
@BackgroundScheduler
18+
private val backgroundScheduler: Scheduler,
19+
@MainScheduler
20+
private val mainScheduler: Scheduler
21+
) : RxActionDispatcher<BannerFeature.Action, BannerFeature.Message>() {
22+
override fun handleAction(action: BannerFeature.Action) {
23+
when (action) {
24+
is BannerFeature.Action.LoadBanners -> {
25+
compositeDisposable += bannerInteractor
26+
.getBanners(action.screen)
27+
.observeOn(mainScheduler)
28+
.subscribeOn(backgroundScheduler)
29+
.subscribeBy(
30+
onSuccess = { onNewMessage(BannerFeature.Message.BannersResult(it)) },
31+
onError = { onNewMessage(BannerFeature.Message.BannersError) }
32+
)
33+
}
34+
}
35+
}
36+
}

0 commit comments

Comments
 (0)