diff --git a/app/src/main/java/com/naviapp/app/NaviApplication.kt b/app/src/main/java/com/naviapp/app/NaviApplication.kt index f5cfad5872..0ed84c3d67 100644 --- a/app/src/main/java/com/naviapp/app/NaviApplication.kt +++ b/app/src/main/java/com/naviapp/app/NaviApplication.kt @@ -29,6 +29,8 @@ import com.navi.analytics.alfred.utils.log import com.navi.analytics.utils.NaviTrackEvent import com.navi.base.sharedpref.PreferenceManager import com.navi.base.utils.AppLaunchUtils +import com.navi.base.utils.QaReleaseLogUtil +import com.navi.base.utils.QaReleaseLogUtil.buildQaReleaseLogMessage import com.navi.base.utils.isNotNullAndNotEmpty import com.navi.chat.base.ChatBaseActivity import com.navi.common.firebaseremoteconfig.FirebaseRemoteConfigHelper @@ -102,6 +104,7 @@ open class NaviApplication : MultiDexApplication(), Application.ActivityLifecycl PreferenceManager.init(this) NaviSDKHelper.init(naviApplication = this) registerActivityLifecycleCallbacks(this) + QaReleaseLogUtil.init(context = applicationContext, flavour = BuildConfig.FLAVOR) // Dumping anr data to click stream ANRWatchDog().setIgnoreDebugger(true).setReportMainThreadOnly().setANRListener { @@ -123,6 +126,10 @@ open class NaviApplication : MultiDexApplication(), Application.ActivityLifecycl anrEventProperties ) + buildQaReleaseLogMessage( + data = anrEventProperties, + logType = QaReleaseLogUtil.ReleaseLogType.ANR_LOG.name + ) if (isDifferentPackage.not() && (AlfredManager.config.getAlfredStatus() && AlfredManager.config.getAnrEnableStatus())) { anrEventProperties[STACK_TRACE] = it.cause?.stackTrace?.get(0).toString() val anrView = @@ -152,6 +159,10 @@ open class NaviApplication : MultiDexApplication(), Application.ActivityLifecycl NaviTrackEvent.trackEventOnClickStream( GLOBAL_APP_CRASH, crashEventProperties ) + buildQaReleaseLogMessage( + data = crashEventProperties, + logType = QaReleaseLogUtil.ReleaseLogType.CRASH_LOG.name + ) if (isDifferentPackage.not() && (AlfredManager.config.getAlfredStatus() && AlfredManager.config.getCrashEnableStatus())) { exception.stackTrace[0]?.let { stackTraceElement -> crashEventProperties[STACK_TRACE] = stackTraceElement.toString() diff --git a/app/src/main/java/com/naviapp/common/navigator/NaviDeepLinkNavigator.kt b/app/src/main/java/com/naviapp/common/navigator/NaviDeepLinkNavigator.kt index b1514e9ab0..7ce3a1a062 100644 --- a/app/src/main/java/com/naviapp/common/navigator/NaviDeepLinkNavigator.kt +++ b/app/src/main/java/com/naviapp/common/navigator/NaviDeepLinkNavigator.kt @@ -29,6 +29,7 @@ import com.navi.ap.utils.constants.APP_PLATFORM_APPLICATION_TYPE import com.navi.ap.utils.constants.PL import com.navi.base.deeplink.listener.DeepLinkListener import com.navi.base.deeplink.util.DeeplinkConstants.LOGOUT +import com.navi.base.deeplink.util.DeeplinkConstants.RELEASE_LOG import com.navi.base.model.CtaData import com.navi.base.model.LineItem import com.navi.base.sharedpref.CommonPrefConstants.CURRENT_USER @@ -138,6 +139,7 @@ import com.naviapp.personalloanrevamp.getloanRevamp.activities.SkipMandateV2Acti import com.naviapp.personalloanrevamp.useridentificationv2.activities.PermissionV2Activity import com.naviapp.personalloanrevamp.useridentificationv2.activities.PermissionV2LocExpActivity import com.naviapp.registration.RegistrationActivity +import com.naviapp.releaselog.activity.ReleaseLogActivity import com.naviapp.status_tracker.StatusTrackerActivity import com.naviapp.usernotification.activities.UserNotificationActivity import com.naviapp.utils.Constants @@ -902,6 +904,9 @@ object NaviDeepLinkNavigator : DeepLinkListener { LOGOUT -> { deleteCacheAndOpenLoginPage() } + RELEASE_LOG -> { + intent = Intent(activity, ReleaseLogActivity::class.java) + } FORGE -> { intent = Intent(activity, ForgeActivity::class.java) } diff --git a/app/src/main/java/com/naviapp/home/fragment/ProfileFragment.kt b/app/src/main/java/com/naviapp/home/fragment/ProfileFragment.kt index df7ebbafc5..e657b12569 100644 --- a/app/src/main/java/com/naviapp/home/fragment/ProfileFragment.kt +++ b/app/src/main/java/com/naviapp/home/fragment/ProfileFragment.kt @@ -20,11 +20,14 @@ import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.LinearLayoutManager import com.navi.analytics.utils.NaviTrackEvent import com.navi.base.deeplink.util.DeeplinkConstants.LOGOUT +import com.navi.base.deeplink.util.DeeplinkConstants.RELEASE_LOG import com.navi.base.model.ActionData +import com.navi.base.model.CtaData import com.navi.base.model.GenericAnalyticsData import com.navi.base.model.NaviClickAction import com.navi.base.model.NaviWidgetClickWithActionData import com.navi.base.sharedpref.PreferenceManager +import com.navi.base.utils.QaReleaseLogUtil.isQaRelease import com.navi.base.utils.orTrue import com.navi.common.listeners.FragmentInterchangeListener import com.navi.common.ui.fragment.BaseFragment @@ -35,6 +38,7 @@ import com.navi.naviwidgets.models.WidgetChangedData import com.navi.naviwidgets.utils.APP_UPDATE_ENABLE import com.navi.naviwidgets.utils.IN_APP_UPDATE import com.navi.naviwidgets.viewholder.ViewHolderFactoryImpl +import com.naviapp.BuildConfig import com.naviapp.R import com.naviapp.analytics.utils.NaviAnalytics import com.naviapp.analytics.utils.NaviAnalytics.Companion.PROFILE_BASIC_DETAILS @@ -49,6 +53,7 @@ import com.naviapp.home.analytics.LandingScreenAnalytics import com.naviapp.home.viewmodel.ProfileVM import com.naviapp.utils.IntentConstants import com.naviapp.utils.addDivider +import com.naviapp.utils.setVisibilityState import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -96,6 +101,19 @@ class ProfileFragment : BaseFragment(), WidgetCallback { binding.backIcon.setOnClickListener { requireActivity().finish() } + if (isQaRelease) { + binding.btnLogs.apply { + this.setVisibilityState(View.VISIBLE) + this.setOnClickListener { + NaviDeepLinkNavigator.navigate( + activity = activity, + ctaData = CtaData(url = RELEASE_LOG) + ) + } + } + } else { + binding.btnLogs.setVisibilityState(View.GONE) + } } private fun reInitData() { diff --git a/app/src/main/java/com/naviapp/network/retrofit/ResponseCallback.kt b/app/src/main/java/com/naviapp/network/retrofit/ResponseCallback.kt index 1b1ce76ef1..5beafcbb1f 100644 --- a/app/src/main/java/com/naviapp/network/retrofit/ResponseCallback.kt +++ b/app/src/main/java/com/naviapp/network/retrofit/ResponseCallback.kt @@ -12,6 +12,8 @@ import com.google.gson.Gson import com.google.gson.reflect.TypeToken import com.navi.analytics.utils.NaviTrackEvent import com.navi.base.utils.BaseUtils +import com.navi.base.utils.QaReleaseLogUtil +import com.navi.base.utils.QaReleaseLogUtil.buildQaReleaseLogMessage import com.navi.common.CommonLibManager import com.navi.common.network.ApiConstants import com.navi.common.network.ApiConstants.API_CODE_ERROR @@ -59,7 +61,15 @@ abstract class ResponseCallback { } private fun handleResponse(response: Response>): RepoResult { - addApiUrlInErrorResponse(response?.body()?.errors, response?.raw()?.request?.url.toString()) + addApiUrlInErrorResponse(response.body()?.errors, response.raw().request.url.toString()) + buildQaReleaseLogMessage( + responseData = response.body()?.data, + statusCode = response.body()?.statusCode.toString(), + request = response.raw().request, + logType = QaReleaseLogUtil.ReleaseLogType.NETWORK_LOG.name, + response = response.raw(), + requestMethod = response.raw().request.method + ) response.body()?.let { if (it.errors?.firstOrNull()?.code == E_OFFER_EXPIRED) { handleError(it.errors, RedirectPageStatus(rejectReason = LOAN_OFFER_EXPIRED)) diff --git a/app/src/main/java/com/naviapp/releaselog/activity/ReleaseLogActivity.kt b/app/src/main/java/com/naviapp/releaselog/activity/ReleaseLogActivity.kt new file mode 100644 index 0000000000..d5dc096b26 --- /dev/null +++ b/app/src/main/java/com/naviapp/releaselog/activity/ReleaseLogActivity.kt @@ -0,0 +1,31 @@ +package com.naviapp.releaselog.activity + +import android.os.Bundle +import androidx.activity.compose.setContent +import androidx.activity.viewModels +import androidx.compose.ui.platform.LocalContext +import com.navi.common.model.ModuleNameV2 +import com.navi.common.ui.activity.BaseActivity +import com.naviapp.releaselog.screens.ReleaseLogScreen +import com.naviapp.releaselog.viewmodel.ReleaseLogViewModel +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class ReleaseLogActivity : BaseActivity() { + + private val viewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + ReleaseLogScreen(LocalContext.current, viewModel) + } + } + + override val screenName: String get() = TAG + override val moduleName: ModuleNameV2 get() = ModuleNameV2.COMMON + + companion object { + const val TAG = "RELEASE_LOG" + } +} diff --git a/app/src/main/java/com/naviapp/releaselog/screens/ReleaseLogScreen.kt b/app/src/main/java/com/naviapp/releaselog/screens/ReleaseLogScreen.kt new file mode 100644 index 0000000000..3d6977af00 --- /dev/null +++ b/app/src/main/java/com/naviapp/releaselog/screens/ReleaseLogScreen.kt @@ -0,0 +1,233 @@ +package com.naviapp.releaselog.screens + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.LocalTextStyle +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Search +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.naviapp.dashboard.menu.customersupport.screens.NaviErrorScreen +import com.naviapp.dashboard.menu.customersupport.screens.NaviLoadingScreen +import com.naviapp.releaselog.viewmodel.ReleaseLogViewModel + + +@Composable +fun ReleaseLogScreen(context: Context, viewModel: ReleaseLogViewModel) { + val logsDataState by viewModel.logsData.collectAsState() + + LaunchedEffect(Unit) { + viewModel.readLogFile(context = context) + } + + when (logsDataState) { + ReleaseLogViewModel.ReleaseLogState.Error -> { + NaviErrorScreen() + } + + ReleaseLogViewModel.ReleaseLogState.Loading -> { + NaviLoadingScreen() + } + + is ReleaseLogViewModel.ReleaseLogState.Success -> { + val data = (logsDataState as ReleaseLogViewModel.ReleaseLogState.Success).data + ShowUi(data = data, context = context, viewModel = viewModel) + } + } + +} + +@Composable +fun ShowUi( + data: List, context: Context, viewModel: ReleaseLogViewModel +) { + val clipboardManager = + context.getSystemService(AppCompatActivity.CLIPBOARD_SERVICE) as ClipboardManager + var searchTextState by remember { mutableStateOf("") } + Column( + modifier = Modifier + .fillMaxHeight() + .padding(16.dp) + ) { + SearchBar(searchText = searchTextState, + viewModel = viewModel, + context = context, + onSearchTextChanged = { searchQuery -> + searchTextState = searchQuery + if (searchQuery.isEmpty()) { + viewModel.showOriginalLogs() + } + }) + + Spacer(modifier = Modifier.height(8.dp)) + + if (data.isNotEmpty()) { + Box( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + ) { + Column( + modifier = Modifier.fillMaxHeight() + ) { + data.forEach { log -> + LogItem(log = log, onLongPress = { + if (log.isNotEmpty()) { + val clipData: ClipData = ClipData.newPlainText("Logs", log) + clipboardManager.setPrimaryClip(clipData) + Toast.makeText( + context, "Copied to Clipboard", Toast.LENGTH_SHORT + ).show() + } + }) + + } + } + } + Spacer(modifier = Modifier.height(2.dp)) + } else { + Box( + modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center + ) { + Text(text = "No logs available", color = Color.Gray) + } + } + } +} + +@Composable +fun SearchBar( + searchText: String, + onSearchTextChanged: (String) -> Unit, + viewModel: ReleaseLogViewModel, + context: Context +) { + Box( + modifier = Modifier + .fillMaxWidth() + .background(Color.Gray) + .padding(8.dp) + .height(30.dp) + .clip(RoundedCornerShape(8.dp)) + ) { + Row( + modifier = Modifier + .fillMaxSize() + .background(Color.White), + verticalAlignment = Alignment.CenterVertically + ) { + BasicTextField( + value = searchText, + onValueChange = { + if (it != searchText) { + onSearchTextChanged(it) + } + }, + textStyle = LocalTextStyle.current.copy( + color = Color.Black, fontSize = 16.sp, letterSpacing = 0.15.sp + ), + modifier = Modifier + .weight(1f) + .background(Color.White) + .padding(4.dp), + singleLine = true, + keyboardOptions = KeyboardOptions.Default.copy( + imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions(onDone = { + if (searchText.length < 3) { + if (searchText.isEmpty()) { + viewModel.showOriginalLogs() + } + Toast.makeText(context, "Min Three Characters", Toast.LENGTH_SHORT).show() + } else { + viewModel.scrollToMatchedText(searchText = searchText) + } + }) + ) + SearchIcon( + imageVector = Icons.Default.Search, + contentDescription = "Search", + onClick = { + if (searchText.length < 3) { + if (searchText.isEmpty()) { + viewModel.showOriginalLogs() + } + Toast.makeText(context, "Min Three Characters", Toast.LENGTH_SHORT).show() + } else { + viewModel.scrollToMatchedText(searchText = searchText) + } + }) + } + } +} + + +@Composable +fun SearchIcon( + imageVector: ImageVector, contentDescription: String?, onClick: () -> Unit +) { + val density = LocalDensity.current.density + Image(imageVector = imageVector, + contentDescription = contentDescription, + modifier = Modifier + .clickable { onClick() } + .padding(8.dp) + .size(8.dp * density)) +} + + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun LogItem(log: String, onLongPress: () -> Unit) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp, vertical = 10.dp) + .combinedClickable(onClick = {}, onLongClick = onLongPress, onDoubleClick = {}) + ) { + Text( + text = log, modifier = Modifier, maxLines = 1, overflow = TextOverflow.Ellipsis + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/naviapp/releaselog/viewmodel/ReleaseLogViewModel.kt b/app/src/main/java/com/naviapp/releaselog/viewmodel/ReleaseLogViewModel.kt new file mode 100644 index 0000000000..cf76232e48 --- /dev/null +++ b/app/src/main/java/com/naviapp/releaselog/viewmodel/ReleaseLogViewModel.kt @@ -0,0 +1,95 @@ +package com.naviapp.releaselog.viewmodel + +import android.content.Context +import androidx.lifecycle.viewModelScope +import com.navi.base.utils.QA_RELEASE_LOGS_FILE_NAME +import com.navi.base.utils.QA_RELEASE_LOGS_FOLDER_NAME +import com.navi.common.utils.log +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import java.io.BufferedReader +import java.io.File +import java.io.FileInputStream +import java.io.InputStreamReader +import com.navi.common.viewmodel.BaseVM +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import javax.inject.Inject + +@HiltViewModel +class ReleaseLogViewModel @Inject constructor() : BaseVM() { + + private val _logsData = MutableStateFlow( + ReleaseLogState.Loading + ) + val logsData = _logsData.asStateFlow() + private var rawLogs: List = emptyList() + + fun readLogFile(context: Context) { + viewModelScope.launch(Dispatchers.IO) { + try { + _logsData.emit(ReleaseLogState.Loading) + val logDirectoryName = QA_RELEASE_LOGS_FOLDER_NAME + val logFileName = QA_RELEASE_LOGS_FILE_NAME + val logFile = File(context.filesDir, "$logDirectoryName/$logFileName") + val logs = mutableListOf() + if (logFile.exists()) { + val fileInputStream = FileInputStream(logFile) + val bufferedReader = BufferedReader(InputStreamReader(fileInputStream)) + var line: String? + val lines = mutableListOf() + val linesToReadFromBottom = 150 + + while (bufferedReader.readLine().also { line = it } != null) { + line?.let { + if (it.isNotEmpty()) { + lines.add(it) + } + } + } + val startIndex = maxOf(0, lines.size - linesToReadFromBottom) + for (i in lines.size - 2 downTo startIndex) { + logs.add(lines[i]) + } + bufferedReader.close() + fileInputStream.close() + rawLogs = logs + _logsData.emit(ReleaseLogState.Success(logs)) + } + } catch (e: Exception) { + e.log() + _logsData.emit(ReleaseLogState.Error) + } + } + } + + + fun showOriginalLogs() { + viewModelScope.launch(Dispatchers.IO) { + _logsData.emit(ReleaseLogState.Success(rawLogs)) + } + } + + + fun scrollToMatchedText( + searchText: String + ) { + viewModelScope.launch(Dispatchers.IO) { + _logsData.emit(ReleaseLogState.Success(rawLogs.filter { + it.contains( + searchText, ignoreCase = true + ) + })) + } + } + + + sealed class ReleaseLogState { + object Loading : ReleaseLogState() + data class Success(val data: List) : ReleaseLogState() + object Error : ReleaseLogState() + } + + +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_profile_ln.xml b/app/src/main/res/layout/fragment_profile_ln.xml index 10a8afe83d..f82a950265 100644 --- a/app/src/main/res/layout/fragment_profile_ln.xml +++ b/app/src/main/res/layout/fragment_profile_ln.xml @@ -37,6 +37,20 @@ + + + Co-lending partners Father\'s name Enter full name + Check Logs diff --git a/app/src/qa/AndroidManifest.xml b/app/src/qa/AndroidManifest.xml index 5a199b92b5..a549ead0a4 100644 --- a/app/src/qa/AndroidManifest.xml +++ b/app/src/qa/AndroidManifest.xml @@ -13,6 +13,15 @@ + tools:targetApi="n" > + + + + diff --git a/navi-base/src/main/java/com/navi/base/deeplink/util/DeeplinkConstants.kt b/navi-base/src/main/java/com/navi/base/deeplink/util/DeeplinkConstants.kt index 4c7cefefd6..2fe23b3d32 100644 --- a/navi-base/src/main/java/com/navi/base/deeplink/util/DeeplinkConstants.kt +++ b/navi-base/src/main/java/com/navi/base/deeplink/util/DeeplinkConstants.kt @@ -15,4 +15,5 @@ object DeeplinkConstants { const val SPLASH = "SPLASH" const val USER_DETAIL = "userdetail" const val LOGOUT = "logout" + const val RELEASE_LOG = "RELEASE_LOG" } diff --git a/navi-base/src/main/java/com/navi/base/utils/Constants.kt b/navi-base/src/main/java/com/navi/base/utils/Constants.kt index 4e0bf587ea..87946a6d92 100644 --- a/navi-base/src/main/java/com/navi/base/utils/Constants.kt +++ b/navi-base/src/main/java/com/navi/base/utils/Constants.kt @@ -33,3 +33,5 @@ const val SKIP_LOADER = "skipLoader" const val PAN_VERIFY_POLLING_FAIL="PAN_VERIFY_POLLING_FAIL" const val APPLICATION_JSON = "application/json" const val EXCLUDE_FROM_HASH_ENCRYPTION = "excludeFromHashEncryption" +const val QA_RELEASE_LOGS_FILE_NAME = "NAVI_QA_RELEASE_LOGS.txt" +const val QA_RELEASE_LOGS_FOLDER_NAME = "NAVI_QA_RELEASE_LOGS_FOLDER" \ No newline at end of file diff --git a/navi-base/src/main/java/com/navi/base/utils/QaReleaseLogUtil.kt b/navi-base/src/main/java/com/navi/base/utils/QaReleaseLogUtil.kt new file mode 100644 index 0000000000..32ab419425 --- /dev/null +++ b/navi-base/src/main/java/com/navi/base/utils/QaReleaseLogUtil.kt @@ -0,0 +1,175 @@ +package com.navi.base.utils + +import android.content.Context +import com.google.gson.Gson +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import com.navi.base.BuildConfig +import kotlinx.coroutines.asCoroutineDispatcher +import okhttp3.Request +import okhttp3.Response +import java.io.File +import java.io.FileOutputStream +import java.io.OutputStreamWriter +import java.net.URL +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import java.util.concurrent.Executors + +object QaReleaseLogUtil { + + private lateinit var applicationContext: Context + private val coroutineDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + var isQaRelease: Boolean = false + + fun init(context: Context, flavour: String) { + applicationContext = context + isQaRelease = flavour == QA && !BuildConfig.DEBUG + } + + fun buildQaReleaseLogMessage( + statusCode: String? = null, + responseData: Any? = null, + logType: String, + request: Request? = null, + data: Map? = null, + response: Response? = null, + requestMethod: String? = null + ) { + if (!isQaRelease) { + return + } + coroutineDispatcher.executor.execute { + val logData = hashMapOf() + val timestamp = + SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date()) + .toString() + var endPoint = "" + when (logType) { + ReleaseLogType.NETWORK_LOG.name -> { + val networkData: HashMap = hashMapOf() + endPoint = URL(request?.url.toString()).path + + val responseJsonString = try { + Gson().toJson(responseData) + } catch (e: Exception) { + "" + } + val responseObject = try { + JsonParser.parseString(responseJsonString).asJsonObject + } catch (e: Exception) { + JsonObject() + } + networkData["RESPONSE_BODY"] = responseObject + networkData["RESPONSE_CODE"] = statusCode.toString() + + val requestJsonString = try { + Gson().toJson(responseData) + } catch (e: Exception) { + "" + } + val requestObject = try { + JsonParser.parseString(requestJsonString).asJsonObject + } catch (e: Exception) { + JsonObject() + } + networkData["REQUEST_BODY"] = requestObject + networkData["URL"] = request?.url.toString() + + val requestHeaderMap: HashMap = hashMapOf() + request?.headers?.forEach { + requestHeaderMap[it.first] = it.second + } + + val requestHeaderJsonString = try { + Gson().toJson(requestHeaderMap) + } catch (e: Exception) { + EMPTY + } + val requestHeaderObject = try { + JsonParser.parseString(requestHeaderJsonString).asJsonObject + } catch (e: Exception) { + JsonObject() + } + networkData["REQUEST_HEADER"] = requestHeaderObject + + + val responseHeaderMap: HashMap = hashMapOf() + response?.headers?.forEach { + responseHeaderMap[it.first] = it.second + } + + val responseHeaderJsonString = try { + Gson().toJson(responseHeaderMap) + } catch (e: Exception) { + EMPTY + } + val responseHeaderObject = try { + JsonParser.parseString(responseHeaderJsonString).asJsonObject + } catch (e: Exception) { + JsonObject() + } + networkData["RESPONSE_HEADER"] = responseHeaderObject + networkData["REQUEST_METHOD"] = requestMethod.toString() + + logData[timestamp] = networkData + } + + ReleaseLogType.ANR_LOG.name, ReleaseLogType.CRASH_LOG.name -> { + logData[timestamp] = data as HashMap + } + + else -> {} + } + val logMessage = Gson().toJson(logData) + writeLogToFile( + logMessage = logMessage, logType, timestamp, endPoint, statusCode, requestMethod + ) + } + } + + private fun writeLogToFile( + logMessage: String, + logType: String, + timeStamp: String, + url: String, + statusCode: String?, + requestMethod: String? + ) { + val logDirectoryName = QA_RELEASE_LOGS_FOLDER_NAME + val logFileName = QA_RELEASE_LOGS_FILE_NAME + val appInternalStorageDir = applicationContext.filesDir + val logDirectory = File(appInternalStorageDir, logDirectoryName) + if (!logDirectory.exists()) { + logDirectory.mkdirs() + } + val logFile = File(logDirectory, logFileName) + try { + val fileOutputStream = FileOutputStream(logFile, true) + val outputStreamWriter = OutputStreamWriter(fileOutputStream) + + outputStreamWriter.append("\r\n") + if (logType == ReleaseLogType.NETWORK_LOG.name) { + outputStreamWriter.append("\r\n---$statusCode $requestMethod $url $timeStamp------") + } else { + outputStreamWriter.append("\r\n--- $logType $timeStamp------") + } + outputStreamWriter.append("\r\n") + outputStreamWriter.append(logMessage) + outputStreamWriter.flush() + + outputStreamWriter.close() + fileOutputStream.close() + } catch (e: Exception) { + e.printStackTrace() + } + } + + enum class ReleaseLogType { + NETWORK_LOG, + CRASH_LOG, + ANR_LOG + } + +} \ No newline at end of file diff --git a/pulse/src/main/java/com/navi/pulse/network/PulseNetworkRepository.kt b/pulse/src/main/java/com/navi/pulse/network/PulseNetworkRepository.kt index fc49de613d..ceaf59eb8f 100644 --- a/pulse/src/main/java/com/navi/pulse/network/PulseNetworkRepository.kt +++ b/pulse/src/main/java/com/navi/pulse/network/PulseNetworkRepository.kt @@ -7,10 +7,21 @@ package com.navi.pulse.network +import com.navi.base.utils.QaReleaseLogUtil +import com.navi.base.utils.QaReleaseLogUtil.buildQaReleaseLogMessage import retrofit2.Response class PulseNetworkRepository { suspend fun sendEvents(url: String, pulseRequest: PulseRequest): Response { - return PulseRetrofitProvider.getApiService().sendEvents(url, pulseRequest) + val response = PulseRetrofitProvider.getApiService().sendEvents(url, pulseRequest) + buildQaReleaseLogMessage( + responseData = response.body(), + statusCode = response.body()?.code.toString(), + request = response.raw().request, + logType = QaReleaseLogUtil.ReleaseLogType.NETWORK_LOG.name, + response = response.raw(), + requestMethod = response.raw().request.method + ) + return response } }