Skip to content

Commit 2526c96

Browse files
축제 기능 추가 (+ 리모트컨피그 추가) (#138)
* 말풍선 디자인 및 위치 수정 * 가격정책 반영 * lint * 스크린 expanded 상태로 뜨게하기 * X자 추가 및 필터 부분 Full아닐 때 없애기 * lint * 그림자 추가 및 1차 디자인 수정 * CustomDialog로 변경 * 디자인 수정 및 Dialog 높이 설정 * Handle에 Drag기능 추가 * dialog_filter.xml의 id들 snake case로 수정 * gradle 추가 * SplashActivity에 FeatureChecker 추가 * featurechecker 값에 따라 필터 기능이 보이도록 함 * lint * vm 구현 * QA 반영 * 말풍선 위치 함수 생성 * lint * 스위치 구현 * 동적 아이콘 변화 * 아이콘 git에 추가 * QA 사소한 반영 * featurechecker fetch 위치 변경 * 토글 애니메이션 수정 --------- Co-authored-by: kevin990222 <[email protected]> Co-authored-by: kevin990222 <[email protected]>
1 parent cee21f6 commit 2526c96

32 files changed

+504
-137
lines changed

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ dependencies {
168168
implementation(platform("com.google.firebase:firebase-bom:32.0.0"))
169169
implementation("com.google.firebase:firebase-crashlytics-ktx")
170170
implementation("com.google.firebase:firebase-analytics-ktx")
171+
implementation("com.google.firebase:firebase-config-ktx")
171172

172173
// Glide
173174
implementation("com.github.bumptech.glide:glide:4.15.1")

app/src/main/AndroidManifest.xml

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@
5656
android:exported="true"
5757
android:theme="@style/Theme.Siksha.Splash">
5858
<intent-filter>
59-
<action android:name="android.intent.action.MAIN" />
60-
6159
<category android:name="android.intent.category.LAUNCHER" />
6260
</intent-filter>
6361

@@ -69,8 +67,30 @@
6967
<data android:scheme="@string/kakao_scheme"
7068
android:host="@string/kakaolink_host" />
7169
</intent-filter>
72-
7370
</activity>
71+
72+
<activity-alias
73+
android:name="com.wafflestudio.siksha2.ui.SikshaNormal"
74+
android:icon="@mipmap/ic_launcher"
75+
android:exported="true"
76+
android:targetActivity=".ui.SplashActivity">
77+
<intent-filter>
78+
<action android:name="android.intent.action.MAIN"/>
79+
<category android:name="android.intent.category.LAUNCHER"/>
80+
</intent-filter>
81+
</activity-alias>
82+
83+
<activity-alias
84+
android:name="com.wafflestudio.siksha2.ui.SikshaFestival"
85+
android:icon="@mipmap/ic_festival"
86+
android:exported="true"
87+
android:targetActivity=".ui.SplashActivity">
88+
<intent-filter>
89+
<action android:name="android.intent.action.MAIN"/>
90+
<category android:name="android.intent.category.LAUNCHER"/>
91+
</intent-filter>
92+
</activity-alias>
93+
7494
<activity
7595
android:name="com.kakao.sdk.auth.AuthCodeHandlerActivity"
7696
android:exported="true"
63.9 KB
Loading
Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,57 @@
11
package com.wafflestudio.siksha2
22

3-
object FeatureChecker {
3+
import com.google.firebase.ktx.Firebase
4+
import com.google.firebase.remoteconfig.ConfigUpdate
5+
import com.google.firebase.remoteconfig.ConfigUpdateListener
6+
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
7+
import com.google.firebase.remoteconfig.FirebaseRemoteConfigException
8+
import com.google.firebase.remoteconfig.ktx.remoteConfig
9+
import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
10+
import timber.log.Timber
11+
import javax.inject.Inject
12+
import javax.inject.Singleton
413

5-
enum class Feature {
6-
COMMUNITY_TAB
14+
@Singleton
15+
class FeatureChecker @Inject constructor() {
16+
private val remoteConfig: FirebaseRemoteConfig = Firebase.remoteConfig
17+
18+
init {
19+
val configSettings = remoteConfigSettings {
20+
minimumFetchIntervalInSeconds = if (BuildConfig.DEBUG) 1 else 43200
21+
}
22+
remoteConfig.setDefaultsAsync(R.xml.featurechecker_default)
23+
remoteConfig.setConfigSettingsAsync(configSettings)
24+
remoteConfig.addOnConfigUpdateListener(object : ConfigUpdateListener {
25+
override fun onUpdate(configUpdate: ConfigUpdate) {
26+
Timber.d("FeatureChecker Update Success")
27+
// Log.d(TAG, "Updated keys: " + configUpdate.updatedKeys);
28+
29+
// if (configUpdate.updatedKeys.contains("welcome_message")) {
30+
// remoteConfig.activate().addOnCompleteListener {
31+
// // displayWelcomeMessage()
32+
// }
33+
// }
34+
}
35+
36+
override fun onError(error: FirebaseRemoteConfigException) {
37+
Timber.d("FeatureChecker Error")
38+
// Log.w(TAG, "Config update error with code: " + error.code, error)
39+
}
40+
})
741
}
842

9-
private val featureFlags = mapOf(
10-
Feature.COMMUNITY_TAB to true
11-
)
43+
fun fetchFeaturesConfig() {
44+
remoteConfig.fetchAndActivate()
45+
.addOnCompleteListener { task ->
46+
if (task.isSuccessful) {
47+
Timber.d("Fetch Success: ${remoteConfig.getBoolean("festivalFeatureEnabled")}")
48+
} else {
49+
Timber.d("Fetch Failed: ${remoteConfig.getBoolean("festivalFeatureEnabled")}")
50+
}
51+
}
52+
}
1253

13-
fun isFeatureEnabled(feature: Feature): Boolean {
14-
return featureFlags[feature] ?: false
54+
fun isFeatureEnabled(featureFlag: String): Boolean {
55+
return remoteConfig.getBoolean(featureFlag)
1556
}
1657
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package com.wafflestudio.siksha2.components.festival
2+
3+
import androidx.compose.animation.Crossfade
4+
import androidx.compose.animation.core.LinearOutSlowInEasing
5+
import androidx.compose.animation.core.animateFloatAsState
6+
import androidx.compose.animation.core.tween
7+
import androidx.compose.foundation.Image
8+
import androidx.compose.foundation.clickable
9+
import androidx.compose.foundation.interaction.MutableInteractionSource
10+
import androidx.compose.foundation.layout.Box
11+
import androidx.compose.foundation.layout.fillMaxSize
12+
import androidx.compose.foundation.layout.height
13+
import androidx.compose.foundation.layout.offset
14+
import androidx.compose.foundation.layout.padding
15+
import androidx.compose.foundation.layout.width
16+
import androidx.compose.material.Text
17+
import androidx.compose.runtime.Composable
18+
import androidx.compose.runtime.State
19+
import androidx.compose.runtime.getValue
20+
import androidx.compose.runtime.remember
21+
import androidx.compose.ui.Alignment
22+
import androidx.compose.ui.Modifier
23+
import androidx.compose.ui.platform.LocalDensity
24+
import androidx.compose.ui.res.painterResource
25+
import androidx.compose.ui.text.font.FontWeight
26+
import androidx.compose.ui.unit.IntOffset
27+
import androidx.compose.ui.unit.dp
28+
import androidx.compose.ui.unit.sp
29+
import com.wafflestudio.siksha2.R
30+
import com.wafflestudio.siksha2.ui.SikshaColors
31+
import kotlin.math.roundToInt
32+
33+
@Composable
34+
fun FestivalToggle(
35+
checked: State<Boolean>,
36+
onClick: () -> Unit,
37+
modifier: Modifier = Modifier
38+
) {
39+
val interactionSource = remember { MutableInteractionSource() }
40+
Box(
41+
modifier = modifier
42+
.padding(end = 18.dp)
43+
.width(50.dp)
44+
.height(24.dp)
45+
.clickable(
46+
interactionSource = interactionSource,
47+
indication = null
48+
) { onClick() }
49+
) {
50+
val density = LocalDensity.current
51+
// 배경색
52+
Crossfade(
53+
targetState = checked.value,
54+
animationSpec = tween(
55+
durationMillis = 300,
56+
easing = LinearOutSlowInEasing
57+
),
58+
label = "축제 메뉴 토글 스위치"
59+
) { isChecked ->
60+
if (isChecked) {
61+
Image(
62+
modifier = Modifier.fillMaxSize(),
63+
painter = painterResource(R.drawable.festival_toggle_background_active),
64+
contentDescription = "축제 메뉴 보기"
65+
)
66+
} else {
67+
Image(
68+
modifier = Modifier.fillMaxSize(),
69+
painter = painterResource(R.drawable.festival_toggle_background_inactive),
70+
contentDescription = "일반 메뉴 보기"
71+
)
72+
}
73+
}
74+
75+
// text
76+
val textOffsetActive = with(density) { 6.dp.toPx() }
77+
val textOffsetInactive = with(density) { 26.dp.toPx() }
78+
val textState by animateFloatAsState(
79+
targetValue = if (checked.value) textOffsetActive else textOffsetInactive,
80+
animationSpec = tween(
81+
durationMillis = 300,
82+
easing = LinearOutSlowInEasing
83+
),
84+
label = ""
85+
)
86+
Text(
87+
modifier = Modifier.align(Alignment.CenterStart)
88+
.offset { IntOffset(textState.roundToInt(), 0) },
89+
text = "축제",
90+
color = SikshaColors.White900,
91+
fontSize = 11.sp,
92+
fontWeight = FontWeight.Bold
93+
)
94+
95+
// knob
96+
val knobOffsetActive = with(density) { 28.dp.toPx() }
97+
val knobOffsetInactive = with(density) { 2.dp.toPx() }
98+
val knobState by animateFloatAsState(
99+
targetValue = if (checked.value) knobOffsetActive else knobOffsetInactive,
100+
animationSpec = tween(
101+
durationMillis = 300,
102+
easing = LinearOutSlowInEasing
103+
),
104+
label = ""
105+
)
106+
Image(
107+
modifier = Modifier.width(20.dp).height(20.dp).align(Alignment.CenterStart)
108+
.offset { IntOffset(knobState.roundToInt(), 0) },
109+
painter = painterResource(R.drawable.festival_toggle_knob),
110+
contentDescription = null
111+
)
112+
}
113+
}

app/src/main/java/com/wafflestudio/siksha2/ui/SplashActivity.kt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package com.wafflestudio.siksha2.ui
22

3+
import android.content.ComponentName
34
import android.content.Context
45
import android.content.Intent
6+
import android.content.pm.PackageManager
57
import android.net.ConnectivityManager
68
import android.net.NetworkCapabilities
9+
import android.os.Build
710
import android.os.Bundle
811
import androidx.activity.result.ActivityResultLauncher
912
import androidx.activity.result.contract.ActivityResultContract
@@ -18,6 +21,7 @@ import com.google.android.gms.common.api.Scope
1821
import com.google.android.gms.tasks.Task
1922
import com.kakao.sdk.auth.model.OAuthToken
2023
import com.kakao.sdk.user.UserApiClient
24+
import com.wafflestudio.siksha2.FeatureChecker
2125
import com.wafflestudio.siksha2.R
2226
import com.wafflestudio.siksha2.databinding.ActivitySplashBinding
2327
import com.wafflestudio.siksha2.network.OAuthProvider
@@ -44,6 +48,9 @@ class SplashActivity : AppCompatActivity() {
4448

4549
private lateinit var kakaoSignInLauncher: () -> Unit
4650

51+
@Inject
52+
lateinit var featureChecker: FeatureChecker
53+
4754
@InternalCoroutinesApi
4855
override fun onCreate(savedInstanceState: Bundle?) {
4956
super.onCreate(savedInstanceState)
@@ -72,6 +79,9 @@ class SplashActivity : AppCompatActivity() {
7279
finish()
7380
}
7481

82+
featureChecker.fetchFeaturesConfig()
83+
changeAppIcon()
84+
7585
setUpGoogleLogin()
7686
setUpKakaoLogin()
7787

@@ -165,4 +175,36 @@ class SplashActivity : AppCompatActivity() {
165175
private suspend fun checkLoginStatus(): Boolean {
166176
return userStatusManager.refreshUserToken()
167177
}
178+
179+
private fun changeAppIcon() {
180+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
181+
val mainComponent = ComponentName(this, "com.wafflestudio.siksha2.ui.SplashActivity")
182+
val normalIconComponent = ComponentName(this, "com.wafflestudio.siksha2.ui.SikshaNormal")
183+
val festivalIconComponent = ComponentName(this, "com.wafflestudio.siksha2.ui.SikshaFestival")
184+
packageManager.setComponentEnabledSetting(
185+
normalIconComponent,
186+
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
187+
PackageManager.DONT_KILL_APP
188+
)
189+
packageManager.setComponentEnabledSetting(
190+
festivalIconComponent,
191+
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
192+
PackageManager.DONT_KILL_APP
193+
)
194+
195+
if (featureChecker.isFeatureEnabled("festivalFeatureEnabled")) {
196+
packageManager.setComponentEnabledSetting(
197+
festivalIconComponent,
198+
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
199+
PackageManager.DONT_KILL_APP
200+
)
201+
} else {
202+
packageManager.setComponentEnabledSetting(
203+
normalIconComponent,
204+
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
205+
PackageManager.DONT_KILL_APP
206+
)
207+
}
208+
}
209+
}
168210
}

0 commit comments

Comments
 (0)