Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

친구 탭 React Native 베이스 브랜치 #182

Merged
merged 45 commits into from
Sep 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
81fad59
RN을 위한 gradle 설정 및 버전 업
Jun 26, 2023
5eb540c
라이브러리 버전 업에 따른 임시 조치
Jun 26, 2023
18bc19f
임시 RNActivity 추가
Jun 26, 2023
eb79898
temp
Jun 27, 2023
36ba56e
불필요한 것 제거
Jul 2, 2023
6761a67
Navigation 라이브러리 버전 업 대응
Jul 2, 2023
1682812
정리
Jul 2, 2023
e3b82eb
다운 로직 수정
Jul 8, 2023
2463ffc
String 슥 보내서 슥 받기
Jul 8, 2023
fdddfb6
진짜 유저 토큰 보내기
Jul 8, 2023
0aa560c
뭔가 바꾸기
Jul 9, 2023
c865d8f
캐시 지우는 버튼 만들기
Jul 9, 2023
1831647
되돌리기
Jul 9, 2023
fb4c5b8
fix rn props (#188)
woohm402 Jul 9, 2023
05356ee
로컬/컨피스 선택
Aug 6, 2023
1eef2b2
RN 임시 아이콘
Aug 6, 2023
2134497
api key, token
Aug 6, 2023
12c161a
쓸데없는거 지우기
Aug 11, 2023
5508af5
탭에 RN 붙이기
Aug 11, 2023
4577c99
불필요한 변화 제거
Aug 11, 2023
9e771d3
불필요한 변화 2
Aug 11, 2023
3f91317
코드 변경
Aug 14, 2023
0316874
aar release 파일 우선 직접 추가
Aug 15, 2023
ad80d15
react-native-svg 추가
Aug 15, 2023
b23c266
로컬 번들 사용하기 및 약간의 오류 수정(불완전)
Aug 15, 2023
2747e81
로컬번들 사용환경 만들기
Aug 15, 2023
166ba19
임시 (Activity로 띄우기)
JuTaK97 Aug 30, 2023
c776bae
라이브러리 올바르게 include하는 버전 (공식문서)
Aug 15, 2023
0950afd
Revert "라이브러리 올바르게 include하는 버전 (공식문서)"
JuTaK97 Aug 30, 2023
43a8571
바텀시트 오류 수정
JuTaK97 Sep 4, 2023
ecf358c
Revert "임시 (Activity로 띄우기)"
JuTaK97 Sep 4, 2023
0a3e07b
flipper 추가
JuTaK97 Sep 4, 2023
7a9c9a8
theme, autoScaling 쿠키 추가
JuTaK97 Sep 5, 2023
f2b09cb
lint
JuTaK97 Sep 5, 2023
0b13c1f
lint
JuTaK97 Sep 6, 2023
accb0da
Squashed commit of the following:
JuTaK97 Sep 8, 2023
e93b880
불필요한 변경 제거
JuTaK97 Sep 8, 2023
5c3dbce
fetch 로직 정리
JuTaK97 Sep 9, 2023
1f29d42
lint
JuTaK97 Sep 9, 2023
5696cd0
로그아웃 후 로그인 시 다시 RNView 불러오기
JuTaK97 Sep 9, 2023
e9554a8
중복 제거
JuTaK97 Sep 9, 2023
cf74ade
정리
JuTaK97 Sep 9, 2023
ce24530
주석 추가
JuTaK97 Sep 9, 2023
c31c4ea
다크모드 전환 시 ReactView 새로 그리기
JuTaK97 Sep 9, 2023
a377a6a
혹시모르니 !! 제거
JuTaK97 Sep 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 5 additions & 12 deletions .idea/deploymentTargetDropDown.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 7 additions & 11 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,6 @@ android {
namespace = "com.wafflestudio.snutt2"
compileSdk = 34

repositories {
mavenCentral()
}

defaultConfig {
applicationId = "com.wafflestudio.snutt2"
minSdk = 24
Expand Down Expand Up @@ -188,12 +184,12 @@ dependencies {
// GSON
implementation("com.google.code.gson:gson:2.10.1")

// Glance
implementation("androidx.glance:glance-appwidget:1.0.0-rc01")
implementation("androidx.glance:glance-material:1.0.0-rc01")
implementation("androidx.glance:glance-material3:1.0.0-rc01")
}
// RN
implementation("com.facebook.react:react-android:0.72.3")
implementation("com.facebook.react:hermes-android:0.72.3")
implementation(fileTree(mapOf("dir" to "$rootDir/libs", "include" to listOf("*.aar"))))

repositories {
mavenCentral()
// flipper
implementation("com.facebook.flipper:flipper:0.213.0")
implementation("com.facebook.soloader:soloader:0.10.5")
}
10 changes: 6 additions & 4 deletions app/src/main/java/com/wafflestudio/snutt2/RemoteConfig.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.wafflestudio.snutt2

import com.wafflestudio.snutt2.data.user.UserRepository
import com.wafflestudio.snutt2.lib.network.ApiOnError
import com.wafflestudio.snutt2.lib.network.SNUTTRestApi
import com.wafflestudio.snutt2.lib.network.dto.core.RemoteConfigDto
import kotlinx.coroutines.CoroutineScope
Expand All @@ -11,6 +10,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
Expand All @@ -22,16 +22,14 @@ import javax.inject.Singleton
class RemoteConfig @Inject constructor(
api: SNUTTRestApi,
userRepository: UserRepository,
apiOnError: ApiOnError,
) {
val fetchDone = MutableSharedFlow<Unit>()
private val fetchDone = MutableSharedFlow<Unit>(replay = 1)
private val config = callbackFlow {
userRepository.accessToken.filter { it.isNotEmpty() }.collect {
withContext(Dispatchers.IO) {
try {
send(api._getRemoteConfig())
} catch (e: Exception) {
apiOnError(e)
this@callbackFlow.close()
}
}
Expand All @@ -58,4 +56,8 @@ class RemoteConfig @Inject constructor(

val settingPageNewBadgeTitles: List<String>
get() = config.value.settingsBadgeConfig.new

suspend fun waitForFetchConfig() {
fetchDone.first()
}
}
48 changes: 47 additions & 1 deletion app/src/main/java/com/wafflestudio/snutt2/SNUTTApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,23 @@ package com.wafflestudio.snutt2
import android.app.Application
import android.content.res.Configuration
import androidx.compose.animation.ExperimentalAnimationApi
import com.facebook.flipper.android.AndroidFlipperClient
import com.facebook.flipper.android.utils.FlipperUtils
import com.facebook.flipper.plugins.inspector.DescriptorMapping
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin
import com.facebook.hermes.reactexecutor.HermesExecutorFactory
import com.facebook.react.ReactApplication
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.JavaScriptExecutorFactory
import com.facebook.react.shell.MainReactPackage
import com.facebook.soloader.SoLoader
import com.horcrux.svg.SvgPackage
import com.reactnativecommunity.picker.RNCPickerPackage
import com.swmansion.gesturehandler.RNGestureHandlerPackage
import com.swmansion.reanimated.ReanimatedPackage
import com.swmansion.rnscreens.RNScreensPackage
import com.th3rdwave.safeareacontext.SafeAreaContextPackage
import com.wafflestudio.snutt2.provider.TimetableWidgetProvider
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber
Expand All @@ -11,11 +28,18 @@ import timber.log.Timber
* Created by makesource on 2016. 1. 17..
*/
@HiltAndroidApp
class SNUTTApplication : Application() {
class SNUTTApplication : Application(), ReactApplication {

override fun onCreate() {
super.onCreate()
Timber.plant(Timber.DebugTree())
SoLoader.init(this, false)

if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {
val client = AndroidFlipperClient.getInstance(this)
client.addPlugin(InspectorFlipperPlugin(this, DescriptorMapping.withDefaults()))
client.start()
}
Comment on lines +38 to +42
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flipper 설정

}

@OptIn(ExperimentalAnimationApi::class)
Expand All @@ -24,6 +48,28 @@ class SNUTTApplication : Application() {
TimetableWidgetProvider.refreshWidget(applicationContext)
}

override fun getReactNativeHost(): ReactNativeHost {
return object : ReactNativeHost(this) {
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG

override fun getPackages(): List<ReactPackage> = listOf(
MainReactPackage(),
RNScreensPackage(),
RNGestureHandlerPackage(),
RNCPickerPackage(),
SafeAreaContextPackage(),
ReanimatedPackage(),
SvgPackage(),
)

override fun getJSMainModuleName(): String = "friends"

override fun getJavaScriptExecutorFactory(): JavaScriptExecutorFactory {
return HermesExecutorFactory()
}
}
}

Comment on lines +51 to +72
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReactApplication 상속 구현부

companion object {
private const val TAG = "SNUTT_APPLICATION"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package com.wafflestudio.snutt2.react_native

import android.app.Activity
import android.app.Application
import android.content.Context
import android.os.Bundle
import com.facebook.hermes.reactexecutor.HermesExecutorFactory
import com.facebook.react.ReactInstanceManager
import com.facebook.react.ReactRootView
import com.facebook.react.common.LifecycleState
import com.facebook.react.shell.MainReactPackage
import com.horcrux.svg.SvgPackage
import com.reactnativecommunity.picker.RNCPickerPackage
import com.swmansion.gesturehandler.RNGestureHandlerPackage
import com.swmansion.reanimated.ReanimatedPackage
import com.th3rdwave.safeareacontext.SafeAreaContextPackage
import com.wafflestudio.snutt2.R
import com.wafflestudio.snutt2.RemoteConfig
import com.wafflestudio.snutt2.ui.ThemeMode
import com.wafflestudio.snutt2.ui.isSystemDarkMode
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
import java.net.HttpURLConnection
import java.net.URL

class ReactNativeBundleManager(
private val context: Context,
private val remoteConfig: RemoteConfig,
private val token: StateFlow<String>,
private val themeMode: StateFlow<ThemeMode>,
) {
private val rnBundleFileSrc: String
get() = if (USE_LOCAL_BUNDLE) LOCAL_BUNDLE_URL else remoteConfig.friendBundleSrc
private var myReactInstanceManager: ReactInstanceManager? = null
var reactRootView: ReactRootView? = null

init {
CoroutineScope(Dispatchers.IO).launch {
remoteConfig.waitForFetchConfig()
token.filter { it.isNotEmpty() }.collectLatest {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 하면 로그아웃 했다가 로그인 했을 때 ReactView랑 ReactInstanceManager를 새로 설정하게 된다.

val jsBundleFile = getExistingFriendsBundleFileOrNull() ?: return@collectLatest
withContext(Dispatchers.Main) {
myReactInstanceManager = ReactInstanceManager.builder()
.setApplication(context.applicationContext as Application)
.setCurrentActivity(context as Activity)
.setJavaScriptExecutorFactory(HermesExecutorFactory())
.setJSBundleFile(jsBundleFile.absolutePath)
.addPackages(
listOf(MainReactPackage(), RNGestureHandlerPackage(), ReanimatedPackage(), SafeAreaContextPackage(), RNCPickerPackage(), SvgPackage()),
)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build()
themeMode.collectLatest {
val isDarkMode = when (it) {
ThemeMode.AUTO -> isSystemDarkMode(context)
else -> (it == ThemeMode.DARK)
}
reactRootView = ReactRootView(context).apply {
startReactApplication(
myReactInstanceManager ?: return@apply,
FRIENDS_MODULE_NAME,
Bundle().apply {
putString("x-access-token", token.value)
putString("x-access-apikey", context.getString(R.string.api_key))
putString("theme", if (isDarkMode) "dark" else "light")
putBoolean("allowFontScaling", true)
},
)
}
}
}
}
}
}

// 번들 파일을 저장할 폴더 (없으면 만들기, 실패하면 null)
private fun bundlesBaseFolder(): File? {
val baseDir = File(context.applicationContext.dataDir.absolutePath, BUNDLE_BASE_FOLDER)
return if (baseDir.isDirectory && baseDir.exists()) {
baseDir
} else if (baseDir.mkdir()) {
baseDir
} else {
null
}
}

private fun getExistingFriendsBundleFileOrNull(): File? {
val baseDir = bundlesBaseFolder() ?: return null
val friendsBaseDir = File(baseDir, FRIENDS_MODULE_NAME)
if (friendsBaseDir.exists().not() && friendsBaseDir.mkdir().not()) return null

// Config에서 가져온 bundle name대로 fileName을 만든다.
val targetFileName =
if (USE_LOCAL_BUNDLE) {
LOCAL_BUNDLE_FILE_NAME
} else {
Regex(BUNDLE_FILE_NAME_REGEX).find(rnBundleFileSrc)?.groupValues?.get(1)?.plus(BUNDLE_FILE_SUFFIX) ?: return null
}
val targetFile = File(friendsBaseDir, targetFileName)

// 최신 friends 번들 외에 전부 삭제
friendsBaseDir.listFiles()
?.filter { it.name != targetFileName }
?.forEach { it.delete() }

// 파일이 없거나 파일에 문제가 있으면 새로 다운로드한다.
if (targetFile.exists().not() || targetFile.canRead().not() || USE_LOCAL_BUNDLE) { // TODO: 올바르지 않은 bundle 파일인지 더 정확히 판단하기
try {
val urlConnection = URL(rnBundleFileSrc).openConnection() as HttpURLConnection
urlConnection.connect()
val inputStream = urlConnection.inputStream
val outputStream = FileOutputStream(targetFile)
val buffer = ByteArray(1024000)
var bytesRead: Int

while (inputStream.read(buffer).also { bytesRead = it } != -1) {
outputStream.write(buffer, 0, bytesRead)
}
outputStream.close()
inputStream.close()
urlConnection.disconnect()
} catch (e: Exception) {
return null
}
}
return targetFile
}

// 번들 파일들은 $rootDir/data/ReactNativeBundles 폴더에 각 모듈별로 저장된다.
// friends 모듈의 번들 파일은 $rootDir/data/ReactNativeBundles/friends 폴더에 저장된다.
// 번들 파일의 이름은 src가 https://~~~.com/{version}/android.jsbundle 일 때 version-android.jsbundle 이다.
companion object {
const val BUNDLE_BASE_FOLDER = "/ReactNativeBundles"
const val BUNDLE_FILE_NAME_REGEX = "com/(.*?)/android.jsbundle"
const val BUNDLE_FILE_SUFFIX = "-android.jsbundle"

const val FRIENDS_MODULE_NAME = "friends"

const val USE_LOCAL_BUNDLE = false
const val LOCAL_BUNDLE_FILE_NAME = "android.jsbundle"
const val LOCAL_BUNDLE_URL = "http://localhost:8081/index.bundle?platform=android"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {
this,
0,
intent,
PendingIntent.FLAG_ONE_SHOT,
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

버전 올리고 컴파일 막혀서 수정
MyFirebaseMessegingServie 안쓰긴 하는데, 지우긴 좀 아까워서 냅둠

)
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
val notificationBuilder = NotificationCompat.Builder(this, SNUTT_FIREBASE_CHANNEL)
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/java/com/wafflestudio/snutt2/ui/Theme.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.wafflestudio.snutt2.ui

import android.app.Activity
import android.content.Context
import android.content.res.Configuration
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.MaterialTheme
import androidx.compose.material.darkColors
Expand Down Expand Up @@ -53,6 +55,14 @@ fun isDarkMode(): Boolean {
}
}

fun isSystemDarkMode(context: Context): Boolean {
return when (context.resources?.configuration?.uiMode?.and(Configuration.UI_MODE_NIGHT_MASK)) {
Configuration.UI_MODE_NIGHT_YES -> true
Configuration.UI_MODE_NIGHT_NO -> false
else -> false
}
}

@Composable
fun SNUTTTheme(
content: @Composable () -> Unit,
Expand Down
Loading
Loading