Add user profile, cancel button, dark theme and UI improvements

This commit is contained in:
Алексей Будаев 2026-04-04 18:28:24 +08:00
parent cda6eb7ce0
commit a4d24df8d8
21 changed files with 635 additions and 207 deletions

View file

@ -2,13 +2,18 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.MistralChat">
android:theme="@style/Theme.MistralChat"
android:requestLegacyExternalStorage="true">
<activity
android:name=".ui.MainActivity"

View file

@ -6,6 +6,7 @@ import com.google.gson.JsonArray
import com.mistral.chat.data.Message
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.Call
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
@ -23,6 +24,8 @@ class MistralClient(private val apiKey: String) {
private val gson = Gson()
private val jsonMediaType = "application/json".toMediaType()
private var currentCall: Call? = null
companion object {
private const val BASE_URL = "https://api.mistral.ai/v1"
@ -30,8 +33,7 @@ class MistralClient(private val apiKey: String) {
"mistral-small-latest" to "Mistral Small",
"mistral-medium-latest" to "Mistral Medium",
"mistral-large-latest" to "Mistral Large",
"codestral-latest" to "Codestral",
"pixtral-large-latest" to "Pixtral Large"
"codestral-latest" to "Codestral"
)
}
@ -74,6 +76,11 @@ class MistralClient(private val apiKey: String) {
}
}
fun cancelRequest() {
currentCall?.cancel()
currentCall = null
}
suspend fun chat(
model: String,
messages: List<Message>,
@ -104,7 +111,12 @@ class MistralClient(private val apiKey: String) {
.post(body)
.build()
val response = client.newCall(request).execute()
currentCall = client.newCall(request)
val response = currentCall!!.execute()
if (response.code == 0 || response.code == -1) {
return@withContext Result.failure(Exception("Request cancelled"))
}
if (!response.isSuccessful) {
val errorBody = response.body?.string() ?: "Unknown error"
@ -133,6 +145,7 @@ class MistralClient(private val apiKey: String) {
val usedModel = responseJson.get("model")?.asString ?: model
currentCall = null
Result.success(content to usedModel)
} catch (e: Exception) {
Result.failure(e)

View file

@ -9,6 +9,7 @@ data class UserProfile(
fun toContextString(): String {
return buildString {
append("[User Profile]\n")
if (name.isNotBlank()) append("Name: $name\n")
if (bio.isNotBlank()) append("Bio: $bio\n")
if (preferences.isNotBlank()) append("Preferences: $preferences\n")

View file

@ -3,19 +3,24 @@ package com.mistral.chat.ui
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.view.KeyEvent
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.PopupMenu
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.button.MaterialButton
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.textfield.TextInputEditText
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.mistral.chat.R
import com.mistral.chat.api.MistralClient
import com.mistral.chat.data.Message
@ -27,14 +32,18 @@ class MainActivity : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: MessageAdapter
private lateinit var inputField: TextInputEditText
private lateinit var sendButton: MaterialButton
private lateinit var modelSelector: AutoCompleteTextView
private lateinit var toolbar: MaterialToolbar
private lateinit var sendButton: ImageView
private lateinit var progressIndicator: LinearProgressIndicator
private lateinit var logoButton: ImageView
private lateinit var menuButton: ImageButton
private lateinit var toolbarTitle: TextView
private lateinit var gson: Gson
private var currentJob: kotlinx.coroutines.Job? = null
private var client: MistralClient? = null
private val messages = mutableListOf<Message>()
private var availableModels: List<Pair<String, String>> = emptyList()
private var selectedModelName: String = "mistral-small-latest"
private lateinit var prefs: SharedPreferences
companion object {
@ -43,20 +52,26 @@ class MainActivity : AppCompatActivity() {
private const val KEY_USER_NAME = "user_name"
private const val KEY_USER_BIO = "user_bio"
private const val KEY_USER_PREFS = "user_preferences"
private const val KEY_MESSAGES = "chat_messages"
private const val KEY_PROFILE_HASH = "profile_hash"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
gson = Gson()
prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
client = MistralClient(API_KEY)
toolbar = findViewById(R.id.toolbar)
loadMessages()
logoButton = findViewById(R.id.logoButton)
menuButton = findViewById(R.id.menuButton)
toolbarTitle = findViewById(R.id.toolbarTitle)
recyclerView = findViewById(R.id.recyclerView)
inputField = findViewById(R.id.inputField)
sendButton = findViewById(R.id.sendButton)
modelSelector = findViewById(R.id.modelSelector)
progressIndicator = findViewById(R.id.progressIndicator)
setupToolbar()
@ -64,15 +79,22 @@ class MainActivity : AppCompatActivity() {
loadModels()
setupInput()
inputField.requestFocus()
inputField.postDelayed({
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputField.requestFocus()
imm.showSoftInput(inputField, InputMethodManager.SHOW_IMPLICIT)
}, 100)
}, 300)
}
private fun setupToolbar() {
toolbar.setOnMenuItemClickListener { menuItem ->
logoButton.setOnClickListener {
showModelSelectorDialog()
}
menuButton.setOnClickListener { view ->
val popup = PopupMenu(this, view)
popup.menuInflater.inflate(R.menu.main_menu, popup.menu)
popup.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.action_profile -> {
showProfileDialog()
@ -89,6 +111,23 @@ class MainActivity : AppCompatActivity() {
else -> false
}
}
popup.show()
}
}
private fun showModelSelectorDialog() {
val modelNames = availableModels.map { it.second }.toTypedArray()
val currentModelId = availableModels.find { it.first == selectedModelName }?.second ?: modelNames.firstOrNull() ?: ""
val currentIndex = modelNames.indexOf(currentModelId).coerceAtLeast(0)
AlertDialog.Builder(this)
.setTitle(R.string.select_model)
.setSingleChoiceItems(modelNames, currentIndex) { dialog, which ->
selectedModelName = availableModels[which].first
dialog.dismiss()
}
.setNegativeButton(R.string.cancel, null)
.show()
}
private fun setupRecyclerView() {
@ -101,8 +140,26 @@ class MainActivity : AppCompatActivity() {
private fun setupInput() {
sendButton.setOnClickListener {
if (currentJob?.isActive == true) {
cancelRequest()
} else {
sendInput()
}
}
inputField.setOnEditorActionListener { _, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_SEND || actionId == EditorInfo.IME_ACTION_GO || actionId == EditorInfo.IME_ACTION_DONE || (event?.keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN)) {
sendInput()
true
} else {
false
}
}
}
private fun sendInput() {
val userInput = inputField.text?.toString()?.trim()
if (userInput.isNullOrEmpty()) return@setOnClickListener
if (userInput.isNullOrEmpty()) return
addMessage(Message(content = userInput, isUser = true))
inputField.text?.clear()
@ -110,43 +167,23 @@ class MainActivity : AppCompatActivity() {
sendMessage(userInput)
}
inputField.setOnFocusChangeListener { _, hasFocus ->
if (hasFocus) {
toolbar.title = "Mistral Chat"
}
}
}
private fun loadModels() {
lifecycleScope.launch {
val result = client?.getModels()
result?.onSuccess { models ->
availableModels = models
runOnUiThread {
val modelNames = models.map { it.second }
val adapter = ArrayAdapter(this@MainActivity, android.R.layout.simple_dropdown_item_1line, modelNames)
modelSelector.setAdapter(adapter)
val codestralIndex = models.indexOfFirst { it.first.contains("codestral", ignoreCase = true) }
if (codestralIndex >= 0) {
modelSelector.setText(modelNames[codestralIndex], false)
} else if (modelNames.isNotEmpty()) {
modelSelector.setText(modelNames[0], false)
selectedModelName = models[codestralIndex].first
} else if (models.isNotEmpty()) {
selectedModelName = models[0].first
}
}
}?.onFailure {
availableModels = MistralClient.AVAILABLE_MODELS
runOnUiThread {
val modelNames = availableModels.map { it.second }
val adapter = ArrayAdapter(this@MainActivity, android.R.layout.simple_dropdown_item_1line, modelNames)
modelSelector.setAdapter(adapter)
val codestralIndex = availableModels.indexOfFirst { it.first.contains("codestral", ignoreCase = true) }
if (codestralIndex >= 0) {
modelSelector.setText(modelNames[codestralIndex], false)
} else {
modelSelector.setText(modelNames[0], false)
}
selectedModelName = MistralClient.AVAILABLE_MODELS.firstOrNull()?.first ?: "mistral-small-latest"
}
}
}
@ -155,18 +192,45 @@ class MainActivity : AppCompatActivity() {
private fun addMessage(message: Message) {
messages.add(message)
adapter.notifyItemInserted(messages.size - 1)
saveMessages()
recyclerView.postDelayed({
recyclerView.scrollToPosition(messages.size - 1)
}, 100)
}
private fun loadMessages() {
val json = prefs.getString(KEY_MESSAGES, null)
if (json != null) {
try {
val type = object : TypeToken<List<Message>>() {}.type
val loaded: List<Message> = gson.fromJson(json, type)
messages.clear()
messages.addAll(loaded)
} catch (e: Exception) {
// Ignore parse errors
}
}
}
private fun saveMessages() {
val json = gson.toJson(messages)
prefs.edit().putString(KEY_MESSAGES, json).apply()
}
private fun sendMessage(userInput: String) {
val selectedModelName = modelSelector.text.toString()
val selectedModel = selectedModelName
val matchedModel = availableModels.find { it.second == selectedModelName }
val selectedModel = matchedModel?.first ?: "mistral-small-latest"
sendButton.isEnabled = false
sendButton.setImageResource(R.drawable.ic_stop)
progressIndicator.isVisible = true
currentJob = lifecycleScope.launch {
try {
val userProfile = loadUserProfile()
val profileContext = if (!userProfile.isEmpty()) {
"\n[User Profile]\n${userProfile.toContextString()}\n"
userProfile.toContextString()
} else ""
val apiMessages = messages.map { msg ->
@ -176,26 +240,33 @@ class MainActivity : AppCompatActivity() {
)
}.toMutableList()
if (profileContext.isNotEmpty() && apiMessages.isEmpty()) {
if (profileContext.isNotEmpty()) {
apiMessages.add(0, Message(content = profileContext, isUser = true))
}
sendButton.isEnabled = false
progressIndicator.isVisible = true
lifecycleScope.launch {
val result = client?.chat(selectedModel, apiMessages)
result?.onSuccess { (response, usedModel) ->
addMessage(Message(content = response, isUser = false, senderName = usedModel))
}?.onFailure { error ->
addMessage(Message(content = "Error: ${error.message}", isUser = false, senderName = "Error"))
val errorMessage = error.message ?: "Unknown error"
val userFriendlyMessage = getUserFriendlyError(errorMessage)
addMessage(Message(content = userFriendlyMessage, isUser = false, senderName = "Error"))
}
} catch (e: kotlinx.coroutines.CancellationException) {
addMessage(Message(content = "❌ Отменено пользователем", isUser = false, senderName = "Cancelled"))
} finally {
sendButton.isEnabled = true
sendButton.setImageResource(R.drawable.ic_mistral_logo)
progressIndicator.isVisible = false
}
}
}
private fun cancelRequest() {
currentJob?.cancel()
client?.cancelRequest()
}
private fun loadUserProfile(): UserProfile {
return UserProfile(
@ -210,6 +281,7 @@ class MainActivity : AppCompatActivity() {
.putString(KEY_USER_NAME, profile.name)
.putString(KEY_USER_BIO, profile.bio)
.putString(KEY_USER_PREFS, profile.preferences)
.remove(KEY_PROFILE_HASH)
.apply()
}
@ -218,6 +290,7 @@ class MainActivity : AppCompatActivity() {
.remove(KEY_USER_NAME)
.remove(KEY_USER_BIO)
.remove(KEY_USER_PREFS)
.remove(KEY_PROFILE_HASH)
.apply()
}
@ -242,12 +315,10 @@ class MainActivity : AppCompatActivity() {
preferences = preferencesInput.text?.toString() ?: ""
)
saveUserProfile(newProfile)
toolbar.subtitle = if (newProfile.name.isNotBlank()) newProfile.name else null
}
.setNegativeButton(R.string.cancel, null)
.setNeutralButton(R.string.delete) { _, _ ->
deleteUserProfile()
toolbar.subtitle = null
}
.show()
}
@ -258,6 +329,7 @@ class MainActivity : AppCompatActivity() {
.setPositiveButton(R.string.yes) { _, _ ->
messages.clear()
adapter.notifyDataSetChanged()
prefs.edit().remove(KEY_MESSAGES).apply()
}
.setNegativeButton(R.string.no, null)
.show()
@ -269,4 +341,51 @@ class MainActivity : AppCompatActivity() {
.setPositiveButton(android.R.string.ok, null)
.show()
}
private fun getUserFriendlyError(error: String): String {
return when {
error.contains("timeout", ignoreCase = true) ||
error.contains("TimedOut", ignoreCase = true) ||
error.contains("SocketTimeoutException", ignoreCase = true) ||
error.contains("connection abort", ignoreCase = true) ||
error.contains("Software caused connection abort", ignoreCase = true) ->
"⚠️ Время ожидания истекло. Проверьте интернет-соединение и попробуйте снова."
error.contains("network", ignoreCase = true) ||
error.contains("Unable to resolve host", ignoreCase = true) ||
error.contains("No route to host", ignoreCase = true) ->
"🌐 Нет подключения к интернету. Проверьте соединение и попробуйте снова."
error.contains("401", ignoreCase = true) ||
error.contains("unauthorized", ignoreCase = true) ->
"🔑 Ошибка авторизации. Проверьте API ключ в настройках приложения."
error.contains("403", ignoreCase = true) ||
error.contains("forbidden", ignoreCase = true) ->
"🚫 Доступ запрещён. Возможно, API ключ истёк или недействителен."
error.contains("429", ignoreCase = true) ||
error.contains("rate limit", ignoreCase = true) ||
error.contains("too many requests", ignoreCase = true) ->
"⏳ Слишком много запросов. Подождите немного и попробуйте снова."
error.contains("500", ignoreCase = true) ||
error.contains("internal server error", ignoreCase = true) ->
"🔧 Ошибка сервера Mistral. Попробуйте выбрать другую модель или повторите позже."
error.contains("503", ignoreCase = true) ||
error.contains("service unavailable", ignoreCase = true) ->
"🛠️ Сервис временно недоступен. Попробуйте позже."
error.contains("404", ignoreCase = true) ||
error.contains("not found", ignoreCase = true) ->
"Ресурс не найден. Попробуйте выбрать другую модель."
error.contains("null", ignoreCase = true) ||
error.contains("empty response", ignoreCase = true) ->
"📭 Пустой ответ от сервера. Попробуйте ещё раз."
else -> "❌ Произошла ошибка: $error"
}
}
}

View file

@ -8,9 +8,14 @@ import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.mistral.chat.R
import com.mistral.chat.data.Message
import io.noties.markwon.Markwon
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin
import io.noties.markwon.ext.tables.TablePlugin
import io.noties.markwon.ext.tasklist.TaskListPlugin
class MessageAdapter(private val messages: List<Message>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
@ -43,7 +48,7 @@ class MessageAdapter(private val messages: List<Message>) : RecyclerView.Adapter
val textView = itemView.findViewById<TextView>(R.id.messageText)
textView.text = message.content
textView.setBackgroundResource(R.drawable.bg_message_user)
textView.setTextColor(0xFFFFFFFF.toInt())
textView.setTextColor(ContextCompat.getColor(itemView.context, R.color.user_message_text))
itemView.setOnLongClickListener {
copyToClipboard(itemView.context, message.content)
@ -53,14 +58,20 @@ class MessageAdapter(private val messages: List<Message>) : RecyclerView.Adapter
}
class AssistantMessageHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val markwon: Markwon = Markwon.builder(itemView.context)
.usePlugin(StrikethroughPlugin.create())
.usePlugin(TablePlugin.create(itemView.context))
.usePlugin(TaskListPlugin.create(itemView.context))
.build()
fun bind(message: Message) {
val senderNameView = itemView.findViewById<TextView>(R.id.senderName)
val textView = itemView.findViewById<TextView>(R.id.messageText)
senderNameView.text = message.senderName ?: "Assistant"
textView.text = message.content
markwon.setMarkdown(textView, message.content)
textView.setBackgroundResource(R.drawable.bg_message_assistant)
textView.setTextColor(0xFF000000.toInt())
textView.setTextColor(ContextCompat.getColor(itemView.context, R.color.assistant_message_text))
itemView.setOnLongClickListener {
copyToClipboard(itemView.context, message.content)

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#211B16"/>
<corners android:radius="16dp"/>
<padding android:left="12dp" android:top="8dp" android:right="12dp" android:bottom="8dp"/>
</shape>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FF6B35"/>
<corners android:radius="16dp"/>
<padding android:left="12dp" android:top="8dp" android:right="12dp" android:bottom="8dp"/>
</shape>

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#E0E0E0"/>
<solid android:color="#E7E0EC"/>
<corners android:radius="16dp"/>
<padding android:left="12dp" android:top="8dp" android:right="12dp" android:bottom="8dp"/>
</shape>

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#1976D2"/>
<solid android:color="#FF6B35"/>
<corners android:radius="16dp"/>
<padding android:left="12dp" android:top="8dp" android:right="12dp" android:bottom="8dp"/>
</shape>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#00000000"/>
<corners android:radius="20dp"/>
</shape>

View file

@ -1,17 +1,147 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
android:viewportWidth="600"
android:viewportHeight="600"
android:width="600dp"
android:height="600dp">
<path
android:fillColor="#FFFFFF"
android:pathData="M54,35 L54,73 M35,54 L73,54"
android:strokeWidth="6"
android:strokeColor="#FFFFFF"/>
android:pathData="M0 0L0 600L600 600L600 0L0 0z"
android:fillColor="#FFFFFF" />
<path
android:fillColor="#FFFFFF"
android:pathData="M54,42 A12,12 0 1,1 54,66 A12,12 0 1,1 54,42"
android:strokeWidth="3"
android:strokeColor="#FFFFFF"/>
android:pathData="M30 30C34.7199 31.9806 40.9198 31 46 31L77 31L187 31L570 31C565.28 29.0194 559.08 30 554 30L523 30L413 30L30 30z"
android:fillColor="#FFD907" />
<path
android:pathData="M30 31L30 137L570 137L570 31L30 31z"
android:fillColor="#FFD800" />
<path
android:pathData="M30 137C34.7199 138.981 40.9198 138 46 138L77 138L187 138L570 138C565.28 136.019 559.08 137 554 137L523 137L413 137L30 137z"
android:fillColor="#FFD301" />
<path
android:pathData="M30 138C34.7199 139.981 40.9198 139 46 139L77 139L187 139L570 139C565.28 137.019 559.08 138 554 138L523 138L413 138L30 138z"
android:fillColor="#FFB401" />
<path
android:pathData="M30 139C34.7199 140.981 40.9198 140 46 140L77 140L187 140L569 140C564.28 138.019 558.08 139 553 139L522 139L412 139L30 139z"
android:fillColor="#FFAE00" />
<path
android:pathData="M569 139C562.987 140.777 556.239 140 550 140L515 140L402 140L30 140L30 246L163 246L163 166L202 166C205.88 166 213.386 164.593 216.397 167.603C219.407 170.614 218 178.12 218 182L218 220L256 220C259.88 220 267.386 218.593 270.397 221.603C272.268 223.474 271.983 226.582 271.999 229L272 246L325 246L325 220L379 220L379 166L418 166C421.88 166 429.386 164.593 432.397 167.603C436.138 171.345 434 183.068 434 188L434 246L570 246L570 172L570 151C570 147.017 570.552 142.699 569 139z"
android:fillColor="#FFAF00" />
<path
android:pathData="M163 166C168.216 168.189 175.381 167 181 167L217 167C211.784 164.811 204.619 166 199 166L163 166M379 166C384.216 168.189 391.381 167 397 167L433 167C427.784 164.811 420.619 166 415 166L379 166z"
android:fillColor="#FFCD60" />
<path
android:pathData="M163 167L163 246C165.497 240.049 164 231.42 164 225L164 186C164 180.113 165.292 172.462 163 167z"
android:fillColor="#FFDB90" />
<path
android:pathData="M164 167L164 383L110 383L110 436L271 436L271 383L217 383L217 328L256 328C259.88 328 267.386 326.593 270.397 329.603C273.407 332.614 272 340.12 272 344L272 382L325 382L325 328L364 328C367.88 328 375.386 326.593 378.397 329.603C381.407 332.614 380 340.12 380 344L380 383L326 383L326 436L487 436L487 383L433 383L433 167L380 167L380 221L326 221L326 275L271 275L271 221L217 221L217 167L164 167z"
android:fillColor="#FFFFFF" />
<path
android:pathData="M217 167L217 221L271 221C266.031 218.915 259.35 220 254 220L218 220L218 184C218 178.65 219.085 171.969 217 167z"
android:fillColor="#FFCF69" />
<path
android:pathData="M379 167L379 221C380.875 216.531 380 210.809 380 206L380 180C380 175.74 380.661 170.959 379 167z"
android:fillColor="#FFDD94" />
<path
android:pathData="M433 167L433 246C435.497 240.049 434 231.42 434 225L434 186C434 180.113 435.292 172.462 433 167z"
android:fillColor="#FFD16E" />
<path
android:pathData="M325 220C330.216 222.189 337.381 221 343 221L379 221C373.784 218.811 366.619 220 361 220L325 220z"
android:fillColor="#FFCF69" />
<path
android:pathData="M271 221L271 246C273.413 240.25 273.413 226.75 271 221z"
android:fillColor="#FFD16E" />
<path
android:pathData="M325 221L325 246C327.413 240.25 327.413 226.75 325 221z"
android:fillColor="#FFDD94" />
<path
android:pathData="M30 246L30 354L163 354L163 246L30 246z"
android:fillColor="#FF8205" />
<path
android:pathData="M163 246L163 354C165.903 347.081 164 336.48 164 329L164 274C164 265.729 166.206 253.639 163 246z"
android:fillColor="#FFC892" />
<path
android:pathData="M271 246L271 275L325 275C320.031 272.915 313.35 274 308 274L272 274C272 265.712 274.21 253.649 271 246z"
android:fillColor="#FFB770" />
<path
android:pathData="M272 246L272 274L325 274L325 246L272 246z"
android:fillColor="#FF8205" />
<path
android:pathData="M325 246L325 275C327.292 269.538 326 261.887 326 256C326 252.581 326.328 249.166 325 246z"
android:fillColor="#FFCA97" />
<path
android:pathData="M433 246L433 354C435.903 347.081 434 336.48 434 329L434 274C434 265.729 436.206 253.639 433 246z"
android:fillColor="#FFB770" />
<path
android:pathData="M434 246L434 354L570 354L570 246L434 246z"
android:fillColor="#FF8205" />
<path
android:pathData="M217 328C222.216 330.189 229.381 329 235 329L271 329C265.784 326.811 258.619 328 253 328L217 328M325 328C330.216 330.189 337.381 329 343 329L379 329L379 354C381.179 348.806 380.038 341.598 379.999 336C379.984 333.791 380.361 330.583 378.397 329.028C375.302 326.577 367.731 328 364 328L325 328z"
android:fillColor="#FFCA97" />
<path
android:pathData="M217 329L217 354C219.413 348.25 219.413 334.75 217 329z"
android:fillColor="#FFB770" />
<path
android:pathData="M218 329L218 354L271 354L271 329L218 329z"
android:fillColor="#FF8205" />
<path
android:pathData="M271 329L271 354C273.413 348.25 273.413 334.75 271 329z"
android:fillColor="#FFC892" />
<path
android:pathData="M325 329L325 354C327.413 348.25 327.413 334.75 325 329z"
android:fillColor="#FFB975" />
<path
android:pathData="M326 329L326 354L379 354L379 329L326 329z"
android:fillColor="#FF8205" />
<path
android:pathData="M30 354L30 461L570 461L570 354L434 354L434 382L472 382C475.88 382 483.386 380.593 486.397 383.603C489.407 386.614 488 394.12 488 398L488 439L366 439L336 439C332.367 439 327.242 439.814 324.603 436.682C321.691 433.226 323 426.2 323 422L323 385L273 385L273 437L109 437L109 382L163 382L163 354L30 354z"
android:fillColor="#FA500F" />
<path
android:pathData="M163 354L163 383C165.292 377.538 164 369.887 164 364C164 360.581 164.328 357.166 163 354z"
android:fillColor="#FCB296" />
<path
android:pathData="M217 354L217 383L271 383C266.031 380.915 259.35 382 254 382L218 382C218 373.712 220.21 361.649 217 354z"
android:fillColor="#FB9771" />
<path
android:pathData="M218 354L218 382L271 382L271 354L218 354z"
android:fillColor="#FA500F" />
<path
android:pathData="M271 354L271 383L325 383C320.031 380.915 313.35 382 308 382L272 382C272 373.712 274.21 361.649 271 354z"
android:fillColor="#FCB9A0" />
<path
android:pathData="M325 354L325 383L379 383C374.031 380.915 367.35 382 362 382L326 382C326 373.712 328.21 361.649 325 354z"
android:fillColor="#FB9771" />
<path
android:pathData="M326 354L326 382L379 382L379 354L326 354z"
android:fillColor="#FA500F" />
<path
android:pathData="M379 354L379 383C381.292 377.538 380 369.887 380 364C380 360.581 380.328 357.166 379 354z"
android:fillColor="#FCB69B" />
<path
android:pathData="M433 354L433 383L487 383C482.031 380.915 475.35 382 470 382L434 382C434 373.712 436.21 361.649 433 354M109 382C114.216 384.189 121.381 383 127 383L163 383C157.784 380.811 150.619 382 145 382L109 382z"
android:fillColor="#FB9771" />
<path
android:pathData="M109 383L109 437L271 437C266.28 435.019 260.08 436 255 436L223 436L110 436L110 400C110 394.65 111.085 387.969 109 383z"
android:fillColor="#FCB296" />
<path
android:pathData="M271 383L271 437C272.661 433.041 272 428.26 272 424L272 398C272 393.191 272.875 387.469 271 383z"
android:fillColor="#FC9B76" />
<path
android:pathData="M272 383L272 437C274.085 432.031 273 425.35 273 420L273 385L323 385L323 423C323 426.88 321.593 434.386 324.603 437.397C327.188 439.981 332.694 439 336 439L366 439L488 439L488 437L325 437L325 383L272 383z"
android:fillColor="#FA4F0E" />
<path
android:pathData="M325 383L325 437L487 437C482.28 435.019 476.08 436 471 436L439 436L326 436L326 400C326 394.65 327.085 387.969 325 383z"
android:fillColor="#FCB296" />
<path
android:pathData="M487 383L487 437C488.661 433.041 488 428.26 488 424L488 398C488 393.191 488.875 387.469 487 383z"
android:fillColor="#FB956E" />
<path
android:pathData="M30 461C34.7199 462.981 40.9198 462 46 462L77 462L187 462L570 462C565.28 460.019 559.08 461 554 461L523 461L413 461L30 461z"
android:fillColor="#F6460C" />
<path
android:pathData="M30 462C34.7199 463.981 40.9198 463 46 463L77 463L187 463L570 463C565.28 461.019 559.08 462 554 462L523 462L413 462L30 462z"
android:fillColor="#E30E01" />
<path
android:pathData="M30 463C34.7199 464.981 40.9198 464 46 464L77 464L187 464L569 464C564.28 462.019 558.08 463 553 463L522 463L412 463L30 463z"
android:fillColor="#E00400" />
<path
android:pathData="M569 463C562.988 464.777 556.239 464 550 464L515 464L402 464L30 464L30 570L570 570L570 496L570 475C570 471.017 570.552 466.699 569 463z"
android:fillColor="#E10500" />
</vector>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<!-- Three vertical dots -->
<path
android:fillColor="?attr/colorOnSurface"
android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,14c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,20c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2z"/>
</vector>

View file

@ -0,0 +1,68 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
android:viewportWidth="2000"
android:viewportHeight="2000"
android:width="2000dp"
android:height="2000dp">
<group
android:translateX="283"
android:translateY="294">
<path
android:pathData="M0 0C94.05 0 188.1 0 285 0C285 92.73 285 185.46 285 281C378.39 281 471.78 281 568 281C568 373.4 568 465.8 568 561C661.39 561 754.78 561 851 561C851 468.6 851 376.2 851 281C944.72 281 1038.44 281 1135 281C1135 188.27 1135 95.54 1135 0C1228.72 0 1322.44 0 1419 0C1419 370.92 1419 741.84 1419 1124C1512.72 1124 1606.44 1124 1703 1124C1703 1217.06 1703 1310.12 1703 1406C1421.84 1406 1140.68 1406 851 1406C851 1312.94 851 1219.88 851 1124C757.94 1124 664.88 1124 569 1124C569.0396235 1216.43249801 569.0396235 1216.43249801 569.09008789 1308.86499023C569.09460449 1326.6973877 569.09460449 1326.6973877 569.09544373 1335.03187561C569.09637954 1340.88266905 569.10006151 1346.73345303 569.10557556 1352.58424377C569.11191748 1359.32336687 569.11484146 1366.06248085 569.11344495 1372.80160696C569.11283958 1376.25280501 569.11383405 1379.70398264 569.11921513 1383.155177C569.12413862 1386.84076799 569.1230086 1390.52630678 569.12025452 1394.2118988C569.12462351 1395.84710023 569.12462351 1395.84710023 569.12908077 1397.51533604C569.1270899 1398.49647058 569.12509903 1399.47760513 569.12304783 1400.48847103C569.12361741 1401.7683184 569.12361741 1401.7683184 569.1241985 1403.07402128C569 1405 569 1405 568 1406C566.14184447 1406.09801994 564.27990365 1406.12447185 562.4191646 1406.1241985C561.19707927 1406.12644579 559.97499394 1406.12869308 558.71587572 1406.13100848C557.33944024 1406.12844217 555.96300493 1406.12578523 554.58656979 1406.12304783C553.13408481 1406.12378657 551.6816 1406.12496419 550.22911569 1406.12654433C546.20089104 1406.12963397 542.17269134 1406.12642127 538.14446853 1406.12226619C533.77607496 1406.11884944 529.40768408 1406.12119944 525.03929001 1406.12284368C517.37096344 1406.12492356 509.7026446 1406.12326324 502.03431892 1406.11920547C490.63166904 1406.11318835 479.22902407 1406.112868 467.82637289 1406.11374746C448.64842698 1406.11506365 429.47048451 1406.11171129 410.2925396 1406.10573006C391.29443787 1406.09982005 372.29633758 1406.09596553 353.29823494 1406.09487724C352.11852257 1406.0948085 350.9388102 1406.09473976 349.72334897 1406.09466894C343.73257971 1406.09433062 337.74181045 1406.09402923 331.75104118 1406.09374207C289.2800714 1406.09163971 246.80910349 1406.08489695 204.33813477 1406.07543945C163.06925466 1406.06626023 121.80037506 1406.05917406 80.53149414 1406.05493164C79.26018409 1406.05480011 77.98887403 1406.05466859 76.67903943 1406.05453308C63.91242572 1406.0532146 51.145812 1406.05193878 38.37919828 1406.05069422C12.37039832 1406.04815777 -13.63840162 1406.04533651 -39.64720154 1406.04234314C-40.84313532 1406.04220585 -42.03906911 1406.04206856 -43.27124331 1406.04192711C-79.93990807 1406.03770266 -116.60857253 1406.03227067 -153.27723694 1406.02565002C-154.31862995 1406.02546247 -155.36002297 1406.02527491 -156.43297335 1406.02508167C-198.62198238 1406.01746502 -240.81099116 1406.00857203 -283 1406C-283 1312.94 -283 1219.88 -283 1124C-189.61 1124 -96.22 1124 0 1124C0 753.08 0 382.16 0 0ZM285 843C285 935.73 285 1028.46 285 1124C378.06 1124 471.12 1124 567 1124C567 1031.27 567 938.54 567 843C473.94 843 380.88 843 285 843ZM852 843C852 935.73 852 1028.46 852 1124C945.39 1124 1038.78 1124 1135 1124C1135 1031.27 1135 938.54 1135 843C1041.61 843 948.22 843 852 843Z"
android:fillColor="#FF8104" />
</group>
<group
android:translateX="283"
android:translateY="294">
<path
android:pathData="M0 0C94.05 0 188.1 0 285 0C285 92.73 285 185.46 285 281C378.39 281 471.78 281 568 281C568 373.4 568 465.8 568 561C661.39 561 754.78 561 851 561C851 468.6 851 376.2 851 281C944.72 281 1038.44 281 1135 281C1135 188.27 1135 95.54 1135 0C1228.72 0 1322.44 0 1419 0C1419 185.46 1419 370.92 1419 562C950.73 562 482.46 562 0 562C0 376.54 0 191.08 0 0Z"
android:fillColor="#FFAE00" />
</group>
<group
android:translateX="1134"
android:translateY="1418">
<path
android:pathData="M0 0C281.16 0 562.32 0 852 0C852 93.06 852 186.12 852 282C570.84 282 289.68 282 0 282C0 188.94 0 95.88 0 0Z"
android:fillColor="#E10400" />
</group>
<group
android:translateY="1418">
<path
android:pathData="M0 0C281.16 0 562.32 0 852 0C852 92.73 852 185.46 852 281C851 282 851 282 848.63661947 282.12467116C847.57485937 282.12451518 846.51309926 282.1243592 845.4191646 282.1241985C844.19707927 282.12644579 842.97499394 282.12869308 841.71587572 282.13100848C840.33944024 282.12844217 838.96300493 282.12578523 837.58656979 282.12304783C836.13408481 282.12378657 834.6816 282.12496419 833.22911569 282.12654433C829.20089104 282.12963397 825.17269134 282.12642127 821.14446853 282.12226619C816.77607496 282.11884944 812.40768408 282.12119944 808.03929001 282.12284368C800.37096344 282.12492356 792.7026446 282.12326324 785.03431892 282.11920547C773.63166904 282.11318835 762.22902407 282.112868 750.82637289 282.11374746C731.64842698 282.11506365 712.47048451 282.11171129 693.2925396 282.10573006C674.29443787 282.09982005 655.29633758 282.09596553 636.29823494 282.09487724C635.11852257 282.0948085 633.9388102 282.09473976 632.72334897 282.09466894C626.73257971 282.09433062 620.74181045 282.09402923 614.75104118 282.09374207C572.2800714 282.09163971 529.80910349 282.08489695 487.33813477 282.07543945C446.06925466 282.06626023 404.80037506 282.05917406 363.53149414 282.05493164C362.26018409 282.05480011 360.98887403 282.05466859 359.67903943 282.05453308C346.91242572 282.0532146 334.145812 282.05193878 321.37919828 282.05069422C295.37039832 282.04815777 269.36159838 282.04533651 243.35279846 282.04234314C242.15686468 282.04220585 240.96093089 282.04206856 239.72875669 282.04192711C203.06009193 282.03770266 166.39142747 282.03227067 129.72276306 282.02565002C128.68137005 282.02546247 127.63997703 282.02527491 126.56702665 282.02508167C84.37801762 282.01746502 42.18900884 282.00857203 0 282C0 188.94 0 95.88 0 0Z"
android:fillColor="#E10400" />
</group>
<group
android:translateX="850"
android:translateY="1137">
<path
android:pathData="M0 0C94.05 0 188.1 0 285 0C285 92.73 285 185.46 285 281C190.95 281 96.9 281 0 281C0 188.27 0 95.54 0 0Z"
android:fillColor="#F94F0F" />
</group>
<group
android:translateX="283"
android:translateY="1137">
<path
android:pathData="M0 0C94.05 0 188.1 0 285 0C285 92.73 285 185.46 285 281C190.95 281 96.9 281 0 281C0 188.27 0 95.54 0 0Z"
android:fillColor="#F94F0E" />
</group>
<group
android:translateX="283"
android:translateY="294">
<path
android:pathData="M0 0C94.05 0 188.1 0 285 0C285 92.73 285 185.46 285 281C190.95 281 96.9 281 0 281C0 188.27 0 95.54 0 0Z"
android:fillColor="#FFD800" />
</group>
<group
android:translateX="1418"
android:translateY="1137">
<path
android:pathData="M0 0C93.72 0 187.44 0 284 0C284 92.73 284 185.46 284 281C190.28 281 96.56 281 0 281C0 188.27 0 95.54 0 0Z"
android:fillColor="#F94F0E" />
</group>
<group
android:translateX="1418"
android:translateY="294">
<path
android:pathData="M0 0C93.72 0 187.44 0 284 0C284 92.73 284 185.46 284 281C190.28 281 96.56 281 0 281C0 188.27 0 95.54 0 0Z"
android:fillColor="#FFD700" />
</group>
</vector>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<!-- Stop square icon -->
<path
android:fillColor="#FFFFFF"
android:pathData="M6,6h12v12h-12z"/>
</vector>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
@ -12,71 +12,90 @@
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
android:layout_alignParentTop="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="Mistral Chat"
app:titleCentered="true"
app:menu="@menu/main_menu" />
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp">
<ImageView
android:id="@+id/logoButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:src="@drawable/ic_mistral_logo"
android:contentDescription="@string/select_model"
android:clickable="true"
android:focusable="true" />
<TextView
android:id="@+id/toolbarTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="12dp"
android:text="Le Chat"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="?attr/colorOnSurface" />
<ImageButton
android:id="@+id/menuButton"
android:layout_width="36dp"
android:layout_height="36dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_menu_dots"
android:contentDescription="@string/settings"
android:clickable="true"
android:focusable="true" />
</LinearLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:padding="16dp">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/modelSelectorLayout"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/select_model"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<AutoCompleteTextView
android:id="@+id/modelSelector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:clipToPadding="false"
app:layout_constraintBottom_toTopOf="@id/inputCard"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/modelSelectorLayout" />
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/inputContainer"
android:layout_below="@+id/appBarLayout"
android:padding="16dp"
android:clipToPadding="false" />
<LinearLayout
android:id="@+id/inputContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="vertical"
android:background="?attr/colorSurface"
android:padding="16dp">
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progressIndicator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="gone"
android:layout_marginBottom="8dp" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/inputCard"
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="28dp"
app:cardElevation="4dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingEnd="8dp">
android:paddingStart="8dp"
android:paddingEnd="4dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/inputField"
@ -90,28 +109,21 @@
android:maxLines="5"
android:minHeight="56dp" />
<com.google.android.material.button.MaterialButton
<ImageView
android:id="@+id/sendButton"
style="@style/Widget.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="48dp"
android:layout_height="48dp"
app:icon="@android:drawable/ic_menu_send"
app:iconGravity="textStart"
app:iconPadding="0dp" />
android:layout_width="40dp"
android:layout_height="40dp"
android:padding="8dp"
android:src="@drawable/ic_mistral_logo"
android:background="@drawable/bg_send_button"
android:contentDescription="@string/send"
android:clickable="true"
android:focusable="true" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progressIndicator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="gone"
app:layout_anchor="@id/appBarLayout"
app:layout_anchorGravity="bottom" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</RelativeLayout>

View file

@ -6,14 +6,25 @@
android:layout_height="wrap_content"
android:padding="8dp">
<ImageView
android:id="@+id/modelIcon"
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@drawable/ic_launcher_foreground"
android:contentDescription="Mistral"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="@id/senderName" />
<TextView
android:id="@+id/senderName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textSize="12sp"
android:textColor="#888888"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintStart_toEndOf="@id/modelIcon"
app:layout_constraintTop_toTopOf="parent" />
<TextView

View file

@ -4,19 +4,14 @@
<item
android:id="@+id/action_profile"
android:icon="@android:drawable/ic_menu_myplaces"
android:title="@string/profile"
app:showAsAction="ifRoom" />
android:title="@string/profile" />
<item
android:id="@+id/action_clear"
android:icon="@android:drawable/ic_menu_delete"
android:title="@string/clear_chat"
app:showAsAction="never" />
android:title="@string/clear_chat" />
<item
android:id="@+id/action_about"
android:title="@string/about"
app:showAsAction="never" />
android:title="@string/about" />
</menu>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="user_message_text">#FFFFFF</color>
<color name="assistant_message_text">#E6E1E5</color>
</resources>

View file

@ -1,4 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#6B4EFF</color>
<color name="user_message_text">#FFFFFF</color>
<color name="assistant_message_text">#000000</color>
</resources>

View file

@ -1,27 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Mistral Chat</string>
<string name="select_model">Select Model</string>
<string name="enter_message">Enter message</string>
<string name="send">Send</string>
<string name="profile">Profile</string>
<string name="clear_chat">Clear Chat</string>
<string name="about">About</string>
<string name="profile_title">User Profile</string>
<string name="profile_name">Name</string>
<string name="profile_bio">Bio</string>
<string name="profile_preferences">Preferences</string>
<string name="save">Save</string>
<string name="cancel">Cancel</string>
<string name="delete">Delete</string>
<string name="clear_chat_confirm">Clear all messages?</string>
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="chat_cleared">Chat cleared</string>
<string name="profile_saved">Profile saved</string>
<string name="profile_deleted">Profile deleted</string>
<string name="about_text">Mistral Chat\nPowered by Mistral AI</string>
<string name="name_hint">Your name</string>
<string name="bio_hint">Tell AI about yourself...</string>
<string name="no_profile">No profile set</string>
<string name="app_name">Mistral Le Chat</string>
<string name="select_model">Выбрать модель</string>
<string name="enter_message">Введите сообщение</string>
<string name="send">Отправить</string>
<string name="profile">Профиль</string>
<string name="clear_chat">Очистить чат</string>
<string name="about">О приложении</string>
<string name="profile_title">Профиль пользователя</string>
<string name="profile_name">Имя</string>
<string name="profile_bio">О себе</string>
<string name="profile_preferences">Предпочтения</string>
<string name="save">Сохранить</string>
<string name="cancel">Отмена</string>
<string name="delete">Удалить</string>
<string name="clear_chat_confirm">Очистить все сообщения?</string>
<string name="yes">Да</string>
<string name="no">Нет</string>
<string name="chat_cleared">Чат очищен</string>
<string name="profile_saved">Профиль сохранён</string>
<string name="profile_deleted">Профиль удалён</string>
<string name="about_text">Mistral Le Chat\nPowered by Mistral AI</string>
<string name="name_hint">Ваше имя</string>
<string name="bio_hint">Расскажите о себе...</string>
<string name="no_profile">Профиль не установлен</string>
<string name="settings">Настройки</string>
</resources>

View file

@ -16,6 +16,19 @@
<item name="colorError">#B3261E</item>
<item name="colorOnError">#FFFFFF</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">true</item>
<item name="android:windowLightNavigationBar">true</item>
<item name="android:enforceNavigationBarContrast">false</item>
<item name="alertDialogTheme">@style/AlertDialogTheme</item>
</style>
<style name="AlertDialogTheme" parent="ThemeOverlay.Material3.MaterialAlertDialog">
<item name="shapeAppearanceOverlay">@style/AlertDialogShapeAppearance</item>
</style>
<style name="AlertDialogShapeAppearance" parent="">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">24dp</item>
</style>
</resources>