diff --git a/.gitignore b/.gitignore index bec7fdb0..6e1af9e5 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,5 @@ kotlin-ide/ .kotlin/ .teamcity .idea/ +.aider* +.env diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 967417e7..5a8595a8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -30,6 +30,13 @@ android { getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) + signingConfig = signingConfigs.getByName("debug") + } + + debug { + isDebuggable = true + applicationIdSuffix = ".debug" + versionNameSuffix = "-debug" } } compileOptions { @@ -145,4 +152,7 @@ dependencies { // For JSON serialisation implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") + + // For Debug Build + implementation("com.jakewharton.timber:timber:5.0.1") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a5ed2580..99119f3b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -83,7 +83,7 @@ diff --git a/app/src/main/java/com/greybox/projectmesh/MNetLoggerAndroid.kt b/app/src/main/java/com/greybox/projectmesh/MNetLoggerAndroid.kt index bc9cf905..e31dd461 100644 --- a/app/src/main/java/com/greybox/projectmesh/MNetLoggerAndroid.kt +++ b/app/src/main/java/com/greybox/projectmesh/MNetLoggerAndroid.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import timber.log.Timber import java.io.File import java.text.DateFormat import java.util.Date @@ -52,12 +53,12 @@ class MNetLoggerAndroid( private fun doLog(priority: Int, message: String, exception: Exception?) { when (priority) { - Log.VERBOSE -> Log.v(MeshrabiyaConstants.LOG_TAG, message, exception) - Log.DEBUG -> Log.d(MeshrabiyaConstants.LOG_TAG, message, exception) - Log.INFO -> Log.i(MeshrabiyaConstants.LOG_TAG, message, exception) - Log.WARN -> Log.w(MeshrabiyaConstants.LOG_TAG, message, exception) - Log.ERROR -> Log.e(MeshrabiyaConstants.LOG_TAG, message, exception) - Log.ASSERT -> Log.wtf(MeshrabiyaConstants.LOG_TAG, message, exception) + Log.VERBOSE -> Timber.tag(MeshrabiyaConstants.LOG_TAG).v(exception, message) + Log.DEBUG -> Timber.tag(MeshrabiyaConstants.LOG_TAG).d(exception, message) + Log.INFO -> Timber.tag(MeshrabiyaConstants.LOG_TAG).i(exception, message) + Log.WARN -> Timber.tag(MeshrabiyaConstants.LOG_TAG).w(exception, message) + Log.ERROR -> Timber.tag(MeshrabiyaConstants.LOG_TAG).e(exception, message) + Log.ASSERT -> Timber.tag(MeshrabiyaConstants.LOG_TAG).wtf(exception, message) } val logDisplay = buildString { diff --git a/app/src/main/java/com/greybox/projectmesh/MainActivity.kt b/app/src/main/java/com/greybox/projectmesh/MainActivity.kt index 46868067..14e9703e 100644 --- a/app/src/main/java/com/greybox/projectmesh/MainActivity.kt +++ b/app/src/main/java/com/greybox/projectmesh/MainActivity.kt @@ -86,6 +86,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.platform.LocalSavedStateRegistryOwner import androidx.lifecycle.lifecycleScope +import com.greybox.projectmesh.logging.ReleaseTree import kotlinx.coroutines.launch import com.greybox.projectmesh.messaging.data.entities.Conversation import com.greybox.projectmesh.messaging.ui.viewmodels.ChatScreenViewModel @@ -93,13 +94,26 @@ import com.greybox.projectmesh.views.LogScreen import com.greybox.projectmesh.views.RequestPermissionsScreen +import org.kodein.di.android.BuildConfig import org.kodein.di.compose.localDI +import timber.log.Timber + class MainActivity : ComponentActivity(), DIAware { override val di by closestDI() override fun onCreate(savedInstanceState: Bundle?) { setTheme(R.style.Theme_ProjectMesh) super.onCreate(savedInstanceState) + + // Init Timber for Debug Build + if (BuildConfig.DEBUG) { + Timber.plant(Timber.DebugTree()) + } else { + Timber.plant(ReleaseTree()) + } + + Timber.d("Timber init in ${BuildConfig.BUILD_TYPE}") + // crash screen CrashHandler.init(applicationContext, CrashScreenActivity::class.java) val settingPref: SharedPreferences by di.instance(tag = "settings") @@ -110,7 +124,8 @@ class MainActivity : ComponentActivity(), DIAware { } //Initialize test device: TestDeviceService.initialize() - Log.d("MainActivity", "Test device initialized") + Timber.tag("MainActivity").d("Test device initialized") + setContent { val meshPrefs = getSharedPreferences("project_mesh_prefs", MODE_PRIVATE) var hasRunBefore by rememberSaveable { @@ -168,7 +183,7 @@ class MainActivity : ComponentActivity(), DIAware { val route = "chatScreen/$ip" currentScreen = route } catch (e: Exception) { - Log.e("MainActivity", "Invalid IP address in intent: $ip", e) + Timber.tag("MainActivity").e(e, "Invalid IP address in intent: %s", ip) // Fall back to home screen currentScreen = BottomNavItem.Home.route } @@ -218,6 +233,7 @@ class MainActivity : ComponentActivity(), DIAware { } } } + Timber.tag("BuildCheck").d("Build Type is: ${BuildConfig.BUILD_TYPE}") } private fun ensureDefaultDirectory() { @@ -228,14 +244,16 @@ class MainActivity : ComponentActivity(), DIAware { if (!defaultDirectory.exists()) { // Create the directory if it doesn't exist if (defaultDirectory.mkdirs()) { - Log.d("DirectoryCheck", "Default directory created: ${defaultDirectory.absolutePath}") + Timber.tag("DirectoryCheck").d("Default directory created: ${defaultDirectory + .absolutePath}") } else { - Log.e("DirectoryCheck", "Failed to create default directory: ${defaultDirectory.absolutePath}") + Timber.tag("DirectoryCheck").e("Failed to create default directory: %s", defaultDirectory.absolutePath) } } else { - Log.d("DirectoryCheck", "Default directory already exists: ${defaultDirectory.absolutePath}") + Timber.tag("DirectoryCheck").d("Default directory already exists: ${defaultDirectory + .absolutePath}") } } @@ -291,15 +309,15 @@ fun BottomNavApp(di: DI, val ip = entry.arguments?.getString("ip") ?: throw IllegalArgumentException("Invalid address") - Log.d("Navigation", "Navigating to chatScreen with parameter: $ip") + Timber.tag("Navigation").d("Navigating to chatScreen with parameter: $ip") //determine if this is an ip address val isValidIpAddress = ip.matches(Regex("^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\$")) - Log.d("Navigation", "Is valid IP address: $isValidIpAddress") + Timber.tag("Navigation").d("Is valid IP address: $isValidIpAddress") if(isValidIpAddress) { - Log.d("Navigation", "Showing chat for IP address: $ip") + Timber.tag("Navigation").d("Showing chat for IP address: $ip") // This is a valid IP address, use normal chat screen ChatScreen( virtualAddress = InetAddress.getByName(ip), @@ -308,12 +326,12 @@ fun BottomNavApp(di: DI, } ) } else { - Log.d("Navigation", "Showing chat for conversation ID: $ip") + Timber.tag("Navigation").d("Showing chat for conversation ID: $ip") //conversation id: handle offline chat ConversationChatScreen( conversationId = ip, onBackClick = { - Log.d("Navigation", "Navigating back from conversation") + Timber.tag("Navigation").d("Navigating back from conversation") navController.popBackStack() } ) @@ -333,8 +351,8 @@ fun BottomNavApp(di: DI, val sharedUrisViewModel: SharedUriViewModel = viewModel(activity) SendScreen( onSwitchToSelectDestNode = { uris -> - Log.d("uri_track_nav_send", "size: " + uris.size.toString()) - Log.d("uri_track_nav_send", "List: $uris") + Timber.tag("uri_track_nav_send").d("size: %s", uris.size.toString()) + Timber.tag("uri_track_nav_send").d("List: $uris") sharedUrisViewModel.setUris(uris) navController.navigate("selectDestNode") } @@ -344,8 +362,8 @@ fun BottomNavApp(di: DI, val activity = LocalContext.current as ComponentActivity val sharedUrisViewModel: SharedUriViewModel = viewModel(activity) val sendUris by sharedUrisViewModel.uris.collectAsState() - Log.d("uri_track_nav_selectDestNode", "size: " + sendUris.size.toString()) - Log.d("uri_track_nav_selectDestNode", "List: $sendUris") + Timber.tag("uri_track_nav_selectDestNode").d("size: %s", sendUris.size.toString()) + Timber.tag("uri_track_nav_selectDestNode").d("List: $sendUris") SelectDestNodeScreen( uris = sendUris, popBackWhenDone = {navController.popBackStack()}, @@ -366,7 +384,7 @@ fun BottomNavApp(di: DI, onLanguageChange = onLanguageChange, onRestartServer = onRestartServer, onDeviceNameChange = { newDeviceName -> - Log.d("BottomNavApp", "Device name changed to: $newDeviceName") + Timber.tag("BottomNavApp").d("Device name changed to: $newDeviceName") // Retrieve the local UUID from SharedPreferences val localUuid = settingsPrefs.getString("UUID", null) if (localUuid != null) { @@ -378,7 +396,7 @@ fun BottomNavApp(di: DI, name = newDeviceName, address = appServer.localVirtualAddr.hostAddress // <-- local IP here ) - Log.d("BottomNavApp", "Updated local user with new name: $newDeviceName") + Timber.tag("BottomNavApp").d("Updated local user with new name:$newDeviceName") // 2. Retrieve all connected users (those with a non-null address) val connectedUsers = userRepository.getAllConnectedUsers() @@ -386,16 +404,17 @@ fun BottomNavApp(di: DI, user.address?.let { ip -> try { val remoteAddr = InetAddress.getByName(ip) - Log.d("BottomNavApp", "Broadcasting updated info to: $ip") + Timber.tag("BottomNavApp").d("Broadcasting updated info to: $ip") appServer.pushUserInfoTo(remoteAddr) } catch (e: Exception) { - Log.e("BottomNavApp", "Error processing IP address: $ip", e) + Timber.tag("BottomNavApp").e(e, "Error processing IP " + + "address:$ip") } } } } } else { - Log.e("BottomNavApp", "Local UUID not found; cannot update user") + Timber.tag("BottomNavApp").e("Local UUID not found; cannot update user") } }, onAutoFinishChange = onAutoFinishChange, @@ -411,7 +430,7 @@ fun BottomNavApp(di: DI, onConversationSelected = { userIdentifier -> // userIdentifier will be either an IP address (for online users) // or a conversation ID (for offline users) - Log.d("Navigation", "Selected conversation/user: $userIdentifier") + Timber.tag("Navigation").d("Selected conversation/user: $userIdentifier") //navigate to the chat screen with this identifier navController.navigate("chatScreen/${userIdentifier}") } @@ -420,14 +439,18 @@ fun BottomNavApp(di: DI, ChatNodeListScreen( onNodeSelected = { ip -> val remoteAddr = InetAddress.getByName(ip) - Log.d("ChatHandshake", "Node selected with IP: $ip, remoteAddr: $remoteAddr") + Timber.tag("ChatHandshake").d("Node selected with IP: $ip, remoteAddr: + $remoteAddr") // Request remote user info - Log.d("ChatHandshake", "Requesting remote user info from: ${remoteAddr.hostAddress}") + Timber.tag("ChatHandshake").d("Requesting remote user info from: + ${remoteAddr + .hostAddress}") appServer.requestRemoteUserInfo(remoteAddr) // Push local user info to remote node - Log.d("ChatHandshake", "Pushing local user info to: ${remoteAddr.hostAddress}") + Timber.tag("ChatHandshake").d("Pushing local user info to: ${remoteAddr + .hostAddress}") appServer.pushUserInfoTo(remoteAddr) // Navigate to the chat screen @@ -453,7 +476,7 @@ fun BottomNavApp(di: DI, try { InetAddress.getByName(ipParam) } catch (e: Exception) { - Log.e("Navigation", "Error creating address from parameter: $ipParam", e) + Timber.tag("Navigation").e(e, "Error creating address from parameter:$ipParam") null } } else { @@ -489,7 +512,7 @@ fun ConversationChatScreen ( conversationId: String, onBackClick: () -> Unit ){ - Log.d("ConversationChatScreen", "Starting to load conversation: $conversationId") + Timber.tag("ConversationChatScreen").d("Starting to load conversation: $conversationId") var conversationState = remember { mutableStateOf(null) } var isLoading = remember { mutableStateOf(true) } @@ -501,19 +524,20 @@ fun ConversationChatScreen ( // Load the conversation data LaunchedEffect(conversationId) { - Log.d("ConversationChatScreen", "LaunchedEffect triggered for ID: $conversationId") + Timber.tag("ConversationChatScreen").d("LaunchedEffect triggered for ID: $conversationId") coroutineScope.launch { try { - Log.d("ConversationChatScreen", "Attempting to fetch conversation") + Timber.tag("ConversationChatScreen").d("Attempting to fetch conversation") val result = GlobalApp.GlobalUserRepo.conversationRepository.getConversationById(conversationId) - Log.d("ConversationChatScreen", "Fetch result: ${result != null}") + Timber.tag("ConversationChatScreen").d("Fetch result: ${result != null}") //conversationState.value = result if (result == null) { errorMessage.value = "Conversation not found" isLoading.value = false /* - Log.e("ConversationChatScreen", "Conversation not found: $conversationId") + Timber.tag("ConversationChatScreen").e("Conversation not found: + $conversationId") Toast.makeText( context, "Conversation not found", @@ -524,10 +548,10 @@ fun ConversationChatScreen ( }else { conversationState.value = result isLoading.value = false - Log.d("ConversationChatScreen", "Loaded conversation: ${result.userName}, online=${result.isOnline}") + Timber.tag("ConversationChatScreen").d("Loaded conversation: ${result.userName}, online=${result.isOnline}") } } catch (e: Exception) { - Log.e("ConversationChatScreen", "Error loading conversation", e) + Timber.tag("ConversationChatScreen").e(e, "Error loading conversation") errorMessage.value = e.message isLoading.value = false /* @@ -589,7 +613,8 @@ fun ConversationChatScreen ( conversation.userAddress?.let { ipAddress -> val statusFromManager = deviceStatusMap[ipAddress] ?: false if (isUserOnline != statusFromManager) { - Log.d("ConversationChatScreen", "Device status changed for ${conversation.userName}: ${if (statusFromManager) "online" else "offline"}") + Timber.tag("ConversationChatScreen").d("Device status changed for ${conversation + .userName}: ${if (statusFromManager) "online" else "offline"}") isUserOnline = statusFromManager } } @@ -600,20 +625,20 @@ fun ConversationChatScreen ( val virtualAddress = if (conversation?.userAddress.isNullOrEmpty()) { //use offline test device address if this is the offline test conversation if (conversation.userName == TestDeviceService.TEST_DEVICE_NAME_OFFLINE) { - Log.d("ConversationChatScreen", "Using offline test device address") + Timber.tag("ConversationChatScreen").d("Using offline test device address") InetAddress.getByName(TestDeviceService.TEST_DEVICE_IP_OFFLINE) } else { //use a placeholder address for regular offline users - Log.d("ConversationChatScreen", "Using placeholder address for offline user") + Timber.tag("ConversationChatScreen").d("Using placeholder address for offline user") InetAddress.getByName("0.0.0.0") } } else { //use the actual address if available - Log.d("ConversationChatScreen", "Using actual address: ${conversation.userAddress}") + Timber.tag("ConversationChatScreen").d("Using actual address: ${conversation.userAddress}") InetAddress.getByName(conversation.userAddress) } - Log.d("ConversationChatScreen", "Showing chat screen for: ${conversation.userName}") + Timber.tag("ConversationChatScreen").d("Showing chat screen for: ${conversation.userName}") // Show the chat screen ChatScreen( diff --git a/app/src/main/java/com/greybox/projectmesh/components/WifiConnection.kt b/app/src/main/java/com/greybox/projectmesh/components/WifiConnection.kt index 864274d4..e3ab6ca3 100644 --- a/app/src/main/java/com/greybox/projectmesh/components/WifiConnection.kt +++ b/app/src/main/java/com/greybox/projectmesh/components/WifiConnection.kt @@ -28,7 +28,6 @@ import com.ustadmobile.meshrabiya.vnet.wifi.WifiConnectException import java.util.regex.Pattern import android.util.Log - // This File is to pre-check the wifi connection, reusing from Meshrabiya test app /* WorkFlow: diff --git a/app/src/main/java/com/greybox/projectmesh/debug/CrashHandler.kt b/app/src/main/java/com/greybox/projectmesh/debug/CrashHandler.kt index 3cc15d6a..684faa5c 100644 --- a/app/src/main/java/com/greybox/projectmesh/debug/CrashHandler.kt +++ b/app/src/main/java/com/greybox/projectmesh/debug/CrashHandler.kt @@ -1,13 +1,9 @@ package com.greybox.projectmesh.debug -import android.R.layout import android.content.Context import android.content.Intent -import android.util.Log -import android.widget.FrameLayout -import android.widget.PopupWindow import com.google.gson.Gson -import org.xml.sax.helpers.DefaultHandler +import timber.log.Timber import java.lang.Exception import java.lang.Thread.UncaughtExceptionHandler import kotlin.system.exitProcess @@ -29,7 +25,7 @@ class CrashHandler(private val context: Context, private val defaultHandler: Unc { val crashIntent = Intent(applicationContext, activity).also { it.putExtra("CrashData", Gson().toJson(exception)) - Log.e("Project Mesh Error","Error: ",exception); + Timber.tag("Project Mesh Error").e(exception, "Error: "); } crashIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) @@ -50,7 +46,7 @@ class CrashHandler(private val context: Context, private val defaultHandler: Unc Gson().fromJson(intent.getStringExtra("CrashData"), Throwable::class.java) } catch (e: Exception) { - Log.e("CrashHandler","getThrowableFromIntent: ",e); + Timber.tag("CrashHandler",).e(e,"getThrowableFromIntent: "); null } diff --git a/app/src/main/java/com/greybox/projectmesh/logging/ReleaseTree.kt b/app/src/main/java/com/greybox/projectmesh/logging/ReleaseTree.kt new file mode 100644 index 00000000..7bb25e13 --- /dev/null +++ b/app/src/main/java/com/greybox/projectmesh/logging/ReleaseTree.kt @@ -0,0 +1,20 @@ +package com.greybox.projectmesh.logging + +import android.util.Log +import timber.log.Timber + +class ReleaseTree : Timber.Tree() { + @Suppress("LogNotTimber") + override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { + // Log WARN and ERROR only to avoid leaking debug info in release + if (priority == Log.ERROR || priority == Log.WARN) { + val safeTag = tag ?: "ReleaseTree" + Log.println(priority, safeTag, message) + + // Log Exceptions if present + t?.let { + Log.e(safeTag, "Exception: $message", it) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/FileEncoder.kt b/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/FileEncoder.kt index 25bcfbf4..6fd45f97 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/FileEncoder.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/FileEncoder.kt @@ -5,53 +5,12 @@ import android.net.Uri import java.io.File import java.io.FileOutputStream import java.io.InputStream -import java.io.OutputStream import kotlin.io.encoding.Base64 import kotlin.io.encoding.ExperimentalEncodingApi -import android.content.SharedPreferences -import android.os.Build -import android.util.Log -import com.greybox.projectmesh.GlobalApp -import com.greybox.projectmesh.db.MeshDatabase -import com.greybox.projectmesh.messaging.data.entities.Message -import com.greybox.projectmesh.messaging.network.MessageNetworkHandler -import com.greybox.projectmesh.extension.updateItem -import com.greybox.projectmesh.testing.TestDeviceService -import com.ustadmobile.meshrabiya.ext.copyToWithProgressCallback -import com.ustadmobile.meshrabiya.util.FileSerializer -import com.ustadmobile.meshrabiya.util.InetAddressSerializer -import fi.iki.elonen.NanoHTTPD -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.cancel -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.flow.updateAndGet -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.internal.headersContentLength -import java.io.Closeable import java.net.InetAddress import java.net.URLEncoder -import java.util.concurrent.atomic.AtomicInteger -import com.greybox.projectmesh.extension.getUriNameAndSize -import org.kodein.di.DI -import org.kodein.di.DIAware -import org.kodein.di.instance -import okhttp3.HttpUrl -import okhttp3.MediaType.Companion.toMediaType import java.net.HttpURLConnection import java.net.URL -import java.net.URLConnection -import java.net.URLDecoder //Use this to encode files not just images //Needs to be tested sometime diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/JSONSchema.kt b/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/JSONSchema.kt index 69ce291f..d7db4d44 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/JSONSchema.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/JSONSchema.kt @@ -1,8 +1,8 @@ package com.greybox.projectmesh.messaging.data.entities -import android.util.Log import org.json.JSONObject import org.json.JSONException +import timber.log.Timber class JSONSchema { @@ -22,8 +22,8 @@ class JSONSchema { """ //Takes JSON string and validates it against JSON Schema fun schemaValidation(json: String): Boolean { - //Log.d("JSONSchema", "Validating JSON: $json") - //Log.d("JSONSchema", "Against schema: $schemaString") + //Timber.tag("JSONSchema").d("Validating JSON: $json") + //Timber.tag("JSONSchema").d("Against schema: $schemaString") try { val schemaJson = JSONObject(schemaString) val jsonObject = JSONObject(json) @@ -31,7 +31,7 @@ class JSONSchema { validate(jsonObject, schemaJson) return true }catch (e: JSONException) { - Log.e("JSONSchema", "JSON schema validation failed: ${e.message}") + Timber.tag("JSONSchema",).e("JSON schema validation failed: ${e.message}") return false } } diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/network/MessageNetworkHandler.kt b/app/src/main/java/com/greybox/projectmesh/messaging/network/MessageNetworkHandler.kt index 1a15b2f4..d693e82b 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/network/MessageNetworkHandler.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/network/MessageNetworkHandler.kt @@ -5,9 +5,7 @@ import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent -import android.util.Log import androidx.core.app.NotificationCompat -import com.google.gson.Gson import com.greybox.projectmesh.GlobalApp import com.greybox.projectmesh.MainActivity import com.greybox.projectmesh.R @@ -17,7 +15,6 @@ import com.greybox.projectmesh.messaging.utils.ConversationUtils import com.greybox.projectmesh.messaging.repository.ConversationRepository import com.greybox.projectmesh.server.AppServer import com.greybox.projectmesh.testing.TestDeviceService -import com.greybox.projectmesh.util.NotificationHelper import java.net.InetAddress import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -29,11 +26,8 @@ import okhttp3.Request import org.kodein.di.DI import org.kodein.di.DIAware import org.kodein.di.instance -import com.greybox.projectmesh.user.UserRepository -import kotlinx.coroutines.runBlocking import android.content.SharedPreferences -import android.os.Parcel -import android.os.Parcelable +import timber.log.Timber import java.net.URI class MessageNetworkHandler( @@ -69,7 +63,7 @@ class MessageNetworkHandler( .get() .build() - Log.d("MessageNetworkHandler", "Request URL: ${request.url}") + Timber.tag("MessageNetworkHandler").d("Request URL: ${request.url}") //Turn this into JSON /* val gs = Gson() @@ -89,20 +83,25 @@ class MessageNetworkHandler( try { httpClient.newCall(request).execute().use { response -> if (response.isSuccessful) { - Log.d("MessageNetworkHandler", "Message sent successfully") + Timber.tag("MessageNetworkHandler").d("Message sent successfully") } else { - Log.e("MessageNetworkHandler", "Failed to send message: ${response.code}") + Timber.tag("MessageNetworkHandler").e("Failed to send message: " + + "${response + .code}") } } } catch (e: Exception) { - Log.e("MessageNetworkHandler", "Failed to send message, connection error: ${e.message}", e) + Timber.tag("MessageNetworkHandler").e(e,"Failed to send message, connection " + + "error: ${e.message}") } } catch (e: Exception) { - Log.e("MessageNetworkHandler", "Failed to send message to ${address.hostAddress}") + Timber.tag("MessageNetworkHandler").e("Failed to send message to ${address + .hostAddress}") } } } + companion object { //process incoming messages and route them to the correct conversation fun handleIncomingMessage( @@ -111,8 +110,8 @@ class MessageNetworkHandler( senderIp: InetAddress, incomingfile: URI? ): Message { - Log.d( - "MessageNetworkHandler", + Timber.tag( + "MessageNetworkHandler").d( "Handling incoming message: $chatMessage, from: ${senderIp.hostAddress}, has file: ${incomingfile != null}" ) @@ -134,8 +133,8 @@ class MessageNetworkHandler( val localUuid = GlobalApp.GlobalUserRepo.prefs.getString("UUID", null) ?: "local-user" val chatName = ConversationUtils.createConversationId(localUuid, userUuid) - Log.d( - "MessageNetworkHandler", + Timber.tag( + "MessageNetworkHandler").d( "Creating message with chat name: $chatName, sender: $sender" ) @@ -168,9 +167,9 @@ class MessageNetworkHandler( ) } - Log.d("MessageNetworkHandler", "Updated conversation with new message") + Timber.tag("MessageNetworkHandler").d("Updated conversation with new message") } catch (e: Exception) { - Log.e("MessageNetworkHandler", "Failed to update conversation", e) + Timber.tag("MessageNetworkHandler").e(e,"Failed to update conversation") } } return message @@ -190,7 +189,7 @@ class MessageNetworkHandler( ?.getDeclaredField("INSTANCE")?.get(null) as? Context ?: throw Exception("Cannot get application context") } catch (e: Exception) { - Log.e("MessageNetworkHandler", "Failed to get application context", e) + Timber.tag("MessageNetworkHandler").e(e,"Failed to get application context") return } @@ -228,9 +227,9 @@ class MessageNetworkHandler( val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.notify(message.hashCode(), notification) - Log.d("MessageNetworkHandler", "Showed notification for message with file") + Timber.tag("MessageNetworkHandler").d("Showed notification for message with file") } catch (e: Exception) { - Log.e("MessageNetworkHandler", "Failed to show notification", e) + Timber.tag("MessageNetworkHandler").e(e, "Failed to show notification") } } } diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/network/MessageService.kt b/app/src/main/java/com/greybox/projectmesh/messaging/network/MessageService.kt index 87317a37..1867f0a9 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/network/MessageService.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/network/MessageService.kt @@ -2,18 +2,15 @@ package com.greybox.projectmesh.messaging.network import android.content.SharedPreferences -import android.util.Log -import com.greybox.projectmesh.GlobalApp import com.greybox.projectmesh.messaging.repository.MessageRepository import com.greybox.projectmesh.messaging.data.entities.Message import com.greybox.projectmesh.messaging.repository.ConversationRepository -import com.greybox.projectmesh.messaging.data.entities.Conversation -import com.greybox.projectmesh.messaging.utils.MessageUtils import com.greybox.projectmesh.user.UserRepository import java.net.InetAddress import org.kodein.di.DI import org.kodein.di.DIAware import org.kodein.di.instance +import timber.log.Timber class MessageService( override val di: DI @@ -61,7 +58,7 @@ class MessageService( } }catch (e: Exception){ - Log.e("MessageService", "Error updating conversation with message", e) + Timber.tag("MessageService").e(e,"Error updating conversation with message") } } } \ No newline at end of file diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/repository/ConversationRepository.kt b/app/src/main/java/com/greybox/projectmesh/messaging/repository/ConversationRepository.kt index 81e8e79c..bb90d1dc 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/repository/ConversationRepository.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/repository/ConversationRepository.kt @@ -1,6 +1,5 @@ package com.greybox.projectmesh.messaging.repository -import android.util.Log import com.greybox.projectmesh.messaging.data.dao.ConversationDao import com.greybox.projectmesh.messaging.data.entities.Conversation import com.greybox.projectmesh.messaging.data.entities.Message @@ -9,6 +8,7 @@ import com.greybox.projectmesh.user.UserEntity import kotlinx.coroutines.flow.Flow import org.kodein.di.DI import org.kodein.di.DIAware +import timber.log.Timber class ConversationRepository( private val conversationDao: ConversationDao, @@ -22,9 +22,9 @@ class ConversationRepository( //get specific convo by id suspend fun getConversationById(conversationId: String): Conversation? { - Log.d("ConversationRepository", "Getting conversation by ID: $conversationId") + Timber.tag("ConversationRepository").d("Getting conversation by ID: $conversationId") val result = conversationDao.getConversationById(conversationId) - Log.d("ConversationRepository", "Result for ID $conversationId: ${result != null}") + Timber.tag("ConversationRepository").d("Result for ID $conversationId: ${result != null}") return result } @@ -32,15 +32,16 @@ class ConversationRepository( //create a unique conversation ID using both UUIDs in order to ensure consistency val conversationId = ConversationUtils.createConversationId(localUuid, remoteUser.uuid) - Log.d("ConversationRepository", "Looking for conversation with ID: $conversationId") - Log.d("ConversationRepository", "Local UUID: $localUuid, Remote UUID: ${remoteUser.uuid}") + Timber.tag("ConversationRepository").d("Looking for conversation with ID: $conversationId") + Timber.tag("ConversationRepository").d("Local UUID: $localUuid, Remote UUID: ${remoteUser + .uuid}") //try to get an existing conversation var conversation = conversationDao.getConversationById(conversationId) //if no conversation exists, create a new one if(conversation == null){ - Log.d("ConversationRepository", "Conversation not found, creating new one with ${remoteUser.name}") + Timber.d("ConversationRepository", "Conversation not found, creating new one with ${remoteUser.name}") conversation = Conversation( id = conversationId, @@ -53,9 +54,11 @@ class ConversationRepository( isOnline = remoteUser.address != null ) conversationDao.insertConversation(conversation) - Log.d("ConversationRepository", "Created new conversation with ${remoteUser.name}") + Timber.tag("ConversationRepository").d("Created new conversation with ${remoteUser + .name}") } else { - Log.d("ConversationRepository", "Found existing conversation with ${remoteUser.name}") + Timber.tag("ConversationRepository").d("Found existing conversation with ${remoteUser + .name}") } return conversation } @@ -98,9 +101,9 @@ class ConversationRepository( ) // Log for debugging - Log.d("ConversationRepository", "Updated user $userUuid connection status: online=$isOnline, address=$userAddress") + Timber.tag("ConversationRepository").d("Updated user $userUuid connection status: online=$isOnline, address=$userAddress") } catch (e: Exception) { - Log.e("ConversationRepository", "Failed to update user connection status", e) + Timber.tag("ConversationRepository").e(e,"Failed to update user connection status") } } diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/ui/screens/ChatScreen.kt b/app/src/main/java/com/greybox/projectmesh/messaging/ui/screens/ChatScreen.kt index 24f690dd..9d3cfab0 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/ui/screens/ChatScreen.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/ui/screens/ChatScreen.kt @@ -4,7 +4,6 @@ package com.greybox.projectmesh.messaging.ui.screens import android.net.Uri import android.os.Bundle -import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Arrangement @@ -73,6 +72,7 @@ import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import timber.log.Timber @Composable fun ChatScreen( @@ -126,9 +126,10 @@ fun ChatScreen( LaunchedEffect(statusMap[virtualAddress.hostAddress]) { val newStatus = statusMap[virtualAddress.hostAddress] ?: false if (deviceStatus != newStatus) { - Log.d( - "ChatScreen", - "Device status changed: ${virtualAddress.hostAddress} is now ${if (newStatus) "online" else "offline"}" + Timber.tag( + "ChatScreen") + .d("Device status changed: ${virtualAddress.hostAddress} is now ${if + (newStatus) "online" else "offline"}" ) deviceStatus = newStatus } @@ -374,7 +375,8 @@ fun DisplayAllMessages(uiState: ChatScreenModel, onClickButton: () -> Unit) { val hasMessages = uiState.allChatMessages.isNotEmpty() LaunchedEffect(uiState.allChatMessages.size) { - Log.d("ChatScreen", "DisplayAllMessages with ${uiState.allChatMessages.size} messages") + Timber.tag("ChatScreen").d("DisplayAllMessages with ${uiState.allChatMessages.size} %s", + "messages") } LazyColumn{ @@ -406,7 +408,7 @@ fun DisplayAllMessages(uiState: ChatScreenModel, onClickButton: () -> Unit) { items( items = uiState.allChatMessages ){ chatMessage -> - Log.d("ChatDebug", "Rendering message: ${chatMessage.content}") + Timber.tag("ChatDebug").d("Rendering message: ${chatMessage.content}") val sender: String = if (chatMessage.sender == "Me") { "Me" } else { diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/ui/viewmodels/ChatScreenViewModel.kt b/app/src/main/java/com/greybox/projectmesh/messaging/ui/viewmodels/ChatScreenViewModel.kt index 68448f70..af1621b5 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/ui/viewmodels/ChatScreenViewModel.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/ui/viewmodels/ChatScreenViewModel.kt @@ -13,16 +13,12 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import org.kodein.di.DI import com.greybox.projectmesh.server.AppServer -import com.greybox.projectmesh.server.AppServer.Companion.DEFAULT_PORT import com.greybox.projectmesh.server.AppServer.OutgoingTransferInfo -import com.greybox.projectmesh.server.AppServer.Status import com.ustadmobile.meshrabiya.ext.addressToDotNotation import com.ustadmobile.meshrabiya.ext.requireAddressAsInt import com.greybox.projectmesh.messaging.utils.ConversationUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import com.ustadmobile.meshrabiya.vnet.AndroidVirtualNode -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -31,11 +27,11 @@ import java.net.InetAddress import com.greybox.projectmesh.messaging.repository.ConversationRepository import com.greybox.projectmesh.user.UserEntity import android.content.SharedPreferences -import android.util.Log import androidx.lifecycle.SavedStateHandle import com.greybox.projectmesh.DeviceStatusManager import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.withTimeoutOrNull +import timber.log.Timber import java.net.URI class ChatScreenViewModel( @@ -75,13 +71,15 @@ class ChatScreenViewModel( } private val savedConversationId = savedStateHandle.get("conversationId") - //Log.d("ChatDebug", "GOT CONVERSATION ID FROM SAVED STATE: $savedConversationId") + //Timber.tag("ChatDebug").d("GOT CONVERSATION ID FROM SAVED STATE: $savedConversationId") private val conversationId = passedConversationId ?: ConversationUtils.createConversationId(localUuid, userUuid) private val chatName = savedConversationId ?: conversationId - //Log.d("ChatDebug", "USING CHAT NAME: $chatName (saved: $savedConversationId, generated: $conversationId)") + //Timber.tag("ChatDebug").d("USING CHAT NAME: $chatName (saved: $savedConversationId, + // generated: + // $conversationId)") @@ -115,7 +113,7 @@ class ChatScreenViewModel( // If we have a conversation ID from navigation, use it directly val effectiveChatName = if (savedConversationId != null) { - Log.d("ChatDebug", "USING SAVED CONVERSATION ID: $savedConversationId INSTEAD OF GENERATED: $chatName") + Timber.tag("ChatDebug").d("USING SAVED CONVERSATION ID: $savedConversationId INSTEAD OF GENERATED: $chatName") savedConversationId } else { chatName @@ -123,17 +121,16 @@ class ChatScreenViewModel( viewModelScope.launch { // Debug logs - Log.d("ChatDebug", "Will query messages with chatName: $chatName") - Log.d("ChatDebug", "Using Conversation ID for messages: $conversationId") - Log.d("ChatDebug", "User UUID: $userUuid") + Timber.tag("ChatDebug").d("Will query messages with chatName: $chatName") + Timber.tag("ChatDebug").d("Using Conversation ID for messages: $conversationId") + Timber.tag("ChatDebug").d("User UUID: $userUuid") //check database content in background withContext(Dispatchers.IO) { val allMessages = db.messageDao().getAll() - Log.d("ChatDebug", "All messages in database: ${allMessages.size}") + Timber.d("ChatDebug", "All messages in database: ${allMessages.size}") for (msg in allMessages) { - Log.d( - "ChatDebug", + Timber.tag("ChatDebug").d( "Message: id=${msg.id}, chat=${msg.chat}, content=${msg.content}, sender=${msg.sender}" ) } @@ -158,13 +155,14 @@ class ChatScreenViewModel( _uiState.update { prev -> prev.copy(allChatMessages = initialMessages) } - Log.d("ChatDebug", "IMMEDIATELY LOADED ${initialMessages.size} MESSAGES FOR OFFLINE ACCESS") + Timber.tag("ChatDebug").d("IMMEDIATELY LOADED ${initialMessages.size} " + + "MESSAGES FOR OFFLINE ACCESS") } else { - Log.d("ChatDebug", "NO INITIAL MESSAGES FOUND FOR CHAT: $chatName") + Timber.tag("ChatDebug").d("NO INITIAL MESSAGES FOUND FOR CHAT: $chatName") } } catch (e: Exception) { - Log.e("ChatDebug", "ERROR LOADING INITIAL MESSAGES: ${e.message}", e) + Timber.e("ChatDebug", "ERROR LOADING INITIAL MESSAGES: ${e.message}", e) } val messagesFlow = if (isTestDevice) { @@ -175,7 +173,7 @@ class ChatScreenViewModel( } if (testDeviceName != null) { - Log.d("ChatDebug", "Using multi-name query with: [$chatName, $testDeviceName]") + Timber.tag("ChatDebug").d("Using multi-name query with: [$chatName,$testDeviceName]") db.messageDao().getChatMessagesFlowMultipleNames( listOf(chatName, testDeviceName) ) @@ -188,7 +186,7 @@ class ChatScreenViewModel( //collect messages from the chosen flow messagesFlow.collect { newChatMessages -> - Log.d("ChatDebug", "Received ${newChatMessages.size} messages") + Timber.tag("ChatDebug").d("Received ${newChatMessages.size} messages") _uiState.update { prev -> prev.copy(allChatMessages = newChatMessages) } @@ -206,14 +204,11 @@ class ChatScreenViewModel( // Only update if status changed if (_deviceOnlineStatus.value != isOnline) { - Log.d( - "ChatDebug", - "Device status changed: $ipAddress is now ${if (isOnline) "online" else "offline"}" - ) + Timber.tag("ChatDebug").d("Device status changed: $ipAddress is now ${if (isOnline) "online" else "offline"}") _deviceOnlineStatus.value = isOnline if (isOnline) { - Log.d("ChatDebug", "Device came back online - refreshing message history") + Timber.d("ChatDebug", "Device came back online - refreshing message history") // Force refresh messages from database withContext(Dispatchers.IO) { val refreshedMessages = db.messageDao().getChatMessagesSync(chatName) @@ -247,10 +242,10 @@ class ChatScreenViewModel( //Mark this conversation as read conversationRepository.markAsRead(conversationId) - Log.d("ChatScreenViewModel", "Marked conversation as read: $conversationId") + Timber.tag("ChatScreenViewModel").d("Marked conversation as read: $conversationId") } } catch (e: Exception) { - Log.e("ChatScreenViewModel", "Error marking conversation as read", e) + Timber.tag("ChatScreenViewModel").e(e,"Error marking conversation as read") } } @@ -269,7 +264,7 @@ class ChatScreenViewModel( //use same conversationid as chat name val messageEntity = Message(0, sendTime, message, "Me", chatName, file) - Log.d("ChatDebug", "Sending message to chat: $chatName") + Timber.tag("ChatDebug").d("Sending message to chat: $chatName") viewModelScope.launch { //save to local database db.messageDao().addMessage(messageEntity) @@ -295,13 +290,9 @@ class ChatScreenViewModel( message = messageEntity ) - Log.d("ChatScreenViewModel", "Updated conversation with sent message") + Timber.tag("ChatScreenViewModel").d("Updated conversation with sent message") } catch (e: Exception) { - Log.e( - "ChatScreenViewModel", - "Failed to update conversation with sent message", - e - ) + Timber.tag("ChatScreenViewModel").e(e,"Failed to update conversation with sent message") } } @@ -317,23 +308,24 @@ class ChatScreenViewModel( // Update UI based on delivery status if (!delivered) { - Log.d("ChatDebug", "Message delivery failed") + Timber.tag("ChatDebug").d("Message delivery failed") _uiState.update { prev -> prev.copy(offlineWarning = "Message delivery failed. Device may be offline.") } // Force device status verification DeviceStatusManager.verifyDeviceStatus(ipAddress) } else { - Log.d("ChatDebug", "Message delivered successfully") + Timber.tag("ChatDebug").d("Message delivered successfully") } } catch (e: Exception) { - Log.e("ChatScreenViewModel", "Error sending message: ${e.message}", e) + Timber.tag("ChatScreenViewModel").e(e, "Error sending message: ${e.message}") _uiState.update { prev -> prev.copy(offlineWarning = "Error sending message: ${e.message}") } } } else { - Log.d("ChatScreenViewModel", "Device $ipAddress appears to be offline, message saved locally only") + Timber.tag("ChatScreenViewModel").d("Device $ipAddress appears to be offline, " + + "message saved locally only") _uiState.update { prev -> prev.copy(offlineWarning = "Device appears to be offline. Message saved locally only.") } diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/ui/viewmodels/ConversationsHomeScreenViewModel.kt b/app/src/main/java/com/greybox/projectmesh/messaging/ui/viewmodels/ConversationsHomeScreenViewModel.kt index 9fd2933e..0069fdae 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/ui/viewmodels/ConversationsHomeScreenViewModel.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/ui/viewmodels/ConversationsHomeScreenViewModel.kt @@ -1,11 +1,9 @@ package com.greybox.projectmesh.messaging.ui.viewmodels import android.content.SharedPreferences -import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.greybox.projectmesh.DeviceStatusManager -import com.greybox.projectmesh.GlobalApp import com.greybox.projectmesh.messaging.repository.ConversationRepository import com.greybox.projectmesh.messaging.ui.models.ConversationsHomeScreenModel import kotlinx.coroutines.Dispatchers @@ -16,6 +14,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.kodein.di.DI import org.kodein.di.instance +import timber.log.Timber class ConversationsHomeScreenViewModel( di: DI @@ -74,7 +73,7 @@ class ConversationsHomeScreenViewModel( else null ) - Log.d("ConversationsViewModel", + Timber.tag("ConversationsViewModel").d( "Updated conversation status for ${currentConversations.find { it.userUuid == userUuid }?.userName}: online=$isOnline") } @@ -83,7 +82,7 @@ class ConversationsHomeScreenViewModel( refreshConversations() } } catch (e: Exception) { - Log.e("ConversationsViewModel", "Error updating conversation statuses", e) + Timber.tag("ConversationsViewModel").e(e,"Error updating conversation statuses") } } } @@ -97,11 +96,12 @@ class ConversationsHomeScreenViewModel( //get local device id val localUuid = sharedPrefs.getString("UUID", null) ?: "local-user" - Log.d("ConversationsViewModel", "Local UUID: $localUuid") + Timber.tag("ConversationsViewModel").d("Local UUID: $localUuid") //collect conversations from repository conversationRepository.getAllConversations().collect { conversations -> - Log.d("ConversationsViewModel", "Loaded ${conversations.size} conversations") + Timber.tag("ConversationsViewModel").d("Loaded ${conversations.size} " + + "conversations") //filter out conversations with self val filteredConversations = conversations.filter { conversation -> @@ -109,12 +109,13 @@ class ConversationsHomeScreenViewModel( } conversations.forEach { conversation -> - Log.d("ConversationsViewModel", "Conversation: ID=${conversation.id}, UserUUID=${conversation.userUuid}, Name=${conversation.userName}") + Timber.tag("ConversationsViewModel").d("Conversation: ID=${conversation + .id}, UserUUID=${conversation.userUuid}, Name=${conversation.userName}") } - Log.d("ConversationsViewModel", "Filtering out conversations with UUID: $localUuid") + Timber.tag("ConversationsViewModel").d("Filtering out conversations with UUID:$localUuid") - Log.d("ConversationsViewModel", "After filtering self: ${filteredConversations.size} conversations") + Timber.tag("ConversationsViewModel").d("After filtering self:${filteredConversations.size} conversations") //update the UI state with the conversations @@ -127,7 +128,7 @@ class ConversationsHomeScreenViewModel( } } }catch (e: Exception) { - Log.e("ConversationsViewModel", "Error loading conversations", e) + Timber.tag("ConversationsViewModel").e(e,"Error loading conversations") _uiState.update { it.copy ( @@ -150,7 +151,7 @@ class ConversationsHomeScreenViewModel( try { conversationRepository.markAsRead(conversationId) } catch (e: Exception) { - Log.e("ConversationsViewModel", "Error marking conversation as read", e) + Timber.tag("ConversationsViewModel").e(e, "Error marking conversation as read") } } } diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/utils/Logger.kt b/app/src/main/java/com/greybox/projectmesh/messaging/utils/Logger.kt index 9000bc11..804c5174 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/utils/Logger.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/utils/Logger.kt @@ -1,6 +1,6 @@ package com.greybox.projectmesh.utils -import android.util.Log +import timber.log.Timber /** * Centralized logging utility for the app. @@ -13,28 +13,28 @@ object Logger { fun d(tag: String, message: String) { if (LOGGING_ENABLED) { - Log.d("$TAG_PREFIX$tag", message) + Timber.tag("$TAG_PREFIX$tag").d(message) } } fun i(tag: String, message: String) { if (LOGGING_ENABLED) { - Log.i("$TAG_PREFIX$tag", message) + Timber.tag("$TAG_PREFIX$tag").i(message) } } fun w(tag: String, message: String) { if (LOGGING_ENABLED) { - Log.w("$TAG_PREFIX$tag", message) + Timber.tag("$TAG_PREFIX$tag").w(message) } } fun e(tag: String, message: String, throwable: Throwable? = null) { if (LOGGING_ENABLED) { if (throwable != null) { - Log.e("$TAG_PREFIX$tag", message, throwable) + Timber.tag("$TAG_PREFIX$tag").e(throwable, message) } else { - Log.e("$TAG_PREFIX$tag", message) + Timber.tag("$TAG_PREFIX$tag").e(message) } } } @@ -42,9 +42,9 @@ object Logger { // Log important events that should be visible even in production fun critical(tag: String, message: String, throwable: Throwable? = null) { if (throwable != null) { - Log.e("$TAG_PREFIX${tag}_CRITICAL", message, throwable) + Timber.tag("$TAG_PREFIX${tag}_CRITICAL").e(throwable, message) } else { - Log.e("$TAG_PREFIX${tag}_CRITICAL", message) + Timber.tag("$TAG_PREFIX${tag}_CRITICAL").e(message) } } } \ No newline at end of file diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/utils/MessageMigrationUtils.kt b/app/src/main/java/com/greybox/projectmesh/messaging/utils/MessageMigrationUtils.kt index a2bb342f..708df0ab 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/utils/MessageMigrationUtils.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/utils/MessageMigrationUtils.kt @@ -1,8 +1,6 @@ //script to help migrate existing messages package com.greybox.projectmesh.messaging.utils -import android.content.Context -import android.util.Log import com.greybox.projectmesh.GlobalApp import com.greybox.projectmesh.db.MeshDatabase import com.greybox.projectmesh.testing.TestDeviceService @@ -11,6 +9,7 @@ import kotlinx.coroutines.withContext import org.kodein.di.DI import org.kodein.di.DIAware import org.kodein.di.instance +import timber.log.Timber class MessageMigrationUtils( override val di: DI @@ -26,7 +25,8 @@ class MessageMigrationUtils( try { //get all messages by id val messages = db.messageDao().getAll() - Log.d("MessageMigration", "Found ${messages.size} messages to check for migration") + Timber.tag("MessageMigration").d("Found ${messages.size} messages to check for " + + "migration") //group messages by their current chat names val messagesByChat = messages.groupBy { it.chat } @@ -35,7 +35,7 @@ class MessageMigrationUtils( messagesByChat.forEach { (chatName, messagesInChat) -> //skip messages that already appear by using convo id format if (chatName.contains("-") && (chatName.count { it == '-' } >= 1)){ - Log.d("MessageMigration", "Chat $chatName already appears to use conversation ID format") + Timber.tag("MessageMigration").d("Chat $chatName already appears to use conversation ID format") return@forEach } @@ -56,10 +56,9 @@ class MessageMigrationUtils( "unknown-${chatName}" } }catch (e: Exception) { - Log.e( - "MessageMigration", - "Error finding user for chat $chatName", - e + Timber.tag( + "MessageMigration").e(e, + "Error finding user for chat $chatName" ) "unknown-${chatName}" } @@ -71,7 +70,7 @@ class MessageMigrationUtils( val newChatName = createConversationId(localUuid, userUuid) if (chatName != newChatName) { - Log.d("MessageMigration", "Migrating ${messagesInChat.size} messages from '$chatName' to '$newChatName'") + Timber.tag("MessageMigration").d("Migrating ${messagesInChat.size} messages from '$chatName' to '$newChatName'") //Create new messages with the updated chat name val updatedMessages = messagesInChat.map { @@ -84,11 +83,12 @@ class MessageMigrationUtils( db.messageDao().addMessage(message) } - Log.d("MessageMigration", "Successfully migrated messages for chat 'chatName'") + Timber.tag("MessageMigration").d("Successfully migrated messages for chat" + + " 'chatName'") } } }catch (e:Exception){ - Log.e("MessageMigration", "Error during message migration", e) + Timber.tag("MessageMigration").e(e,"Error during message migration") } } } diff --git a/app/src/main/java/com/greybox/projectmesh/navigation/BottomNavHost.kt b/app/src/main/java/com/greybox/projectmesh/navigation/BottomNavHost.kt index 959ed943..54e83557 100644 --- a/app/src/main/java/com/greybox/projectmesh/navigation/BottomNavHost.kt +++ b/app/src/main/java/com/greybox/projectmesh/navigation/BottomNavHost.kt @@ -1,10 +1,13 @@ package com.greybox.projectmesh.navigation +import android.content.pm.ApplicationInfo import androidx.compose.runtime.Composable import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController import androidx.compose.material3.* +import androidx.compose.runtime.remember import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow @@ -12,6 +15,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.sp import androidx.navigation.compose.rememberNavController import com.greybox.projectmesh.R +import org.kodein.di.android.BuildConfig data class NavigationItem( val route: String, @@ -29,15 +33,24 @@ fun BottomNavigationBarPreview() { @Composable fun BottomNavigationBar(navController: NavHostController) { - val items = listOf( - NavigationItem(BottomNavItem.Home.route, stringResource(id = R.string.home), BottomNavItem.Home.icon), - NavigationItem(BottomNavItem.Network.route, stringResource(id = R.string.network), BottomNavItem.Network.icon), - NavigationItem(BottomNavItem.Send.route, stringResource(id = R.string.send), BottomNavItem.Send.icon), - NavigationItem(BottomNavItem.Receive.route, stringResource(id = R.string.receive), BottomNavItem.Receive.icon), - NavigationItem(BottomNavItem.Chat.route, stringResource(id=R.string.chat), BottomNavItem.Chat.icon), - NavigationItem(BottomNavItem.Log.route, stringResource(id = R.string.log), BottomNavItem.Log.icon), - NavigationItem(BottomNavItem.Settings.route, stringResource(id = R.string.settings), BottomNavItem.Settings.icon) - ) + val context = LocalContext.current + val isDebuggable = remember { + (context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0 + } + val items = buildList { + add(NavigationItem(BottomNavItem.Home.route, stringResource(id = R.string.home), BottomNavItem.Home.icon)) + add(NavigationItem(BottomNavItem.Network.route, stringResource(id = R.string.network), BottomNavItem.Network.icon)) + add(NavigationItem(BottomNavItem.Send.route, stringResource(id = R.string.send), BottomNavItem.Send.icon)) + add(NavigationItem(BottomNavItem.Receive.route, stringResource(id = R.string.receive), BottomNavItem.Receive.icon)) + add(NavigationItem(BottomNavItem.Chat.route, stringResource(id = R.string.chat), BottomNavItem.Chat.icon)) + + // ✅ Only add the log tab if it's a DEBUG build + if (isDebuggable) { + add(NavigationItem(BottomNavItem.Log.route, stringResource(id = R.string.log), BottomNavItem.Log.icon)) + } + + add(NavigationItem(BottomNavItem.Settings.route, stringResource(id = R.string.settings), BottomNavItem.Settings.icon)) + } NavigationBar { val currentRoute = navController.currentDestination?.route items.forEach { item -> @@ -47,7 +60,7 @@ fun BottomNavigationBar(navController: NavHostController) { label = { Text( item.label, - fontSize = 7.5.sp, + fontSize = 10.5.sp, maxLines = 1, softWrap = false, overflow = TextOverflow.Clip @@ -66,7 +79,7 @@ fun BottomNavigationBar(navController: NavHostController) { launchSingleTop = true } }, - + ) } } diff --git a/app/src/main/java/com/greybox/projectmesh/server/AppServer.kt b/app/src/main/java/com/greybox/projectmesh/server/AppServer.kt index b2f20ffa..b6a03d12 100644 --- a/app/src/main/java/com/greybox/projectmesh/server/AppServer.kt +++ b/app/src/main/java/com/greybox/projectmesh/server/AppServer.kt @@ -7,7 +7,6 @@ import android.content.Intent import android.content.SharedPreferences import android.net.Uri import android.os.Build -import android.se.omapi.Session import android.util.Log import androidx.core.app.NotificationCompat import com.google.gson.Gson @@ -58,17 +57,12 @@ import okhttp3.HttpUrl import okhttp3.MediaType.Companion.toMediaType import java.net.URLDecoder import kotlinx.coroutines.runBlocking -import okhttp3.Call -import okhttp3.Callback import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.RequestBody -//import okhttp3.RequestBody.Companion.toRequestBody -import okhttp3.Response -import java.io.IOException import java.net.URI import okhttp3.RequestBody.Companion.toRequestBody import com.greybox.projectmesh.util.NotificationHelper import com.ustadmobile.meshrabiya.log.MNetLogger +import timber.log.Timber /* This File is the Server for transferring files @@ -120,9 +114,9 @@ class AppServer( // Restart method to stop and start the server with an optional new IP address fun restart() { stop() // Stop the server using NanoHTTPD's built-in stop method - Log.d("AppServer", "Server stopped successfully") + Timber.d("AppServer", "Server stopped successfully") start(SOCKET_READ_TIMEOUT, false) // Start the server using NanoHTTPD's built-in start method - Log.d("AppServer", "Server restarted successfully on port: $localPort") + Timber.d("AppServer", "Server restarted successfully on port: $localPort") } //change to json @@ -421,7 +415,8 @@ class AppServer( routeToChatEnabled = true chatConversationId = conversation.id } catch (e: Exception) { - Log.e("AppServer", "Failed to find conversation for file sender", e) + Timber.tag("AppServer").e(e, "Failed to find conversation for file " + + "sender") } } @@ -469,34 +464,34 @@ class AppServer( return newFixedLengthResponse("OK") } else if(path.startsWith("/getDeviceName")){ - Log.d("AppServer", "local ip address: ${localVirtualAddr.hostAddress}") + Timber.tag("AppServer").d("local ip address: ${localVirtualAddr.hostAddress}") val settingPref: SharedPreferences by di.instance(tag="settings") return newFixedLengthResponse(settingPref.getString("device_name", Build.MODEL) ?: Build.MODEL) } else if(path.startsWith("/chat")) { - //Log.d("AppServer", "Received chat message*******") + //Timber.d("AppServer", "Received chat message*******") //Processes JSON payload into useable data val files = mutableMapOf() session.parseBody(files) val jsonPayload = files["postData"] // This is where the full body lives - //Log.d("AppServer", "JSON Payload******: $jsonPayload") + //Timber.d("AppServer", "JSON Payload******: $jsonPayload") //Checks if payload is null/blank if (jsonPayload.isNullOrBlank()) { - Log.e("AppServer", "Empty or missing JSON payload") + Timber.tag("AppServer").e("Empty or missing JSON payload") return newFixedLengthResponse(Response.Status.BAD_REQUEST, "text/plain", "Empty or missing JSON payload") }else{ - Log.d("AppServer", "JSON payload not blank") + Timber.tag("AppServer").d("JSON payload not blank") } //Validates validity of JSON payload val JSONschema = JSONSchema() - Log.d("AppServer", "Validating JSON payload: ${jsonPayload::class.simpleName}") + Timber.d("AppServer", "Validating JSON payload: ${jsonPayload::class.simpleName}") if (!JSONschema.schemaValidation(jsonPayload)) { - Log.e("AppServer", "Invalid JSON payload") + Timber.e("AppServer", "Invalid JSON payload") return newFixedLengthResponse(Response.Status.BAD_REQUEST, "application/json", """{"error":"Invalid JSON schema"}""") }else{ - Log.d("AppServer", "Valid JSON payload") + Timber.d("AppServer", "Valid JSON payload") } //Deserialize the JSON payload @@ -506,7 +501,9 @@ class AppServer( val time = deserialzedJSON.dateReceived ?: System.currentTimeMillis() val senderIpStr = deserialzedJSON.sender ?: null - Log.d("AppServer", "Received chat message: '$chatMessage' from $senderIpStr at $time") + Timber.tag("AppServer").d("Received chat message: '$chatMessage' from $senderIpStr at" + + " " + + "$time") // val chatMessage = session.parameters["chatMessage"]?.firstOrNull() // val timeParam = session.parameters["time"]?.firstOrNull() // val time = timeParam?.toLongOrNull() ?: System.currentTimeMillis() @@ -519,7 +516,7 @@ class AppServer( val senderIp = try { InetAddress.getByName(senderIpStr) } catch (e: Exception) { - Log.e("AppServer", "Invalid sender IP address: $senderIpStr", e) + Timber.tag("AppServer").e(e,"Invalid sender IP address: $senderIpStr") return newFixedLengthResponse(Response.Status.BAD_REQUEST, "text/plain", "Invalid sender IP") } @@ -530,14 +527,14 @@ class AppServer( try { URI.create(fileUriStr) } catch (e: Exception) { - Log.e("AppServer", "Invalid file URI: $fileUriStr", e) + Timber.tag("AppServer").e(e,"Invalid file URI: $fileUriStr") null } } else { null } - Log.d("AppServer", "Received chat message: '$chatMessage' from $senderIpStr") + Timber.tag("AppServer").d("Received chat message: '$chatMessage' from $senderIpStr") try { val message = MessageNetworkHandler.handleIncomingMessage( @@ -549,13 +546,13 @@ class AppServer( scope.launch { db.messageDao().addMessage(message) - Log.d("AppServer", "Message saved to database: $message") + Timber.tag("AppServer").d("Message saved to database: $message") } // Change response type to ensure it's properly formatted return newFixedLengthResponse(Response.Status.OK, "text/plain", "OK") } catch (e: Exception) { - Log.e("AppServer", "Error processing chat message", e) + Timber.e("AppServer", "Error processing chat message", e) return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, "text/plain", "Error processing message") } @@ -573,18 +570,18 @@ class AppServer( scope.launch { try { val ipStr = wifiAddress.hostAddress - Log.d("AppServer", "wifiAddress: $ipStr") + Timber.tag("AppServer").d("wifiAddress: $ipStr") // GET /getDeviceName val uri = "http://$ipStr:$port/getDeviceName" val request = Request.Builder().url(uri).build() - Log.d("AppServer", "Request: $request") + Timber.tag("AppServer").d("Request: $request") val response = httpClient.newCall(request).execute() - Log.d("AppServer", "Response: $response") + Timber.tag("AppServer").d("Response: $response") // The remote device's name val remoteDeviceName = response.body?.string() - Log.d("AppServer", "Remote device name: $remoteDeviceName") + Timber.tag("AppServer").d("Remote device name: $remoteDeviceName") response.close() // best practice: close the response @@ -612,7 +609,8 @@ class AppServer( } catch (e: Exception) { e.printStackTrace() - Log.e("AppServer", "Failed to get device name from ${wifiAddress.hostAddress}") + Timber.tag("AppServer").e("Failed to get device name from ${wifiAddress + .hostAddress}") } } } @@ -633,7 +631,7 @@ class AppServer( // Possibly store lastSeen, remote IP, etc., if your entity includes those fields } } catch (e: Exception) { - Log.e("AppServer", "Failed to fetch /myinfo from $remoteAddr", e) + Timber.e("AppServer", "Failed to fetch /myinfo from $remoteAddr", e) } } }*/ @@ -675,9 +673,9 @@ class AppServer( val notificationManager = appContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.notify(1005, notification) - Log.d("AppServer", "Showed notification for file in chat") + Timber.d("AppServer", "Showed notification for file in chat") } catch (e: Exception) { - Log.e("AppServer", "Failed to show file in chat notification", e) + Timber.e("AppServer", "Failed to show file in chat notification", e) // Fall back to regular notification NotificationHelper.showFileReceivedNotification(appContext, transfer.name) } @@ -867,7 +865,8 @@ class AppServer( return response.isSuccessful } } catch (e: Exception) { - Log.e("AppServer", "Failed to check if device ${remoteAddr.hostAddress} is reachable", e) + Timber.tag("AppServer").e(e, "Failed to check if device ${remoteAddr.hostAddress} is " + + "reachable") return false } } @@ -967,7 +966,7 @@ class AppServer( // Store the echo response in our database db.messageDao().addMessage(testMessage) - Log.d("AppServer", "Test device echoed message: $message") + Timber.tag("AppServer").d("Test device echoed message: $message") return true } @@ -982,7 +981,7 @@ class AppServer( .addQueryParameter("senderIp", localVirtualAddr.hostAddress) .build() - Log.d("AppServer", "Request URL: $httpUrl") + Timber.tag("AppServer").d("Request URL: $httpUrl") val gs = Gson() val msg = Message(//test this @@ -993,15 +992,15 @@ class AppServer( content = message, file = null//made the file null so that the file doesn't send ) - Log.d("AppServer", "Messagefile: ${msg.file.toString()}") + Timber.d("AppServer", "Messagefile: ${msg.file.toString()}") val msgJson = gs.toJson(msg) //modified http val httpURL = HttpUrl.Builder() .scheme("http") .host(address.hostAddress) .port(DEFAULT_PORT) .addPathSegment("chat").build() - //Log.d("AppServer", "HTTP URL: $httpURL") + //Timber.d("AppServer", "HTTP URL: $httpURL") //post request body val mt = "application/json; charset=utf-8".toMediaType() val rbody = msgJson.toRequestBody(mt) - //Log.d("Appserver", "HTTP URL: $httpUrl") + //Timber.d("Appserver", "HTTP URL: $httpUrl") val request = Request.Builder() .url(httpUrl) @@ -1014,20 +1013,23 @@ class AppServer( httpClient.newCall(request).execute().use { response -> val successful = response.isSuccessful if (successful) { - Log.d("AppServer", "Message successfully sent to ${address.hostAddress}") + Timber.tag("AppServer").d("Message successfully sent to ${address + .hostAddress}") } else { - Log.d("AppServer", "Failed to send message to ${address.hostAddress}, status code: ${response.code}") + Timber.tag("AppServer").d("Failed to send message to ${address + .hostAddress}, status code: ${response.code}") } return successful } } catch (e: Exception) { - Log.e("AppServer", "Failed to send message to ${address.hostAddress}: ${e.message}", e) + Timber.tag("AppServer").e(e,"Failed to send message to ${address.hostAddress}: ${e + .message}") return false } } catch (e: Exception) { e.printStackTrace() - Log.d("AppServer", "Failed to send message to ${address.hostAddress}: ${e.message}") + Timber.tag("AppServer").d("Failed to send message to ${address.hostAddress}: ${e.message}") return false } } @@ -1038,7 +1040,7 @@ class AppServer( scope.launch {//check to see if this accommodates for json //not sending URI, i don't think the json is working try { - Log.d("AppServer", "chat message: $message") + Timber.tag("AppServer").d("chat message: $message") if (TestDeviceService.isTestDevice(address)) { // Create an echo response from our test device val testMessage = Message( @@ -1052,7 +1054,7 @@ class AppServer( // Store the echo response in our database db.messageDao().addMessage(testMessage) - Log.d("AppServer", "Test device echoed message: $message") + Timber.tag("AppServer").d("Test device echoed message: $message") return@launch } @@ -1067,19 +1069,19 @@ class AppServer( .addQueryParameter("senderIp", localVirtualAddr.hostAddress) .build() - Log.d("AppServer", "Request URL: $httpUrl") + Timber.tag("AppServer").d("Request URL: $httpUrl") val request = Request.Builder() .url(httpUrl) .build() val response = httpClient.newCall(request).execute() - Log.d("AppServer", "Response: ${response.code}") + Timber.tag("AppServer").d("Response: ${response.code}") } catch (e: Exception) { e.printStackTrace() - Log.d("AppServer", "Failed to send message to ${address.hostAddress}") + Timber.tag("AppServer").d("Failed to send message to ${address.hostAddress}") } } } @@ -1091,12 +1093,12 @@ class AppServer( val sharedPrefs: SharedPreferences by di.instance(tag = "settings") val localUuid = sharedPrefs.getString("UUID", null) if (localUuid == null) { - Log.e("AppServer", "Local UUID not found, cannot push user info") + Timber.tag("AppServer").e("Local UUID not found, cannot push user info") return@launch } val localUser = runBlocking { userRepository.getUser(localUuid) } if (localUser == null) { - Log.e("AppServer", "Local user info not found in DB, cannot push user info") + Timber.tag("AppServer").e("Local user info not found in DB, cannot push user info") return@launch } @@ -1109,15 +1111,17 @@ class AppServer( .post(requestBody) .build() - Log.d("AppServer", "Pushing user info to $url with payload: $userJson") + Timber.tag("AppServer").d("Pushing user info to $url with payload: $userJson") try { val response = httpClient.newCall(request).execute() - // Log the response code and body (if any) + // Timber the response code and body (if any) val responseBody = response.body?.string() - Log.d("AppServer", "Response from ${remoteAddr.hostAddress}: Code=${response.code}, Body=$responseBody") + Timber.tag("AppServer").d("Response from ${remoteAddr.hostAddress}: Code=${response + .code}, Body=$responseBody") } catch (e: Exception) { - Log.e("AppServer", "Failed to push user info to ${remoteAddr.hostAddress}", e) + Timber.tag("AppServer").e(e,"Failed to push user info to ${remoteAddr + .hostAddress}") } } } @@ -1140,11 +1144,12 @@ class AppServer( try { val url = "http://${remoteAddr.hostAddress}:$port/myinfo" val request = Request.Builder().url(url).build() - Log.d("AppServer", "Requesting remote user info from $url") + Timber.tag("AppServer").d("Requesting remote user info from $url") val response = httpClient.newCall(request).execute() val userJson = response.body?.string() - Log.d("AppServer", "Received user info from ${remoteAddr.hostAddress}: $userJson") + Timber.tag("AppServer").d("Received user info from ${remoteAddr.hostAddress}: " + + "$userJson") response.close() if (!userJson.isNullOrEmpty()) { @@ -1185,10 +1190,11 @@ class AppServer( localUuid = localUuid, remoteUser = remoteUserWithIp ) - Log.d("AppServer", "Created conversation with ${remoteUserWithIp.name}") + Timber.tag("AppServer").d("Created conversation with ${remoteUserWithIp + .name}") } }catch (e: Exception) { - Log.e("AppServer", "Failed to create conversation", e) + Timber.tag("AppServer").e(e, "Failed to create conversation") //Update Device Status Manager to show device is offline when connection fails DeviceStatusManager.updateDeviceStatus(remoteAddr.hostAddress, false) @@ -1204,10 +1210,11 @@ class AppServer( } } - Log.d("AppServer", "Updated local DB with remote user info: $remoteUserWithIp") + Timber.tag("AppServer").d("Updated local DB with remote user info: " + + "$remoteUserWithIp") } } catch (e: Exception) { - Log.e("AppServer", "Failed to fetch /myinfo from ${remoteAddr.hostAddress}", e) + Timber.tag("AppServer").e(e,"Failed to fetch /myinfo from ${remoteAddr.hostAddress}") } } } @@ -1220,9 +1227,10 @@ class AppServer( isOnline = isOnline, userAddress = userAddress ) - Log.d("AppServer", "Updated user $userUuid connection status: online=$isOnline, address=$userAddress") + Timber.tag("AppServer").d("Updated user $userUuid connection status: " + + "online=$isOnline, address=$userAddress") }catch (e: Exception){ - Log.e("AppServer", "Failed to update user connection status", e) + Timber.tag("AppServer").e(e,"Failed to update user connection status") } } } @@ -1235,9 +1243,10 @@ class AppServer( isOnline = isOnline, userAddress = userAddress ) - Log.d("AppServer", "Updated user $userUuid connection status: online=$isOnline, address=$userAddress") + Timber.tag("AppServer").d("Updated user $userUuid connection status: " + + "online=$isOnline, address=$userAddress") } catch (e: Exception){ - Log.e("AppServer", "Failed to update user connection status", e) + Timber.tag("AppServer").e(e,"Failed to update user connection status") } } } @@ -1253,9 +1262,9 @@ class AppServer( userAddress = null ) } - Log.d("AppServer", "Marked all users as offline") + Timber.tag("AppServer").d("Marked all users as offline") } catch (e: Exception) { - Log.e("AppServer", "Failed to mark all users as offline", e) + Timber.tag("AppServer").e(e,"Failed to mark all users as offline") } } } diff --git a/app/src/main/java/com/greybox/projectmesh/testing/TestDeviceEntry.kt b/app/src/main/java/com/greybox/projectmesh/testing/TestDeviceEntry.kt index d78b4a0c..2f6c9a07 100644 --- a/app/src/main/java/com/greybox/projectmesh/testing/TestDeviceEntry.kt +++ b/app/src/main/java/com/greybox/projectmesh/testing/TestDeviceEntry.kt @@ -1,10 +1,9 @@ package com.greybox.projectmesh.testing -import android.util.Log -import com.ustadmobile.meshrabiya.log.MNetLogger import com.ustadmobile.meshrabiya.mmcp.MmcpOriginatorMessage import com.ustadmobile.meshrabiya.vnet.VirtualNode import com.ustadmobile.meshrabiya.vnet.VirtualNodeDatagramSocket +import timber.log.Timber import java.net.DatagramSocket import java.net.InetAddress import java.util.concurrent.Executors @@ -29,8 +28,9 @@ class TestDeviceEntry { acc or ((byte.toInt() and 0xFF) shl (24 - (index * 8))) } - Log.d("TestDeviceEntry", "Creating test entry with IP: ${TestDeviceService.TEST_DEVICE_IP}") - Log.d("TestDeviceEntry", "Test address as int: $testAddressInt") + Timber.tag("TestDeviceEntry").d("Creating test entry with IP: ${TestDeviceService + .TEST_DEVICE_IP}") + Timber.tag("TestDeviceEntry").d("Test address as int: $testAddressInt") //create basic MmcpOriginatorMessage @@ -66,7 +66,7 @@ class TestDeviceEntry { return Pair(testAddressInt, lastOriginatorMessage) } catch (e: Exception) { - Log.e("TestDeviceEntry", "Error creating test entry", e) + Timber.tag("TestDeviceEntry").e(e,"Error creating test entry") throw e } } diff --git a/app/src/main/java/com/greybox/projectmesh/testing/TestDeviceService.kt b/app/src/main/java/com/greybox/projectmesh/testing/TestDeviceService.kt index 34754beb..d5447c0d 100644 --- a/app/src/main/java/com/greybox/projectmesh/testing/TestDeviceService.kt +++ b/app/src/main/java/com/greybox/projectmesh/testing/TestDeviceService.kt @@ -1,10 +1,9 @@ package com.greybox.projectmesh.testing -import android.util.Log -import com.greybox.projectmesh.GlobalApp import com.greybox.projectmesh.GlobalApp.GlobalUserRepo.userRepository import com.greybox.projectmesh.messaging.data.entities.Message import kotlinx.coroutines.runBlocking +import timber.log.Timber import java.net.InetAddress class TestDeviceService { @@ -41,13 +40,14 @@ class TestDeviceService { } } isInitialized = true - Log.d("TestDeviceService", "Test device initialized successfully with IP: $TEST_DEVICE_IP") + Timber.tag("TestDeviceService").d("Test device initialized successfully with " + + "IP: $TEST_DEVICE_IP") //initialize offline test device initializeOfflineDevice() } } catch (e: Exception) { - Log.e("TestDeviceService", "Failed to initialize test device", e) + Timber.tag("TestDeviceService").e(e,"Failed to initialize test device") } } @@ -74,10 +74,10 @@ class TestDeviceService { } } offlineDeviceInitialized = true - Log.d("TestDeviceService", "Offline test device initialized successfully") + Timber.tag("TestDeviceService").d("Offline test device initialized successfully") } } catch (e: Exception) { - Log.e("TestDeviceService", "Failed to initialize offline test device", e) + Timber.tag("TestDeviceService").e(e, "Failed to initialize offline test device") } } diff --git a/app/src/main/java/com/greybox/projectmesh/testing/TestMNetLogger.kt b/app/src/main/java/com/greybox/projectmesh/testing/TestMNetLogger.kt index 2aabc3be..b52a7bd0 100644 --- a/app/src/main/java/com/greybox/projectmesh/testing/TestMNetLogger.kt +++ b/app/src/main/java/com/greybox/projectmesh/testing/TestMNetLogger.kt @@ -1,5 +1,6 @@ package com.greybox.projectmesh.testing + import android.util.Log import com.ustadmobile.meshrabiya.log.MNetLogger diff --git a/app/src/main/java/com/greybox/projectmesh/viewModel/HomeScreenViewModel.kt b/app/src/main/java/com/greybox/projectmesh/viewModel/HomeScreenViewModel.kt index 541a5709..2c9b4a94 100644 --- a/app/src/main/java/com/greybox/projectmesh/viewModel/HomeScreenViewModel.kt +++ b/app/src/main/java/com/greybox/projectmesh/viewModel/HomeScreenViewModel.kt @@ -2,7 +2,6 @@ package com.greybox.projectmesh.viewModel import android.content.SharedPreferences import android.os.Build -import android.util.Log import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -22,6 +21,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeoutOrNull import org.kodein.di.DI import org.kodein.di.instance +import timber.log.Timber data class HomeScreenModel( @@ -144,7 +144,7 @@ class HomeScreenViewModel(di: DI, viewModelScope.launch { val wasWifiConnected = _uiState.value.wifiState?.wifiStationState?.status try { - Log.d("HotspotDebug", "Attempt 1: Setting Hotspot to $enable") + Timber.tag("HotspotDebug").d("Attempt 1: Setting Hotspot to $enable") val response = withTimeoutOrNull(1500) { node.setWifiHotspotEnabled( enabled = enable, @@ -153,17 +153,17 @@ class HomeScreenViewModel(di: DI, ) } if (response == null) { - Log.w("HotspotDebug", "No response within 1500ms, Retrying...") + Timber.tag("HotspotDebug").w("No response within 1500ms, Retrying...") // Retry once val retryResponse = node.setWifiHotspotEnabled( enabled = enable, preferredBand = _uiState.value.band, hotspotType = _uiState.value.hotspotTypeToCreate ) - Log.d("HotspotDebug", "Retry successful: $retryResponse") + Timber.tag("HotspotDebug").d("Retry successful: $retryResponse") } else { - Log.d("HotspotDebug", "Hotspot set successfully: $response") + Timber.tag("HotspotDebug").d("Hotspot set successfully: $response") } if (!_concurrencyKnown.value && Build.VERSION.SDK_INT < Build.VERSION_CODES.R){ delay(500) @@ -172,16 +172,17 @@ class HomeScreenViewModel(di: DI, { if(isWifiStillConnected == WifiStationState.Status.INACTIVE || isWifiStillConnected == null){ markStaApConcurrencyUnsupported() - Log.d("HotspotDebug", "Wi-Fi disconnected after enabling hotspot. STA/AP concurrency NOT supported.") + Timber.tag("HotspotDebug").d("Wi-Fi disconnected after enabling hotspot. STA/AP concurrency NOT supported.") } else{ markStaApConcurrencySupported() - Log.d("HotspotDebug", "Wi-Fi still connected after enabling hotspot. STA/AP concurrency supported.") + Timber.tag("HotspotDebug").d("Wi-Fi still connected after enabling " + + "hotspot. STA/AP concurrency supported.") } } } } catch (e: Exception) { - Log.e("HotspotDebug", "Failed to set hotspot: ${e.message}") + Timber.tag("HotspotDebug").e("Failed to set hotspot: ${e.message}") } } } @@ -208,7 +209,7 @@ class HomeScreenViewModel(di: DI, } } catch (e: Exception){ - Log.e("HomeScreenViewModel", "onConnectWifi: ${e.message}") + Timber.tag("HomeScreenViewModel").e("onConnectWifi: ${e.message}") } } } diff --git a/app/src/main/java/com/greybox/projectmesh/viewModel/LogScreenViewModel.kt b/app/src/main/java/com/greybox/projectmesh/viewModel/LogScreenViewModel.kt index bc1a261f..56016888 100644 --- a/app/src/main/java/com/greybox/projectmesh/viewModel/LogScreenViewModel.kt +++ b/app/src/main/java/com/greybox/projectmesh/viewModel/LogScreenViewModel.kt @@ -8,7 +8,6 @@ import com.ustadmobile.meshrabiya.log.LogLine import com.ustadmobile.meshrabiya.log.MNetLogger import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch diff --git a/app/src/main/java/com/greybox/projectmesh/viewModel/NetworkScreenViewModel.kt b/app/src/main/java/com/greybox/projectmesh/viewModel/NetworkScreenViewModel.kt index 2c64e112..b6006867 100644 --- a/app/src/main/java/com/greybox/projectmesh/viewModel/NetworkScreenViewModel.kt +++ b/app/src/main/java/com/greybox/projectmesh/viewModel/NetworkScreenViewModel.kt @@ -1,6 +1,5 @@ package com.greybox.projectmesh.viewModel -import android.util.Log import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -8,7 +7,6 @@ import com.greybox.projectmesh.DeviceStatusManager import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.delay import org.kodein.di.DI import com.greybox.projectmesh.server.AppServer import com.ustadmobile.meshrabiya.ext.addressToByteArray @@ -19,10 +17,8 @@ import com.greybox.projectmesh.testing.TestDeviceEntry import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.kodein.di.instance +import timber.log.Timber import java.net.InetAddress -import com.greybox.projectmesh.GlobalApp -import com.greybox.projectmesh.testing.TestDeviceService -import kotlinx.coroutines.withTimeoutOrNull data class NetworkScreenModel( val connectingInProgressSsid: String? = null, @@ -56,7 +52,7 @@ class NetworkScreenViewModel(di:DI, savedStateHandle: SavedStateHandle): ViewMod disconnectedNodes.forEach { nodeAddress -> val ipAddress = InetAddress.getByAddress(nodeAddress.addressToByteArray()).hostAddress DeviceStatusManager.handleNetworkDisconnect(ipAddress) - Log.d("NetworkScreenViewModel", "Detected disconnection of node: $ipAddress") + Timber.tag("NetworkScreenViewModel").d("Detected disconnection of node: $ipAddress") } // Update previous nodes for next comparison @@ -66,9 +62,10 @@ class NetworkScreenViewModel(di:DI, savedStateHandle: SavedStateHandle): ViewMod val allNodesWithTest = nodeState.originatorMessages.toMutableMap() allNodesWithTest[testEntry.first] = testEntry.second - Log.d("NetworkScreenViewModel", "Updating nodes. Count: ${allNodesWithTest.size}") - Log.d("NetworkScreenViewModel", "Test device address: ${testEntry.first}") - Log.d("NetworkScreenViewModel", "All nodes: ${allNodesWithTest.keys}") + Timber.tag("NetworkScreenViewModel").d("Updating nodes. Count: ${allNodesWithTest + .size}") + Timber.tag("NetworkScreenViewModel").d("Test device address: ${testEntry.first}") + Timber.tag("NetworkScreenViewModel").d("All nodes: ${allNodesWithTest.keys}") // update the UI state with the new state _uiState.update { prev -> @@ -132,7 +129,9 @@ class NetworkScreenViewModel(di:DI, savedStateHandle: SavedStateHandle): ViewMod //Update central status manager to show device as online DeviceStatusManager.updateDeviceStatus(ipStr, true) - Log.d("NetworkScreenViewModel", "Pinged user: ${user.name} at $ipStr") + Timber.tag("NetworkScreenViewModel").d("Pinged user: ${user + .name} + at $ipStr") } } catch (e: Exception) { //if ping fails, mark user as offline @@ -144,7 +143,8 @@ class NetworkScreenViewModel(di:DI, savedStateHandle: SavedStateHandle): ViewMod //update central status manager to show device as offline DeviceStatusManager.updateDeviceStatus(ipStr, false) - Log.d("NetworkScreenViewModel", "User ${user.name} appears to be offline") + Timber.tag("NetworkScreenViewModel").d("User ${user.name} appears + to be offline") } } } @@ -169,19 +169,20 @@ class NetworkScreenViewModel(di:DI, savedStateHandle: SavedStateHandle): ViewMod } ?: false if (!isReachable) { - Log.d( - "NetworkScreenViewModel", - "Marking device $deviceIp as offline - not reachable" - ) + Timber.tag( + "NetworkScreenViewModel").d( + "Marking device $deviceIp as offline - not reachable") DeviceStatusManager.updateDeviceStatus(deviceIp, false) } }catch (e: Exception){ - Log.d("NetworkScreenViewModel", "Error checking device $deviceIp: ${e.message}") + Timber.tag("NetworkScreenViewModel").d("Error checking device + $deviceIp: + ${e.message}") DeviceStatusManager.updateDeviceStatus(deviceIp, false) } } }catch (e: Exception) { - Log.e("NetworkScreenViewModel", "Error during periodic ping", e) + Timber.tag("NetworkScreenViewModel").e("Error during periodic ping", e) } //wait for 30 secs before next ming delay(30000) diff --git a/app/src/main/java/com/greybox/projectmesh/viewModel/OnboardingViewModel.kt b/app/src/main/java/com/greybox/projectmesh/viewModel/OnboardingViewModel.kt index b8dcd6b5..7e0f3c71 100644 --- a/app/src/main/java/com/greybox/projectmesh/viewModel/OnboardingViewModel.kt +++ b/app/src/main/java/com/greybox/projectmesh/viewModel/OnboardingViewModel.kt @@ -8,7 +8,8 @@ import kotlinx.coroutines.launch import java.util.UUID import com.greybox.projectmesh.user.UserRepository import android.content.SharedPreferences -import org.acra.ACRA.log +import timber.log.Timber + data class OnboardingUiState( val username: String @@ -65,7 +66,7 @@ class OnboardingViewModel( } val generatedUsername = "Guest$nextGuestNumber" - log.d("Username","Username = $generatedUsername") + Timber.tag("Username").d("Username = $generatedUsername") onResult(generatedUsername) } } diff --git a/app/src/main/java/com/greybox/projectmesh/viewModel/PingScreenViewModel.kt b/app/src/main/java/com/greybox/projectmesh/viewModel/PingScreenViewModel.kt index 0bce0d37..0cb87a9f 100644 --- a/app/src/main/java/com/greybox/projectmesh/viewModel/PingScreenViewModel.kt +++ b/app/src/main/java/com/greybox/projectmesh/viewModel/PingScreenViewModel.kt @@ -9,14 +9,9 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import org.kodein.di.DI import com.greybox.projectmesh.server.AppServer -import com.greybox.projectmesh.user.UserRepository -import com.ustadmobile.meshrabiya.ext.addressToByteArray -import com.ustadmobile.meshrabiya.ext.addressToDotNotation import com.ustadmobile.meshrabiya.ext.requireAddressAsInt import com.ustadmobile.meshrabiya.vnet.AndroidVirtualNode import com.ustadmobile.meshrabiya.vnet.VirtualNode -import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.flow.last import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking diff --git a/app/src/main/java/com/greybox/projectmesh/viewModel/SelectDestNodeScreenViewModel.kt b/app/src/main/java/com/greybox/projectmesh/viewModel/SelectDestNodeScreenViewModel.kt index abe576c7..b1b51708 100644 --- a/app/src/main/java/com/greybox/projectmesh/viewModel/SelectDestNodeScreenViewModel.kt +++ b/app/src/main/java/com/greybox/projectmesh/viewModel/SelectDestNodeScreenViewModel.kt @@ -1,7 +1,6 @@ package com.greybox.projectmesh.viewModel import android.net.Uri -import android.util.Log import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -21,6 +20,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.kodein.di.DI import org.kodein.di.instance +import timber.log.Timber import java.net.InetAddress data class SelectDestNodeScreenModel( @@ -63,14 +63,14 @@ class SelectDestNodeScreenViewModel( // convert the ip address to byte array, then convert it to InetAddress object // which can be used to perform network operation val inetAddress = InetAddress.getByAddress(address.addressToByteArray()) - Log.d("uri_track_onClickReveiver", "inetAddress: $inetAddress") + Timber.tag("uri_track_onClickReveiver").d("inetAddress: $inetAddress") // update the ui state to reflect that we are contacting the device _uiState.update { prev -> prev.copy( contactingInProgressDevice = address.addressToDotNotation() ) } - Log.d("uri_track_onClickReveiver", "sendUris: ${sendUris.toString()}") + Timber.tag("uri_track_onClickReveiver").d("sendUris: ${sendUris.toString()}") // Launch a coroutine in the ViewModel Scope viewModelScope.launch { // Switch the coroutine context to Dispatchers.IO for network operations @@ -79,16 +79,18 @@ class SelectDestNodeScreenViewModel( sendUris.map { uri -> async{ try{ - Log.d("uri_track_onClickReveiver_Loop", uri.toString()) + Timber.tag("uri_track_onClickReveiver_Loop").d(uri.toString()) val response = appServer.addOutgoingTransfer( uri = uri, toNode = inetAddress, ) - Log.d("uri_track_onClickReveiver_Loop", "response: ${response.toString()}") + Timber.tag("uri_track_onClickReveiver_Loop").d("response: ${response + .toString()}") true } catch (e: Exception){ - Log.e("AppServer", "Exception attempting to send $uri to destination", e) + Timber.tag("AppServer").e(e, "Exception attempting to send $uri to " + + "destination") false } } @@ -96,7 +98,7 @@ class SelectDestNodeScreenViewModel( } // if any of the transfers were successful, pop back to previous screen if(transfer.any{it}){ - Log.d("uri_track_onClickReveiver", "popBackWhenDone") + Timber.tag("uri_track_onClickReveiver").d("popBackWhenDone") popBackWhenDone() } } diff --git a/app/src/main/java/com/greybox/projectmesh/views/LogScreen.kt b/app/src/main/java/com/greybox/projectmesh/views/LogScreen.kt index df0ed1c2..23b7808a 100644 --- a/app/src/main/java/com/greybox/projectmesh/views/LogScreen.kt +++ b/app/src/main/java/com/greybox/projectmesh/views/LogScreen.kt @@ -1,13 +1,10 @@ package com.greybox.projectmesh.views import android.widget.Toast -import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.clickable import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize @@ -30,7 +27,6 @@ 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.input.pointer.pointerInput import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext diff --git a/app/src/main/java/com/greybox/projectmesh/views/NetworkScreen.kt b/app/src/main/java/com/greybox/projectmesh/views/NetworkScreen.kt index d9ed3baa..6023167f 100644 --- a/app/src/main/java/com/greybox/projectmesh/views/NetworkScreen.kt +++ b/app/src/main/java/com/greybox/projectmesh/views/NetworkScreen.kt @@ -9,7 +9,6 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.platform.LocalSavedStateRegistryOwner import androidx.lifecycle.viewmodel.compose.viewModel import com.greybox.projectmesh.ViewModelFactory -import com.greybox.projectmesh.viewModel.HomeScreenViewModel import com.greybox.projectmesh.viewModel.NetworkScreenModel import com.greybox.projectmesh.viewModel.NetworkScreenViewModel import org.kodein.di.compose.localDI diff --git a/app/src/main/java/com/greybox/projectmesh/views/ReceiveScreen.kt b/app/src/main/java/com/greybox/projectmesh/views/ReceiveScreen.kt index 6fb7712c..2bde0ac9 100644 --- a/app/src/main/java/com/greybox/projectmesh/views/ReceiveScreen.kt +++ b/app/src/main/java/com/greybox/projectmesh/views/ReceiveScreen.kt @@ -9,7 +9,6 @@ import android.os.Build import android.os.Environment import android.provider.DocumentsContract import android.provider.MediaStore -import android.util.Log import android.webkit.MimeTypeMap import android.widget.Toast import androidx.compose.foundation.clickable @@ -56,6 +55,7 @@ import org.kodein.di.instance import java.io.File import androidx.compose.material3.HorizontalDivider import com.greybox.projectmesh.viewModel.ReceiveScreenModel +import timber.log.Timber @Composable fun ReceiveScreen( @@ -96,7 +96,7 @@ fun HandleIncomingTransfers( LaunchedEffect(onAutoFinishChange) { autoFinishEnabled = settingPref.getBoolean("auto_finish", false) - Log.d("ReceiveScreen", "autoFinishEnabled: $autoFinishEnabled") + Timber.tag("ReceiveScreen").d("autoFinishEnabled: $autoFinishEnabled") } LaunchedEffect(autoFinishEnabled, uiState.incomingTransfers) { if (autoFinishEnabled) { diff --git a/app/src/main/java/com/greybox/projectmesh/views/SettingsScreen.kt b/app/src/main/java/com/greybox/projectmesh/views/SettingsScreen.kt index 2ae8e9d2..fcf5816b 100644 --- a/app/src/main/java/com/greybox/projectmesh/views/SettingsScreen.kt +++ b/app/src/main/java/com/greybox/projectmesh/views/SettingsScreen.kt @@ -2,7 +2,6 @@ package com.greybox.projectmesh.views import android.content.Intent import android.content.SharedPreferences -import android.graphics.Paint.Align import android.net.Uri import android.os.Build import android.widget.Toast @@ -55,8 +54,6 @@ import com.greybox.projectmesh.ViewModelFactory import com.greybox.projectmesh.ui.theme.AppTheme import com.greybox.projectmesh.ui.theme.GradientButton import com.greybox.projectmesh.ui.theme.GradientLongButton -import com.greybox.projectmesh.viewModel.HomeScreenViewModel -import com.greybox.projectmesh.viewModel.SendScreenViewModel import com.greybox.projectmesh.viewModel.SettingsScreenViewModel import org.kodein.di.compose.localDI import org.kodein.di.instance