-
Notifications
You must be signed in to change notification settings - Fork 0
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
Changes from all commits
81fad59
5eb540c
18bc19f
eb79898
36ba56e
6761a67
1682812
e3b82eb
2463ffc
fdddfb6
0aa560c
c865d8f
1831647
fb4c5b8
05356ee
1eef2b2
2134497
12c161a
5508af5
4577c99
9e771d3
3f91317
0316874
ad80d15
b23c266
2747e81
166ba19
c776bae
0950afd
43a8571
ecf358c
0a3e07b
7a9c9a8
f2b09cb
0b13c1f
accb0da
e93b880
5c3dbce
1f29d42
5696cd0
e9554a8
cf74ade
ce24530
c31c4ea
a377a6a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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() | ||
} | ||
} | ||
|
||
@OptIn(ExperimentalAnimationApi::class) | ||
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ReactApplication 상속 구현부 |
||
companion object { | ||
private const val TAG = "SNUTT_APPLICATION" | ||
} | ||
|
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
---|---|---|
|
@@ -29,7 +29,7 @@ class MyFirebaseMessagingService : FirebaseMessagingService() { | |
this, | ||
0, | ||
intent, | ||
PendingIntent.FLAG_ONE_SHOT, | ||
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 버전 올리고 컴파일 막혀서 수정 |
||
) | ||
val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) | ||
val notificationBuilder = NotificationCompat.Builder(this, SNUTT_FIREBASE_CHANNEL) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Flipper 설정