
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
package kr.co.tadadang.user
import android.animation.Animator
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Color
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import android.view.Gravity
import android.view.View
import android.view.WindowManager
import android.webkit.CookieManager
import android.webkit.JavascriptInterface
import android.webkit.ValueCallback
import android.webkit.WebChromeClient
import android.webkit.WebResourceError
import android.webkit.WebResourceRequest
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import kr.co.tadadang.user.native_bridge.ExitAppVCDelegate
import kr.co.tadadang.user.native_bridge.HideIntroVCDelegate
import kr.co.tadadang.user.sms.SmsRetrieverInterface
import kr.co.tadadang.user.util.AnimationUtil
class MainActivity : ComponentActivity(), HideIntroVCDelegate, ExitAppVCDelegate {
private lateinit var mContext: Context
private lateinit var mActivity: MainActivity
private lateinit var mWebView: WebView
private val mCookieManager by lazy { CookieManager.getInstance() }
private var mToast: Toast? = null
var mOriUserAgent: String = ""
private var uploadMessage: ValueCallback<Array<Uri>>? = null
val resultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
var results: Array<Uri>? =
WebChromeClient.FileChooserParams.parseResult(it.resultCode, it.data)
if (results != null) {
uploadMessage?.onReceiveValue(results)
} else {
val uris = ArrayList<Uri>()
if (intent != null) {
val clipData = intent.clipData!!
if (clipData.itemCount > 0) {
var i = 0
val size = clipData.itemCount
while (i < size) {
val uri = clipData.getItemAt(i).uri
uris.add(uri)
i++
}
results = uris.toTypedArray()
}
}
if (results != null) {
uploadMessage?.onReceiveValue(results)
} else {
uploadMessage?.onReceiveValue(null)
}
}
}
}
@SuppressLint("SetJavaScriptEnabled")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mContext = this
mActivity = this
mWebView = findViewById(R.id.webview)
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
setCookieAllow(mWebView)
val webSettings = mWebView.settings
webSettings.javaScriptEnabled = true
webSettings.allowContentAccess = true
webSettings.allowFileAccess = true
webSettings.allowFileAccessFromFileURLs = true
webSettings.allowUniversalAccessFromFileURLs = true
webSettings.builtInZoomControls = false
webSettings.setRenderPriority(WebSettings.RenderPriority.HIGH)
webSettings.domStorageEnabled = true
webSettings.textZoom = 100
webSettings.cacheMode = WebSettings.LOAD_NO_CACHE
webSettings.setSupportMultipleWindows(true)
webSettings.javaScriptCanOpenWindowsAutomatically = true
var versionCode = 0
var versionName = ""
try {
val pInfo = this.packageManager.getPackageInfo(packageName, 0)
versionCode = pInfo.versionCode
versionName = pInfo.versionName
} catch (e: PackageManager.NameNotFoundException) {
}
mOriUserAgent = webSettings.userAgentString
var newUserAgent = mOriUserAgent
newUserAgent += ",type:admin"
var isDarkMode = false
try {
val darkModeCheck =
mContext.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
if (darkModeCheck == Configuration.UI_MODE_NIGHT_YES) {
isDarkMode = true
}
} catch (_: Exception) {
}
newUserAgent += ",theme:${if (isDarkMode) "dark" else "light"}"
newUserAgent += ",os:aos"
newUserAgent += ",vercode:$versionCode"
newUserAgent += ",vername:$versionName"
newUserAgent += ",osver:" + Build.VERSION.SDK_INT
newUserAgent += ",:end:"
webSettings.userAgentString = newUserAgent
mWebView.webChromeClient = object : WebChromeClient() {
override fun onShowFileChooser(
webView: WebView?,
filePathCallback: ValueCallback<Array<Uri>>?,
fileChooserParams: FileChooserParams?
): Boolean {
try {
if (fileChooserParams != null) {
uploadMessage?.onReceiveValue(null)
uploadMessage = null
uploadMessage = filePathCallback
val isMultiSelect =
fileChooserParams.mode == FileChooserParams.MODE_OPEN_MULTIPLE
if (fileChooserParams.acceptTypes.indexOf(".png") != -1 || fileChooserParams.acceptTypes.indexOf(
"image/*"
) != -1
) {
// 이미지 선택기
val galleryIntent = Intent(Intent.ACTION_PICK)
galleryIntent.type = "image/*"
if (isMultiSelect) {
galleryIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
}
val intent = Intent.createChooser(galleryIntent, "")
resultLauncher.launch(intent)
} else if (fileChooserParams.acceptTypes.indexOf(".avi") != -1 || fileChooserParams.acceptTypes.indexOf(
"video/*"
) != -1
) {
// 비디오 선택기
val videoIntent = Intent(
Intent.ACTION_PICK,
MediaStore.Video.Media.EXTERNAL_CONTENT_URI
)
videoIntent.type = "video/*"
if (isMultiSelect) {
videoIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
}
resultLauncher.launch(videoIntent)
} else {
// 파일 선택기
val galleryIntent = Intent(Intent.ACTION_PICK)
galleryIntent.type = "video/*, image/*"
if (isMultiSelect) {
galleryIntent.putExtra(
Intent.EXTRA_ALLOW_MULTIPLE,
true
)
}
resultLauncher.launch(galleryIntent)
}
return true
}
} catch (e: Exception) {
uploadMessage = null
e.printStackTrace()
}
return false
}
}
mWebView.webViewClient = object : WebViewClient() {
// 페이지 오류 처리
override fun onReceivedError(
view: WebView?,
request: WebResourceRequest?,
error: WebResourceError?
) {
showErrorPage(request?.url)
super.onReceivedError(view, request, error)
}
override fun onReceivedError(
view: WebView?,
errorCode: Int,
description: String?,
failingUrl: String?
) {
showErrorPage(Uri.parse(failingUrl))
super.onReceivedError(view, errorCode, description, failingUrl)
}
override fun onPageFinished(view: WebView?, url: String?) {
CookieManager.getInstance().flush()
pageFinishedAction(Uri.parse(url))
super.onPageFinished(view, url)
}
// 브릿지 처리
override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?
): Boolean {
if (checkUrl(view, request?.url)) {
return true
}
return super.shouldOverrideUrlLoading(view, request)
}
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
if (checkUrl(view, Uri.parse(url))) {
return true
}
return super.shouldOverrideUrlLoading(view, url)
}
}
mWebView.setDownloadListener { url, userAgent, contentDisposition, mimeType, contentLength ->
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(url)
startActivity(intent)
}
mCookieManager.acceptCookie()
mCookieManager.acceptThirdPartyCookies(mWebView)
mWebView.addJavascriptInterface(WebAppInterface(), "Android")
mWebView.addJavascriptInterface(SmsRetrieverInterface(this, mWebView), "SmsRetriever")
mWebView.loadUrl(Const.PAGE_URL)
val window = window
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
window.statusBarColor = Color.parseColor("#0B192F") // 원하는 색상 설정
window.decorView.systemUiVisibility = 0 // 기본 상태(어두운 배경에 밝은 글자)
askNotificationPermission()
}
// Declare the launcher at the top of your Activity/Fragment:
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission(),
) { isGranted: Boolean ->
if (isGranted) {
// FCM SDK (and your app) can post notifications.
} else {
// TODO: Inform user that that your app will not show notifications.
}
}
private fun askNotificationPermission() {
// This is only necessary for API level >= 33 (TIRAMISU)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.POST_NOTIFICATIONS
) ==
PackageManager.PERMISSION_GRANTED
) {
// FCM SDK (and your app) can post notifications.
} else if (shouldShowRequestPermissionRationale(android.Manifest.permission.POST_NOTIFICATIONS)) {
// TODO: display an educational UI explaining to the user the features that will be enabled
// by them granting the POST_NOTIFICATION permission. This UI should provide the user
// "OK" and "No thanks" buttons. If the user selects "OK," directly request the permission.
// If the user selects "No thanks," allow the user to continue without notifications.
} else {
// Directly ask for the permission
requestPermissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS)
}
}
}
@JavascriptInterface
fun copyToClipboard(text: String?) {
val clipboard: ClipboardManager =
mContext.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Copied Text", text)
clipboard.setPrimaryClip(clip)
// 사용자에게 피드백 제공 (Toast 메시지)
//Toast.makeText(mContext, "클립보드에 복사되었습니다!", Toast.LENGTH_SHORT).show()
}
override fun onStop() {
super.onStop()
onBackground()
}
override fun onPause() {
super.onPause()
try {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} catch (e: NullPointerException) {
}
LocalBroadcastManager.getInstance(this).unregisterReceiver(smsReceiver)
onBackground()
}
private fun setCookieAllow(webView: WebView) {
try {
val cookieManager = CookieManager.getInstance()
cookieManager.setAcceptCookie(true)
webView.settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
cookieManager.setAcceptThirdPartyCookies(webView, true)
} catch (_: Exception) {
}
}
// 페이지 접속 에러 처리
fun showErrorPage(uri: Uri?) {
if (uri != null && (uri.toString().endsWith("favicon.ico") || uri.toString()
.indexOf("sockjs-node") != -1)
) {
return
}
// 에러 처리 부분
}
// 페이지 로드 완료 처리
fun pageFinishedAction(uri: Uri?) {
// 로드 완료 처리 부분
}
// 링크 주소 인젝션 처리
fun checkUrl(webview: WebView?, uri: Uri?): Boolean {
// 링크 주소 중간에 가로채기 관련 별도 처리 추가
if (uri != null) {
if (!uri.toString().startsWith(Const.PAGE_URL)) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(uri.toString()))
startActivity(intent)
return true
}
}
return false
}
override fun onBackPressed() {
val currentUrl = mWebView.url
if (currentUrl != null && (currentUrl.contains("dashboard") || currentUrl.contains("chatlist") || currentUrl.contains("login") || currentUrl.contains("feed") )) {
exitAppAction()
}else
{
mWebView.loadUrl("javascript:appBackAction()")
}
//onBackAction()
// if (mWebView.canGoBack()) {
// // 웹뷰가 뒤로 갈 수 있는 경우
// val currentUrl = mWebView.url
// if (currentUrl != null && (currentUrl.contains("dashboard") || currentUrl.contains("feed") || currentUrl.contains("chatlist"))) {
//
// }else {
// mWebView.loadUrl("javascript:appBackAction()");
// }
// } else {
//
// }
//mWebView.loadUrl("javascript:appBackAction()");
}
private val onBackPressedCallback: OnBackPressedCallback =
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
//onBackAction()
this.isEnabled = false
onBackPressedDispatcher.onBackPressed()
}
}
// 뒤로가기 버튼 처리
var back = 0L
// private fun onBackAction() {
// val layIntro = findViewById<View>(R.id.lay_intro)
// if (layIntro.visibility == View.VISIBLE) {
// if (System.currentTimeMillis() > back + 2000) {
// back = System.currentTimeMillis()
// showCenterToast(R.string.backkey_finish)
// } else {
// hideCenterToast()
// finish()
// }
// return
// } else {
// mWebView.loadUrl("javascript:appBackAction()")
// return
// }
// }
private fun onBackground() {
mWebView.loadUrl("javascript:appBackground()")
}
private fun onForeground() {
mWebView.loadUrl("javascript:appForeground()")
}
inner class WebAppInterface
internal constructor() {
@JavascriptInterface
fun nativeBridge(cmd: String?, data: String?, callback: String?) {
if (cmd != null) {
for (bridgeItem in Const.APP_BRIDGE_LIST) {
if (bridgeItem.cmd.equals(cmd, true)) {
bridgeItem.startAction(mActivity, mWebView, data, callback)
}
}
}
}
}
private fun showCenterToast(msgRes: Int, duration: Int = Toast.LENGTH_LONG) {
hideCenterToast()
runOnUiThread {
mToast = Toast.makeText(mContext, msgRes, duration)
mToast!!.setGravity(Gravity.CENTER, 0, 0)
mToast!!.show()
}
}
private fun hideCenterToast() {
runOnUiThread {
if (mToast != null) {
mToast!!.cancel()
mToast = null
}
}
}
override fun hideIntroAction() {
val layIntro = findViewById<View>(R.id.lay_intro)
if (layIntro.visibility == View.VISIBLE) {
val ani = AnimationUtil.getAlpha(layIntro, 350, 1f, 0f)
if (ani != null) {
ani.addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator) {
}
override fun onAnimationEnd(animation: Animator) {
layIntro.visibility = View.GONE
}
override fun onAnimationCancel(animation: Animator) {
layIntro.visibility = View.GONE
}
override fun onAnimationRepeat(animation: Animator) {
}
})
ani.start()
} else {
layIntro.visibility = View.GONE
}
}
}
override fun exitAppAction() {
if (System.currentTimeMillis() > back + 2000) {
back = System.currentTimeMillis()
showCenterToast(R.string.backkey_finish)
} else {
hideCenterToast()
finish()
}
}
companion object {
private const val TAG = "MainActivity"
}
override fun onResume() {
super.onResume()
LocalBroadcastManager.getInstance(this)
.registerReceiver(smsReceiver, IntentFilter("SMS_CODE_RECEIVED"))
onForeground()
}
private val smsReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val smsCode = intent?.getStringExtra("smsCode")
smsCode?.let {
val smsRetrieverInterface = SmsRetrieverInterface(this@MainActivity, mWebView)
smsRetrieverInterface.onSmsReceived(it)
}
}
}
}