diff --git a/app/.gitignore b/app/.gitignore index 796b96d1..cccc2074 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1 +1,2 @@ /build +./google-services.json \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a4087cb3..dcbe1ea4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,20 +1,22 @@ plugins { id("com.android.application") id("org.jetbrains.kotlin.android") + id("com.google.gms.google-services") + } android { - namespace = "com.nervesparks.iris" + namespace = "com.nervesparks.irisGPT" compileSdk = 35 ndkVersion = "26.1.10909125" defaultConfig { - applicationId = "com.nervesparks.iris" + applicationId = "com.nervesparks.irisGPT" minSdk = 28 targetSdk = 34 - versionCode = 1 - versionName = "1.0" + versionCode = 4 + versionName = "1.2" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { @@ -84,6 +86,7 @@ dependencies { implementation("androidx.navigation:navigation-compose:2.8.5") implementation("androidx.compose.foundation:foundation-layout-android:1.7.6") implementation("androidx.games:games-activity:3.0.5") + implementation("com.google.firebase:firebase-common-ktx:21.0.0") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.2.1") androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") @@ -94,7 +97,7 @@ dependencies { implementation("androidx.core:core-splashscreen:1.0.1") implementation("com.squareup.okhttp3:okhttp:4.10.0") implementation("androidx.datastore:datastore-preferences:1.1.1") - - + implementation(platform("com.google.firebase:firebase-bom:33.8.0")) + implementation("com.google.firebase:firebase-firestore") } \ No newline at end of file diff --git a/app/src/main/java/com/nervesparks/iris/ChatScreen.kt b/app/src/main/java/com/nervesparks/irisGPT/ChatScreen.kt similarity index 94% rename from app/src/main/java/com/nervesparks/iris/ChatScreen.kt rename to app/src/main/java/com/nervesparks/irisGPT/ChatScreen.kt index a771f61a..79c7557d 100644 --- a/app/src/main/java/com/nervesparks/iris/ChatScreen.kt +++ b/app/src/main/java/com/nervesparks/irisGPT/ChatScreen.kt @@ -1,4 +1,4 @@ -package com.nervesparks.iris +package com.nervesparks.irisGPT import android.annotation.SuppressLint import android.app.DownloadManager @@ -53,13 +53,14 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController -import com.nervesparks.iris.ui.AboutScreen -import com.nervesparks.iris.ui.BenchMarkScreen -import com.nervesparks.iris.ui.MainChatScreen -import com.nervesparks.iris.ui.ModelsScreen -import com.nervesparks.iris.ui.ParametersScreen -import com.nervesparks.iris.ui.SearchResultScreen -import com.nervesparks.iris.ui.SettingsScreen +import com.nervesparks.irisGPT.ui.AboutScreen +import com.nervesparks.irisGPT.ui.BenchMarkScreen +import com.nervesparks.irisGPT.ui.MainChatScreen +import com.nervesparks.irisGPT.ui.ModelsScreen +import com.nervesparks.irisGPT.ui.ParametersScreen +import com.nervesparks.irisGPT.ui.ReportScreen +import com.nervesparks.irisGPT.ui.SearchResultScreen +import com.nervesparks.irisGPT.ui.SettingsScreen import java.io.File @@ -71,6 +72,7 @@ enum class ChatScreen(@StringRes val title: Int) { ParamsScreen(title = R.string.parameters_screen_title), AboutScreen(title = R.string.about_screen_title), BenchMarkScreen(title = R.string.benchmark_screen_title), + ReportScreen(title = R.string.report_screen_title) } @@ -291,6 +293,9 @@ fun ChatScreen( }, onBenchMarkScreenButtonClicked = { navController.navigate((ChatScreen.BenchMarkScreen.name)) + }, + onReportScreenButtonClicked = { + navController.navigate((ChatScreen.ReportScreen.name)) } ) @@ -317,6 +322,9 @@ fun ChatScreen( composable(route = ChatScreen.BenchMarkScreen.name){ BenchMarkScreen(viewModel) } + composable(route = ChatScreen.ReportScreen.name){ + ReportScreen(viewModel) + } } } } diff --git a/app/src/main/java/com/nervesparks/iris/Downloadable.kt b/app/src/main/java/com/nervesparks/irisGPT/Downloadable.kt similarity index 99% rename from app/src/main/java/com/nervesparks/iris/Downloadable.kt rename to app/src/main/java/com/nervesparks/irisGPT/Downloadable.kt index 5e7626dc..3e6645dc 100644 --- a/app/src/main/java/com/nervesparks/iris/Downloadable.kt +++ b/app/src/main/java/com/nervesparks/irisGPT/Downloadable.kt @@ -1,4 +1,4 @@ -package com.nervesparks.iris +package com.nervesparks.irisGPT import android.app.DownloadManager import android.net.Uri diff --git a/app/src/main/java/com/nervesparks/iris/MainActivity.kt b/app/src/main/java/com/nervesparks/irisGPT/MainActivity.kt similarity index 99% rename from app/src/main/java/com/nervesparks/iris/MainActivity.kt rename to app/src/main/java/com/nervesparks/irisGPT/MainActivity.kt index ca2f03ee..3d8d0f70 100644 --- a/app/src/main/java/com/nervesparks/iris/MainActivity.kt +++ b/app/src/main/java/com/nervesparks/irisGPT/MainActivity.kt @@ -1,4 +1,4 @@ -package com.nervesparks.iris +package com.nervesparks.irisGPT import android.app.DownloadManager import android.content.ClipboardManager @@ -62,8 +62,8 @@ import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.border import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider -import com.nervesparks.iris.data.UserPreferencesRepository -import com.nervesparks.iris.ui.SettingsBottomSheet +import com.nervesparks.irisGPT.data.UserPreferencesRepository +import com.nervesparks.irisGPT.ui.SettingsBottomSheet class MainViewModelFactory( diff --git a/app/src/main/java/com/nervesparks/iris/MainViewModel.kt b/app/src/main/java/com/nervesparks/irisGPT/MainViewModel.kt similarity index 97% rename from app/src/main/java/com/nervesparks/iris/MainViewModel.kt rename to app/src/main/java/com/nervesparks/irisGPT/MainViewModel.kt index dfbe2de9..c9a083a4 100644 --- a/app/src/main/java/com/nervesparks/iris/MainViewModel.kt +++ b/app/src/main/java/com/nervesparks/irisGPT/MainViewModel.kt @@ -1,4 +1,4 @@ -package com.nervesparks.iris +package com.nervesparks.irisGPT import android.content.Context import android.llama.cpp.LLamaAndroid @@ -14,7 +14,8 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel -import com.nervesparks.iris.data.UserPreferencesRepository +import androidx.lifecycle.viewmodel.compose.viewModel +import com.nervesparks.irisGPT.data.UserPreferencesRepository import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -98,7 +99,7 @@ class MainViewModel(private val llamaAndroid: LLamaAndroid = LLamaAndroid.instan private set var eot_str = "" - + var showLoader = false var refresh by mutableStateOf(false) @@ -253,6 +254,7 @@ class MainViewModel(private val llamaAndroid: LLamaAndroid = LLamaAndroid.instan } fun send() { + showLoader = true val userMessage = removeExtraWhiteSpaces(message) message = "" @@ -278,15 +280,18 @@ class MainViewModel(private val llamaAndroid: LLamaAndroid = LLamaAndroid.instan .collect { response -> // Create a new assistant message with the response if (getIsMarked()) { + showLoader = false addMessage("codeBlock", response) } else { + showLoader = false addMessage("assistant", response) } } } finally { if (!getIsCompleteEOT()) { + showLoader = false trimEOT() } } @@ -487,6 +492,7 @@ class MainViewModel(private val llamaAndroid: LLamaAndroid = LLamaAndroid.instan } fun stop() { + showLoader = false llamaAndroid.stopTextGeneration() } diff --git a/app/src/main/java/com/nervesparks/iris/data/UserPreferencesRespository.kt b/app/src/main/java/com/nervesparks/irisGPT/data/UserPreferencesRespository.kt similarity index 96% rename from app/src/main/java/com/nervesparks/iris/data/UserPreferencesRespository.kt rename to app/src/main/java/com/nervesparks/irisGPT/data/UserPreferencesRespository.kt index 5e38ced1..416ec368 100644 --- a/app/src/main/java/com/nervesparks/iris/data/UserPreferencesRespository.kt +++ b/app/src/main/java/com/nervesparks/irisGPT/data/UserPreferencesRespository.kt @@ -1,4 +1,4 @@ -package com.nervesparks.iris.data +package com.nervesparks.irisGPT.data import android.content.Context diff --git a/app/src/main/java/com/nervesparks/iris/ui/AboutScreen.kt b/app/src/main/java/com/nervesparks/irisGPT/ui/AboutScreen.kt similarity index 90% rename from app/src/main/java/com/nervesparks/iris/ui/AboutScreen.kt rename to app/src/main/java/com/nervesparks/irisGPT/ui/AboutScreen.kt index 5bcc8169..255e1f33 100644 --- a/app/src/main/java/com/nervesparks/iris/ui/AboutScreen.kt +++ b/app/src/main/java/com/nervesparks/irisGPT/ui/AboutScreen.kt @@ -1,4 +1,4 @@ -package com.nervesparks.iris.ui +package com.nervesparks.irisGPT.ui import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -182,5 +182,13 @@ private val faqs = listOf( "\n" + "Go to Settings > Models.\n" + "Select the model you want to delete.\n" + - "Click the Delete button." -) \ No newline at end of file + "Click the Delete button.", + "How do I report inappropriate content?" to "Models sometimes may generate inappropriate content, If you encounter content that seems inappropriate or concerning, you can report it in a few simple steps:\n" + + "\n" + + "Ensure your device is connected to the internet\n"+ + "Press and hold (long press) on the message or generated content you want to report\n"+ + "When the menu appears, select the 'Report Content' button\n"+ + "\n \nOur team reviews all reports to help maintain a safe environment for all users. Thank you for helping us improve our service." +) + + diff --git a/app/src/main/java/com/nervesparks/iris/ui/BenchMarkScreen.kt b/app/src/main/java/com/nervesparks/irisGPT/ui/BenchMarkScreen.kt similarity index 98% rename from app/src/main/java/com/nervesparks/iris/ui/BenchMarkScreen.kt rename to app/src/main/java/com/nervesparks/irisGPT/ui/BenchMarkScreen.kt index 008ac463..84ca1cd3 100644 --- a/app/src/main/java/com/nervesparks/iris/ui/BenchMarkScreen.kt +++ b/app/src/main/java/com/nervesparks/irisGPT/ui/BenchMarkScreen.kt @@ -1,4 +1,4 @@ -package com.nervesparks.iris.ui +package com.nervesparks.irisGPT.ui import android.os.Build import android.widget.Toast @@ -15,7 +15,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import com.nervesparks.iris.MainViewModel +import com.nervesparks.irisGPT.MainViewModel import kotlinx.coroutines.launch data class BenchmarkState( diff --git a/app/src/main/java/com/nervesparks/iris/ui/MainChatScreen.kt b/app/src/main/java/com/nervesparks/irisGPT/ui/MainChatScreen.kt similarity index 87% rename from app/src/main/java/com/nervesparks/iris/ui/MainChatScreen.kt rename to app/src/main/java/com/nervesparks/irisGPT/ui/MainChatScreen.kt index bfe09baf..1c09ad1a 100644 --- a/app/src/main/java/com/nervesparks/iris/ui/MainChatScreen.kt +++ b/app/src/main/java/com/nervesparks/irisGPT/ui/MainChatScreen.kt @@ -1,16 +1,25 @@ -package com.nervesparks.iris.ui +package com.nervesparks.irisGPT.ui import android.app.Activity import android.app.DownloadManager import android.content.ClipboardManager import android.content.Context import android.content.Intent +import android.net.ConnectivityManager +import android.net.NetworkCapabilities import android.net.Uri +import android.os.Build import android.speech.RecognizerIntent +import android.util.Log import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.keyframes +import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.BorderStroke @@ -33,6 +42,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.ime +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -66,7 +76,6 @@ import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.SheetState -import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TextField @@ -107,18 +116,22 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.toSize -import androidx.compose.ui.window.Dialog -import com.nervesparks.iris.Downloadable -import com.nervesparks.iris.LinearGradient -import com.nervesparks.iris.MainViewModel - -import com.nervesparks.iris.R -import com.nervesparks.iris.ui.components.ChatMessageList -import com.nervesparks.iris.ui.components.DownloadModal -import com.nervesparks.iris.ui.components.LoadingModal - +import com.google.firebase.Firebase +import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.firestore.firestore +import com.nervesparks.irisGPT.Downloadable +import com.nervesparks.irisGPT.LinearGradient +import com.nervesparks.irisGPT.MainViewModel +import com.nervesparks.irisGPT.R +import com.nervesparks.irisGPT.ui.components.DownloadModal +import com.nervesparks.irisGPT.ui.components.LoadingModal +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.io.File +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @@ -137,23 +150,25 @@ fun MainChatScreen ( val focusManager = LocalFocusManager.current println("Thread started: ${Thread.currentThread().name}") val Prompts = listOf( - "Explain the strategic turning points of the Battle of Midway during World War II", - "Describe the innovative technologies that are transforming renewable energy production", - "Outline the core consulting services provided by management consulting firms like McKinsey", - "Walk me through a systematic approach to debugging a complex software issue", - "Trace the architectural and cultural evolution of Paris from medieval times to the modern era", - "Highlight the architectural marvels of Paris, from the Eiffel Tower to the hidden gems of Montmartre", - "Recommend a targeted 15-minute daily routine to improve posture and reduce back pain", + "Explain how to develop a consistent reading habit.", + "Write an email to your manager requesting leave for a day.", + "Suggest time management strategies for handling multiple deadlines effectively.", + "Draft a professional LinkedIn message to connect with a recruiter.", + "Suggest ways to practice mindfulness in a busy daily schedule.", + "Recommend a 15-minute daily workout routine to stay fit with a busy schedule.", + "Provide a simple and polite Spanish translation of 'Excuse me, can you help me?' with explanation.", "List the top 5 science fiction novels that have most influenced modern technological thinking", - "Provide a precise Spanish translation of 'Innovation drives progress' with grammatical explanations", - "Analyze the impact of artificial intelligence on global economic and social landscapes in 2024" + "List security tips to protect personal information online.", + "Recommend three books that can improve communication skills." ) val allModelsExist = models.all { model -> model.destination.exists() } val Prompts_Home = listOf( "Explains complex topics simply.", - "Remembers previous inputs.", - "May sometimes be inaccurate." + "May sometimes be inaccurate.", + "Unable to provide current affairs due to no internet connectivity.", + "Long Press on messages to report.", + ) var recognizedText by remember { mutableStateOf("") } val speechRecognizerLauncher = rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) { @@ -506,6 +521,12 @@ fun MainChatScreen ( } } } + item { + Box (modifier = Modifier.padding(start = 10.dp)) { if(viewModel.showLoader){ + DotsTyping() + } } + + } item { Spacer(modifier = Modifier .height(1.dp) @@ -1062,7 +1083,6 @@ fun MessageBottomSheet( .fillMaxWidth() .fillMaxWidth() .padding(vertical = 5.dp) - ) { // Copy Text Button TextButton( @@ -1116,6 +1136,54 @@ fun MessageBottomSheet( ) } + // Report Content Button + TextButton( + colors = ButtonDefaults.buttonColors(Color(0xFF171E2C)), + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + onClick = onClick@{ + if (!isInternetAvailableMain(context)) { + Toast.makeText(context, "Internet connection required", Toast.LENGTH_LONG).show() + return@onClick + } + + val deviceName = Build.MODEL + + // Create report data + val reportData = ReportContent( + message = message, + modelName = viewModel.loadedModelName.value, + deviceName = deviceName, + topP = viewModel.topP, + topK = viewModel.topK, + temperature = viewModel.temp, + chatHistory = viewModel.messages, + timestamp = System.currentTimeMillis() + ) + + CoroutineScope(Dispatchers.IO).launch { + try { + val success = sendReportToFirebase(reportData) + withContext(Dispatchers.Main) { + if (success) { + Toast.makeText(context, "Report submitted successfully", Toast.LENGTH_SHORT).show() + onDismiss() + } else { + Toast.makeText(context, "Failed to submit report", Toast.LENGTH_SHORT).show() + } + } + } catch (e: Exception) { + withContext(Dispatchers.Main) { + Toast.makeText(context, "Failed to submit report: ${e.message}", Toast.LENGTH_SHORT).show() + } + } + } + } + ) { + Text(text = "Report Content", color = Color(0xFFA0A0A5)) + } + // Selection Container LazyColumn(state = sheetScrollState) { item { @@ -1141,4 +1209,103 @@ fun MessageBottomSheet( } } +} + +data class ReportContent( + val message: String, + val modelName: String, + val deviceName: String, + val topP: Float, + val topK: Int, + val temperature: Float, + val chatHistory: List>, + val timestamp: Long +) + + +// Function to send report to MongoDB +suspend fun sendReportToFirebase(reportContent: ReportContent): Boolean = suspendCoroutine { continuation -> + try { + val db = Firebase.firestore + + db.collection("reports") + .add(reportContent) + .addOnSuccessListener { documentReference -> + Log.d("Firebase", "DocumentSnapshot added with ID: ${documentReference.id}") + continuation.resume(true) + } + .addOnFailureListener { e -> + Log.w("Firebase", "Error adding document", e) + continuation.resume(false) + } + } catch (e: Exception) { + e.printStackTrace() + Log.e("Firebase", "Failed to send report to Firebase Firestore: ${e.message}") + continuation.resume(false) + } +} + +@Composable +fun DotsTyping() { + val dotSize = 8.dp // made it bigger for demo + val delayUnit = 300 // you can change delay to change animation speed + val maxOffset = 10f + + @Composable + fun Dot( + offset: Float + ) = Spacer( + Modifier + .size(dotSize) + .offset(y = -offset.dp) + .background( + color = Color.LightGray, + shape = CircleShape + ) + ) + + val infiniteTransition = rememberInfiniteTransition() + + @Composable + fun animateOffsetWithDelay(delay: Int) = infiniteTransition.animateFloat( + initialValue = 0f, + targetValue = 0f, + animationSpec = infiniteRepeatable( + animation = keyframes { + durationMillis = delayUnit * 4 + 0f at delay with LinearEasing + maxOffset at delay + delayUnit with LinearEasing + 0f at delay + delayUnit * 2 + } + ) + ) + + val offset1 by animateOffsetWithDelay(0) + val offset2 by animateOffsetWithDelay(delayUnit) + val offset3 by animateOffsetWithDelay(delayUnit * 2) + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier.padding(top = maxOffset.dp) + ) { + val spaceSize = 2.dp + + Dot(offset1) + Spacer(Modifier.width(spaceSize)) + Dot(offset2) + Spacer(Modifier.width(spaceSize)) + Dot(offset3) + } +} +fun isInternetAvailableMain(context: Context): Boolean { + val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val network = connectivityManager.activeNetwork ?: return false + val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false + return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + } else { + val networkInfo = connectivityManager.activeNetworkInfo ?: return false + return networkInfo.isConnected + } } \ No newline at end of file diff --git a/app/src/main/java/com/nervesparks/iris/ui/ModelsScreen.kt b/app/src/main/java/com/nervesparks/irisGPT/ui/ModelsScreen.kt similarity index 96% rename from app/src/main/java/com/nervesparks/iris/ui/ModelsScreen.kt rename to app/src/main/java/com/nervesparks/irisGPT/ui/ModelsScreen.kt index d6d514ad..ad15c99f 100644 --- a/app/src/main/java/com/nervesparks/iris/ui/ModelsScreen.kt +++ b/app/src/main/java/com/nervesparks/irisGPT/ui/ModelsScreen.kt @@ -1,4 +1,4 @@ -package com.nervesparks.iris.ui +package com.nervesparks.irisGPT.ui import android.app.DownloadManager import androidx.compose.foundation.clickable @@ -23,10 +23,10 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.nervesparks.iris.MainViewModel -import com.nervesparks.iris.R -import com.nervesparks.iris.ui.components.LoadingModal -import com.nervesparks.iris.ui.components.ModelCard +import com.nervesparks.irisGPT.MainViewModel +import com.nervesparks.irisGPT.R +import com.nervesparks.irisGPT.ui.components.LoadingModal +import com.nervesparks.irisGPT.ui.components.ModelCard import java.io.File @Composable diff --git a/app/src/main/java/com/nervesparks/iris/ui/ParametersScreen.kt b/app/src/main/java/com/nervesparks/irisGPT/ui/ParametersScreen.kt similarity index 98% rename from app/src/main/java/com/nervesparks/iris/ui/ParametersScreen.kt rename to app/src/main/java/com/nervesparks/irisGPT/ui/ParametersScreen.kt index df97933d..4d6c7ca2 100644 --- a/app/src/main/java/com/nervesparks/iris/ui/ParametersScreen.kt +++ b/app/src/main/java/com/nervesparks/irisGPT/ui/ParametersScreen.kt @@ -1,4 +1,4 @@ -package com.nervesparks.iris.ui +package com.nervesparks.irisGPT.ui import android.widget.Toast import androidx.compose.foundation.layout.Arrangement @@ -28,8 +28,8 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import com.nervesparks.iris.MainViewModel -import com.nervesparks.iris.ui.components.LoadingModal +import com.nervesparks.irisGPT.MainViewModel +import com.nervesparks.irisGPT.ui.components.LoadingModal @Composable diff --git a/app/src/main/java/com/nervesparks/irisGPT/ui/Report.kt b/app/src/main/java/com/nervesparks/irisGPT/ui/Report.kt new file mode 100644 index 00000000..5f212d7f --- /dev/null +++ b/app/src/main/java/com/nervesparks/irisGPT/ui/Report.kt @@ -0,0 +1,217 @@ +package com.nervesparks.irisGPT.ui + +import android.app.ActivityManager +import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import android.os.Build +import android.util.Log +import android.widget.Toast +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import com.google.firebase.Firebase +import com.google.firebase.firestore.firestore +import com.nervesparks.irisGPT.MainViewModel +import kotlinx.coroutines.launch +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +data class DeviceInfo( + val manufacturer: String = Build.MANUFACTURER, + val model: String = Build.MODEL, + val processor: String = Build.HARDWARE, + val totalRam: Long = 0, + val availableRam: Long = 0 +) + +data class ReportContentNew( + val reportText: String = "", + val deviceInfo: DeviceInfo = DeviceInfo(), + val modelTemp: Float = 0f, + val modelTopP: Float = 0f, + val modelTopK: Int = 0, + val modelHistory: List> = emptyList() +) + +fun isInternetAvailable(context: Context): Boolean { + val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val network = connectivityManager.activeNetwork ?: return false + val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false + return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + } else { + val networkInfo = connectivityManager.activeNetworkInfo ?: return false + return networkInfo.isConnected + } +} + +@Composable +fun ReportScreen( + viewModel: MainViewModel, + modifier: Modifier = Modifier +) { + val context = LocalContext.current + val scope = rememberCoroutineScope() + var reportText by remember { mutableStateOf("") } + var isSending by remember { mutableStateOf(false) } + var showError by remember { mutableStateOf(false) } + var errorMessage by remember { mutableStateOf("") } + + Column( + modifier = modifier + .fillMaxSize() + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + OutlinedTextField( + value = reportText, + onValueChange = { reportText = it }, + label = { Text("Report Content") }, + modifier = Modifier + .fillMaxWidth() + .weight(1f), + enabled = !isSending, + colors = OutlinedTextFieldDefaults.colors( + unfocusedBorderColor = Color(0xFF666666), + focusedBorderColor = Color(0xFFcfcfd1), + unfocusedLabelColor = Color(0xFF666666), + focusedLabelColor = Color(0xFFcfcfd1), + unfocusedTextColor = Color(0xFFf5f5f5), + focusedTextColor = Color(0xFFf7f5f5), + ) + ) + + if (showError) { + Text( + text = errorMessage, + color = Color.Red, + modifier = Modifier.padding(vertical = 8.dp) + ) + } + + Button( + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF2563EB), + contentColor = Color.White, + disabledContainerColor = Color(0xFF2563EB).copy(alpha = 0.6f), + disabledContentColor = Color.White.copy(alpha = 0.6f) + ), + shape = RoundedCornerShape(8.dp), + elevation = ButtonDefaults.buttonElevation( + defaultElevation = 2.dp, + pressedElevation = 1.dp + ), + onClick = { + scope.launch { + if (!isInternetAvailable(context)) { + showError = true + errorMessage = "Internet connection required" + Toast.makeText(context, "No internet connection", Toast.LENGTH_LONG).show() + return@launch + } + + isSending = true + showError = false + try { + val reportContent = createReportContent( + context, + reportText, + viewModel + ) + val success = sendReportToFirebaseNew(reportContent) + if (success) { + reportText = "" + Toast.makeText(context, "Report sent successfully", Toast.LENGTH_SHORT).show() + } else { + showError = true + errorMessage = "Failed to send report" + Toast.makeText(context, "Failed to send report", Toast.LENGTH_LONG).show() + } + } catch (e: Exception) { + Log.e("ReportScreen", "Error sending report", e) + showError = true + errorMessage = e.message ?: "Unknown error occurred" + Toast.makeText(context, "Error: ${e.message}", Toast.LENGTH_LONG).show() + } finally { + isSending = false + } + } + }, + enabled = reportText.isNotBlank() && !isSending, + ) { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth() + ) { + if (isSending) { + CircularProgressIndicator( + modifier = Modifier.size(24.dp), + color = Color.White, + strokeWidth = 2.dp + ) + } else { + Text( + "Send Report", + style = MaterialTheme.typography.bodyLarge + ) + } + } + } + } +} + +suspend fun sendReportToFirebaseNew(reportContent: ReportContentNew): Boolean = suspendCoroutine { continuation -> + try { + val db = Firebase.firestore + db.collection("reports") + .add(reportContent) + .addOnSuccessListener { documentReference -> + Log.d("Firebase", "DocumentSnapshot added with ID: ${documentReference.id}") + continuation.resume(true) + } + .addOnFailureListener { e -> + Log.w("Firebase", "Error adding document", e) + continuation.resume(false) + } + } catch (e: Exception) { + e.printStackTrace() + Log.e("Firebase", "Failed to send report to Firebase Firestore: ${e.message}") + continuation.resume(false) + } +} + +private fun createReportContent( + context: Context, + reportText: String, + viewModel: MainViewModel +): ReportContentNew { + val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + val memoryInfo = ActivityManager.MemoryInfo() + activityManager.getMemoryInfo(memoryInfo) + + val deviceInfo = DeviceInfo( + manufacturer = Build.MANUFACTURER, + model = Build.MODEL, + processor = Build.HARDWARE, + totalRam = memoryInfo.totalMem, + availableRam = memoryInfo.availMem + ) + + return ReportContentNew( + reportText = reportText, + deviceInfo = deviceInfo, + modelTemp = viewModel.temp, + modelTopP = viewModel.topP, + modelTopK = viewModel.topK, + modelHistory = viewModel.messages + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/nervesparks/iris/ui/SearchResultScreen.kt b/app/src/main/java/com/nervesparks/irisGPT/ui/SearchResultScreen.kt similarity index 97% rename from app/src/main/java/com/nervesparks/iris/ui/SearchResultScreen.kt rename to app/src/main/java/com/nervesparks/irisGPT/ui/SearchResultScreen.kt index 593a9696..7002d481 100644 --- a/app/src/main/java/com/nervesparks/iris/ui/SearchResultScreen.kt +++ b/app/src/main/java/com/nervesparks/irisGPT/ui/SearchResultScreen.kt @@ -1,4 +1,4 @@ -package com.nervesparks.iris.ui +package com.nervesparks.irisGPT.ui import android.app.DownloadManager import android.util.Log @@ -45,11 +45,11 @@ import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.nervesparks.iris.MainViewModel -import com.nervesparks.iris.R -import com.nervesparks.iris.ui.components.InfoModal -import com.nervesparks.iris.ui.components.LoadingModal -import com.nervesparks.iris.ui.components.ModelCard +import com.nervesparks.irisGPT.MainViewModel +import com.nervesparks.irisGPT.R +import com.nervesparks.irisGPT.ui.components.InfoModal +import com.nervesparks.irisGPT.ui.components.LoadingModal +import com.nervesparks.irisGPT.ui.components.ModelCard import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext diff --git a/app/src/main/java/com/nervesparks/iris/ui/SettingsScreen.kt b/app/src/main/java/com/nervesparks/irisGPT/ui/SettingsScreen.kt similarity index 90% rename from app/src/main/java/com/nervesparks/iris/ui/SettingsScreen.kt rename to app/src/main/java/com/nervesparks/irisGPT/ui/SettingsScreen.kt index 4230e580..b21342e2 100644 --- a/app/src/main/java/com/nervesparks/iris/ui/SettingsScreen.kt +++ b/app/src/main/java/com/nervesparks/irisGPT/ui/SettingsScreen.kt @@ -1,4 +1,4 @@ -package com.nervesparks.iris.ui +package com.nervesparks.irisGPT.ui import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -15,7 +15,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.nervesparks.iris.R +import com.nervesparks.irisGPT.R @Composable fun SettingsScreen( @@ -23,6 +23,7 @@ fun SettingsScreen( onModelsScreenButtonClicked: () -> Unit, onAboutScreenButtonClicked: () -> Unit, onBenchMarkScreenButtonClicked: () -> Unit, + onReportScreenButtonClicked: () -> Unit, ) { Box(modifier = Modifier.fillMaxSize()) { LazyColumn( @@ -68,6 +69,13 @@ fun SettingsScreen( iconRes = R.drawable.information_outline_svgrepo_com, onClick = onAboutScreenButtonClicked ) + SettingsDivider() + + SettingsRow( + text = "Report", + iconRes = R.drawable.report_svgrepo_com, + onClick = onReportScreenButtonClicked + ) } } } diff --git a/app/src/main/java/com/nervesparks/iris/ui/components/ChatSection.kt b/app/src/main/java/com/nervesparks/irisGPT/ui/components/ChatSection.kt similarity index 97% rename from app/src/main/java/com/nervesparks/iris/ui/components/ChatSection.kt rename to app/src/main/java/com/nervesparks/irisGPT/ui/components/ChatSection.kt index eecd27f2..edb9b210 100644 --- a/app/src/main/java/com/nervesparks/iris/ui/components/ChatSection.kt +++ b/app/src/main/java/com/nervesparks/irisGPT/ui/components/ChatSection.kt @@ -1,4 +1,4 @@ -package com.nervesparks.iris.ui.components +package com.nervesparks.irisGPT.ui.components import android.content.ClipboardManager import android.content.Context @@ -28,8 +28,8 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import com.nervesparks.iris.MainViewModel -import com.nervesparks.iris.R +import com.nervesparks.irisGPT.MainViewModel +import com.nervesparks.irisGPT.R @Composable diff --git a/app/src/main/java/com/nervesparks/iris/ui/components/DownloadInfoModal.kt b/app/src/main/java/com/nervesparks/irisGPT/ui/components/DownloadInfoModal.kt similarity index 98% rename from app/src/main/java/com/nervesparks/iris/ui/components/DownloadInfoModal.kt rename to app/src/main/java/com/nervesparks/irisGPT/ui/components/DownloadInfoModal.kt index c1b61666..d9006432 100644 --- a/app/src/main/java/com/nervesparks/iris/ui/components/DownloadInfoModal.kt +++ b/app/src/main/java/com/nervesparks/irisGPT/ui/components/DownloadInfoModal.kt @@ -1,4 +1,4 @@ -package com.nervesparks.iris.ui.components +package com.nervesparks.irisGPT.ui.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.* diff --git a/app/src/main/java/com/nervesparks/iris/ui/components/DownloadModal.kt b/app/src/main/java/com/nervesparks/irisGPT/ui/components/DownloadModal.kt similarity index 83% rename from app/src/main/java/com/nervesparks/iris/ui/components/DownloadModal.kt rename to app/src/main/java/com/nervesparks/irisGPT/ui/components/DownloadModal.kt index 0b061c5d..e97a8b1c 100644 --- a/app/src/main/java/com/nervesparks/iris/ui/components/DownloadModal.kt +++ b/app/src/main/java/com/nervesparks/irisGPT/ui/components/DownloadModal.kt @@ -1,4 +1,4 @@ -package com.nervesparks.iris.ui.components +package com.nervesparks.irisGPT.ui.components import android.app.DownloadManager import androidx.compose.foundation.layout.* @@ -12,9 +12,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog -import com.nervesparks.iris.Downloadable -import com.nervesparks.iris.MainViewModel +import com.nervesparks.irisGPT.Downloadable +import com.nervesparks.irisGPT.MainViewModel + + @Composable fun DownloadModal(viewModel: MainViewModel, dm: DownloadManager, models: List) { @@ -33,7 +36,7 @@ fun DownloadModal(viewModel: MainViewModel, dm: DownloadManager, models: List + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bec1d898..69de2126 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -8,5 +8,6 @@ Setting About BenchMark + Report Refresh diff --git a/build.gradle.kts b/build.gradle.kts index 48190392..f6cb9864 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,4 +3,5 @@ plugins { id("com.android.application") version "8.5.2" apply false id("org.jetbrains.kotlin.android") version "1.9.0" apply false id("com.android.library") version "8.5.2" apply false + id("com.google.gms.google-services") version "4.4.2" apply false }