diff --git a/app/build.gradle b/app/build.gradle index 81982a3..b13c057 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' + id 'com.google.devtools.ksp' version '1.9.22-1.0.17' } android { @@ -50,4 +51,10 @@ dependencies { implementation 'io.noties.markwon:ext-strikethrough:4.6.2' implementation 'io.noties.markwon:ext-tables:4.6.2' implementation 'io.noties.markwon:ext-tasklist:4.6.2' + + implementation 'androidx.room:room-runtime:2.6.1' + implementation 'androidx.room:room-ktx:2.6.1' + ksp 'androidx.room:room-compiler:2.6.1' + implementation 'net.zetetic:android-database-sqlcipher:4.5.4' + implementation 'androidx.sqlite:sqlite-ktx:2.4.0' } \ No newline at end of file diff --git a/app/src/main/java/com/mistral/chat/data/ChatDatabase.kt b/app/src/main/java/com/mistral/chat/data/ChatDatabase.kt new file mode 100644 index 0000000..d735978 --- /dev/null +++ b/app/src/main/java/com/mistral/chat/data/ChatDatabase.kt @@ -0,0 +1,64 @@ +package com.mistral.chat.data + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import net.sqlcipher.database.SupportFactory + +@Database( + entities = [Profile::class, Session::class, MessageEntity::class, Setting::class], + version = 1, + exportSchema = false +) +abstract class ChatDatabase : RoomDatabase() { + abstract fun profileDao(): ProfileDao + abstract fun sessionDao(): SessionDao + abstract fun messageDao(): MessageDao + abstract fun settingDao(): SettingDao + + companion object { + private const val DATABASE_NAME = "mistral_chat.db" + + @Volatile + private var INSTANCE: ChatDatabase? = null + + fun getInstance(context: Context): ChatDatabase { + return INSTANCE ?: synchronized(this) { + INSTANCE ?: buildDatabase(context).also { INSTANCE = it } + } + } + + private fun buildDatabase(context: Context): ChatDatabase { + val passphrase = getOrCreatePassphrase(context) + val factory = SupportFactory(passphrase) + + return Room.databaseBuilder( + context.applicationContext, + ChatDatabase::class.java, + DATABASE_NAME + ) + .openHelperFactory(factory) + .fallbackToDestructiveMigration() + .build() + } + + private fun getOrCreatePassphrase(context: Context): ByteArray { + val prefs = context.getSharedPreferences("db_prefs", Context.MODE_PRIVATE) + val existingKey = prefs.getString("db_key", null) + + return if (existingKey != null) { + existingKey.toByteArray(Charsets.UTF_8) + } else { + val newKey = generateSecureKey() + prefs.edit().putString("db_key", newKey).apply() + newKey.toByteArray(Charsets.UTF_8) + } + } + + private fun generateSecureKey(): String { + val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*" + return (1..32).map { chars.random() }.joinToString("") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mistral/chat/data/MessageDao.kt b/app/src/main/java/com/mistral/chat/data/MessageDao.kt new file mode 100644 index 0000000..a4bedba --- /dev/null +++ b/app/src/main/java/com/mistral/chat/data/MessageDao.kt @@ -0,0 +1,31 @@ +package com.mistral.chat.data + +import androidx.room.* +import kotlinx.coroutines.flow.Flow + +@Dao +interface MessageDao { + @Query("SELECT * FROM messages WHERE sessionId = :sessionId ORDER BY timestamp ASC") + fun getMessagesBySession(sessionId: Long): Flow> + + @Query("SELECT * FROM messages WHERE sessionId = :sessionId ORDER BY timestamp ASC") + suspend fun getMessagesBySessionSync(sessionId: Long): List + + @Query("SELECT COUNT(*) FROM messages WHERE sessionId = :sessionId") + suspend fun getMessageCount(sessionId: Long): Int + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(message: MessageEntity): Long + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertAll(messages: List) + + @Delete + suspend fun delete(message: MessageEntity) + + @Query("DELETE FROM messages WHERE sessionId = :sessionId") + suspend fun deleteBySession(sessionId: Long) + + @Query("DELETE FROM messages") + suspend fun deleteAll() +} \ No newline at end of file diff --git a/app/src/main/java/com/mistral/chat/data/Profile.kt b/app/src/main/java/com/mistral/chat/data/Profile.kt new file mode 100644 index 0000000..cd2fd59 --- /dev/null +++ b/app/src/main/java/com/mistral/chat/data/Profile.kt @@ -0,0 +1,15 @@ +package com.mistral.chat.data + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "profiles") +data class Profile( + @PrimaryKey(autoGenerate = true) + val id: Long = 0, + val name: String, + val bio: String = "", + val preferences: String = "", + val createdAt: Long = System.currentTimeMillis(), + val updatedAt: Long = System.currentTimeMillis() +) \ No newline at end of file diff --git a/app/src/main/java/com/mistral/chat/data/ProfileDao.kt b/app/src/main/java/com/mistral/chat/data/ProfileDao.kt new file mode 100644 index 0000000..4941ba5 --- /dev/null +++ b/app/src/main/java/com/mistral/chat/data/ProfileDao.kt @@ -0,0 +1,28 @@ +package com.mistral.chat.data + +import androidx.room.* +import kotlinx.coroutines.flow.Flow + +@Dao +interface ProfileDao { + @Query("SELECT * FROM profiles ORDER BY updatedAt DESC") + fun getAllProfiles(): Flow> + + @Query("SELECT * FROM profiles WHERE id = :id") + suspend fun getProfileById(id: Long): Profile? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(profile: Profile): Long + + @Update + suspend fun update(profile: Profile) + + @Delete + suspend fun delete(profile: Profile) + + @Query("DELETE FROM profiles WHERE id = :id") + suspend fun deleteById(id: Long) + + @Query("DELETE FROM profiles") + suspend fun deleteAll() +} \ No newline at end of file diff --git a/app/src/main/java/com/mistral/chat/data/Session.kt b/app/src/main/java/com/mistral/chat/data/Session.kt new file mode 100644 index 0000000..28faa9e --- /dev/null +++ b/app/src/main/java/com/mistral/chat/data/Session.kt @@ -0,0 +1,29 @@ +package com.mistral.chat.data + +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Index +import androidx.room.PrimaryKey + +@Entity( + tableName = "sessions", + foreignKeys = [ + ForeignKey( + entity = Profile::class, + parentColumns = ["id"], + childColumns = ["profileId"], + onDelete = ForeignKey.SET_NULL + ) + ], + indices = [Index("profileId")] +) +data class Session( + @PrimaryKey(autoGenerate = true) + val id: Long = 0, + val profileId: Long? = null, + val title: String = "Новая сессия", + val isManuallyRenamed: Boolean = false, + val isTitleGenerated: Boolean = false, + val createdAt: Long = System.currentTimeMillis(), + val updatedAt: Long = System.currentTimeMillis() +) \ No newline at end of file diff --git a/app/src/main/java/com/mistral/chat/data/SessionDao.kt b/app/src/main/java/com/mistral/chat/data/SessionDao.kt new file mode 100644 index 0000000..63bdaab --- /dev/null +++ b/app/src/main/java/com/mistral/chat/data/SessionDao.kt @@ -0,0 +1,40 @@ +package com.mistral.chat.data + +import androidx.room.* +import kotlinx.coroutines.flow.Flow + +@Dao +interface SessionDao { + @Query("SELECT * FROM sessions ORDER BY updatedAt DESC") + fun getAllSessions(): Flow> + + @Query("SELECT * FROM sessions WHERE profileId = :profileId ORDER BY updatedAt DESC") + fun getSessionsByProfile(profileId: Long): Flow> + + @Query("SELECT * FROM sessions WHERE profileId IS NULL ORDER BY updatedAt DESC") + fun getSessionsWithoutProfile(): Flow> + + @Query("SELECT * FROM sessions WHERE id = :id") + suspend fun getSessionById(id: Long): Session? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(session: Session): Long + + @Update + suspend fun update(session: Session) + + @Delete + suspend fun delete(session: Session) + + @Query("DELETE FROM sessions WHERE id = :id") + suspend fun deleteById(id: Long) + + @Query("DELETE FROM sessions") + suspend fun deleteAll() + + @Query("UPDATE sessions SET updatedAt = :timestamp WHERE id = :sessionId") + suspend fun updateTimestamp(sessionId: Long, timestamp: Long = System.currentTimeMillis()) + + @Query("UPDATE sessions SET title = :title, isManuallyRenamed = 1 WHERE id = :sessionId") + suspend fun updateTitle(sessionId: Long, title: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/mistral/chat/data/Setting.kt b/app/src/main/java/com/mistral/chat/data/Setting.kt new file mode 100644 index 0000000..7530abd --- /dev/null +++ b/app/src/main/java/com/mistral/chat/data/Setting.kt @@ -0,0 +1,11 @@ +package com.mistral.chat.data + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "settings") +data class Setting( + @PrimaryKey + val key: String, + val value: String +) \ No newline at end of file diff --git a/app/src/main/java/com/mistral/chat/data/SettingDao.kt b/app/src/main/java/com/mistral/chat/data/SettingDao.kt new file mode 100644 index 0000000..0b06554 --- /dev/null +++ b/app/src/main/java/com/mistral/chat/data/SettingDao.kt @@ -0,0 +1,22 @@ +package com.mistral.chat.data + +import androidx.room.* +import kotlinx.coroutines.flow.Flow + +@Dao +interface SettingDao { + @Query("SELECT * FROM settings WHERE `key` = :key") + suspend fun getSetting(key: String): Setting? + + @Query("SELECT value FROM settings WHERE `key` = :key") + suspend fun getValue(key: String): String? + + @Query("SELECT value FROM settings WHERE `key` = :key") + fun getValueFlow(key: String): Flow + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(setting: Setting) + + @Query("DELETE FROM settings WHERE `key` = :key") + suspend fun delete(key: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/mistral/chat/ui/DrawerSubmenuAdapter.kt b/app/src/main/java/com/mistral/chat/ui/DrawerSubmenuAdapter.kt new file mode 100644 index 0000000..0a6ab8a --- /dev/null +++ b/app/src/main/java/com/mistral/chat/ui/DrawerSubmenuAdapter.kt @@ -0,0 +1,54 @@ +package com.mistral.chat.ui + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView +import com.mistral.chat.R + +class DrawerSubmenuAdapter( + private val items: List, + private val onItemClick: (DrawerMenuItem) -> Unit +) : RecyclerView.Adapter() { + + data class DrawerMenuItem( + val id: String, + val title: String, + val icon: Int? = null, + val isSelected: Boolean = false + ) + + class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val icon: ImageView = itemView.findViewById(R.id.itemIcon) + val title: TextView = itemView.findViewById(R.id.itemTitle) + val check: ImageView = itemView.findViewById(R.id.itemCheck) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.item_drawer, parent, false) + return ViewHolder(view) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val item = items[position] + holder.title.text = item.title + + if (item.icon != null) { + holder.icon.setImageResource(item.icon) + holder.icon.visibility = View.VISIBLE + } else { + holder.icon.visibility = View.GONE + } + + holder.check.visibility = if (item.isSelected) View.VISIBLE else View.GONE + + holder.itemView.setOnClickListener { + onItemClick(item) + } + } + + override fun getItemCount(): Int = items.size +} \ No newline at end of file diff --git a/app/src/main/java/com/mistral/chat/ui/MainActivity.kt b/app/src/main/java/com/mistral/chat/ui/MainActivity.kt index 5503020..14a1c0d 100644 --- a/app/src/main/java/com/mistral/chat/ui/MainActivity.kt +++ b/app/src/main/java/com/mistral/chat/ui/MainActivity.kt @@ -90,6 +90,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte private const val KEY_NEW_SESSION_ON_START = "new_session_on_start" private const val KEY_LAST_PROFILE_ID = "last_profile_id" private const val KEY_SELECTED_MODEL = "selected_model" + private const val KEY_THEME_MODE = "theme_mode" private const val MAX_PROFILES = 10 } @@ -193,7 +194,10 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte R.id.action_clear_all -> { showClearAllDialog() } - R.id.action_settings -> { + R.id.action_appearance -> { + showThemeDialog() + } + R.id.action_session -> { showSettingsDialog() } R.id.action_about -> { @@ -298,6 +302,31 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte .show() } + private fun showThemeDialog() { + val currentTheme = prefs.getInt(KEY_THEME_MODE, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) + val options = arrayOf("Системная", "Светлая", "Тёмная") + val selectedIndex = when (currentTheme) { + AppCompatDelegate.MODE_NIGHT_NO -> 1 + AppCompatDelegate.MODE_NIGHT_YES -> 2 + else -> 0 + } + + AlertDialog.Builder(this) + .setTitle(R.string.appearance_settings) + .setSingleChoiceItems(options, selectedIndex) { dialog, which -> + val mode = when (which) { + 1 -> AppCompatDelegate.MODE_NIGHT_NO + 2 -> AppCompatDelegate.MODE_NIGHT_YES + else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM + } + prefs.edit().putInt(KEY_THEME_MODE, mode).apply() + AppCompatDelegate.setDefaultNightMode(mode) + dialog.dismiss() + } + .setNegativeButton(R.string.cancel, null) + .show() + } + private fun showAboutDialog() { AlertDialog.Builder(this) .setTitle(R.string.about) @@ -707,22 +736,16 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte } private fun addMessage(message: Message) { - val isAssistantMessage = !message.isUser val newPosition = messages.size - 1 messages.add(message) adapter.notifyItemInserted(newPosition) - // Scroll to beginning of assistant messages so user sees the sender name first - if (isAssistantMessage) { - recyclerView.post { - recyclerView.scrollToPosition(0) - // Then scroll to show the new message from beginning - val scrollAmount = (recyclerView.computeVerticalScrollExtent() - 200).coerceAtLeast(0) - recyclerView.post { - recyclerView.scrollBy(0, scrollAmount) - } - } + // Scroll to show new AI response + if (!message.isUser) { + recyclerView.postDelayed({ + recyclerView.scrollToPosition(newPosition) + }, 150) } val sessionId = currentSessionId @@ -738,10 +761,6 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte database.sessionDao().updateTimestamp(sessionId) } } - - recyclerView.postDelayed({ - recyclerView.scrollToPosition(messages.size - 1) - }, 100) } private fun saveMessageToDatabase(sessionId: Long?, content: String, isUser: Boolean, senderName: String?) { diff --git a/app/src/main/java/com/mistral/chat/ui/ProfilesAdapter.kt b/app/src/main/java/com/mistral/chat/ui/ProfilesAdapter.kt new file mode 100644 index 0000000..a7a1a3d --- /dev/null +++ b/app/src/main/java/com/mistral/chat/ui/ProfilesAdapter.kt @@ -0,0 +1,55 @@ +package com.mistral.chat.ui + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.mistral.chat.R +import com.mistral.chat.data.Profile + +class ProfilesAdapter( + private val profiles: List, + private val onProfileClick: (Profile) -> Unit, + private val onProfileLongClick: (Profile) -> Unit, + private val getSelectedProfileId: () -> Long? +) : RecyclerView.Adapter() { + + class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val icon: ImageView = view.findViewById(R.id.profileIcon) + val name: TextView = view.findViewById(R.id.profileName) + val checkmark: ImageView = view.findViewById(R.id.profileCheckmark) + } + + fun refresh() { + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_profile, parent, false) + return ViewHolder(view) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val profile = profiles[position] + val selectedId = getSelectedProfileId() + + holder.name.text = if (profile.name.length > 12) { + profile.name.take(12) + "..." + } else { + profile.name + } + holder.name.alpha = if (profile.id == selectedId) 1.0f else 0.7f + holder.checkmark.visibility = if (profile.id == selectedId) View.VISIBLE else View.GONE + + holder.itemView.setOnClickListener { onProfileClick(profile) } + holder.itemView.setOnLongClickListener { + onProfileLongClick(profile) + true + } + } + + override fun getItemCount(): Int = profiles.size +} \ No newline at end of file diff --git a/app/src/main/java/com/mistral/chat/ui/SessionsAdapter.kt b/app/src/main/java/com/mistral/chat/ui/SessionsAdapter.kt new file mode 100644 index 0000000..9947898 --- /dev/null +++ b/app/src/main/java/com/mistral/chat/ui/SessionsAdapter.kt @@ -0,0 +1,45 @@ +package com.mistral.chat.ui + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.mistral.chat.R +import com.mistral.chat.data.Session + +class SessionsAdapter( + private val sessions: List, + private val getCurrentSessionId: () -> Long?, + private val onSessionClick: (Session) -> Unit, + private val onSessionLongClick: (Session) -> Unit +) : RecyclerView.Adapter() { + + class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val title: TextView = view.findViewById(R.id.sessionTitle) + val checkmark: ImageView = view.findViewById(R.id.sessionCheckmark) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_session, parent, false) + return ViewHolder(view) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val session = sessions[position] + val currentId = getCurrentSessionId() + holder.title.text = session.title + holder.title.alpha = if (session.id == currentId) 1.0f else 0.7f + holder.checkmark.visibility = if (session.id == currentId) View.VISIBLE else View.GONE + + holder.itemView.setOnClickListener { onSessionClick(session) } + holder.itemView.setOnLongClickListener { + onSessionLongClick(session) + true + } + } + + override fun getItemCount(): Int = sessions.size +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml new file mode 100644 index 0000000..1cc0ebc --- /dev/null +++ b/app/src/main/res/drawable/ic_add.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_arrow_back.xml b/app/src/main/res/drawable/ic_arrow_back.xml new file mode 100644 index 0000000..3acb8a8 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_back.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_check.xml b/app/src/main/res/drawable/ic_check.xml new file mode 100644 index 0000000..67fd43c --- /dev/null +++ b/app/src/main/res/drawable/ic_check.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_delete.xml b/app/src/main/res/drawable/ic_delete.xml new file mode 100644 index 0000000..4fd798e --- /dev/null +++ b/app/src/main/res/drawable/ic_delete.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_edit.xml b/app/src/main/res/drawable/ic_edit.xml new file mode 100644 index 0000000..64c725a --- /dev/null +++ b/app/src/main/res/drawable/ic_edit.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_info.xml b/app/src/main/res/drawable/ic_info.xml new file mode 100644 index 0000000..6513bf2 --- /dev/null +++ b/app/src/main/res/drawable/ic_info.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_key.xml b/app/src/main/res/drawable/ic_key.xml new file mode 100644 index 0000000..12e9933 --- /dev/null +++ b/app/src/main/res/drawable/ic_key.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_menu_hamburger.xml b/app/src/main/res/drawable/ic_menu_hamburger.xml new file mode 100644 index 0000000..9b9c873 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_hamburger.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_person.xml b/app/src/main/res/drawable/ic_person.xml new file mode 100644 index 0000000..26cece3 --- /dev/null +++ b/app/src/main/res/drawable/ic_person.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml new file mode 100644 index 0000000..4fb4dc2 --- /dev/null +++ b/app/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_clear_all.xml b/app/src/main/res/layout/dialog_clear_all.xml new file mode 100644 index 0000000..5060023 --- /dev/null +++ b/app/src/main/res/layout/dialog_clear_all.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_profile_edit.xml b/app/src/main/res/layout/dialog_profile_edit.xml new file mode 100644 index 0000000..d61571e --- /dev/null +++ b/app/src/main/res/layout/dialog_profile_edit.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_profiles.xml b/app/src/main/res/layout/dialog_profiles.xml new file mode 100644 index 0000000..42bd6e6 --- /dev/null +++ b/app/src/main/res/layout/dialog_profiles.xml @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/app/src/main/res/layout/drawer_main_menu.xml b/app/src/main/res/layout/drawer_main_menu.xml new file mode 100644 index 0000000..0771fac --- /dev/null +++ b/app/src/main/res/layout/drawer_main_menu.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/drawer_profile_edit.xml b/app/src/main/res/layout/drawer_profile_edit.xml new file mode 100644 index 0000000..696ff31 --- /dev/null +++ b/app/src/main/res/layout/drawer_profile_edit.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/drawer_settings_menu.xml b/app/src/main/res/layout/drawer_settings_menu.xml new file mode 100644 index 0000000..e20facf --- /dev/null +++ b/app/src/main/res/layout/drawer_settings_menu.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/drawer_submenu.xml b/app/src/main/res/layout/drawer_submenu.xml new file mode 100644 index 0000000..e6e6c96 --- /dev/null +++ b/app/src/main/res/layout/drawer_submenu.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_drawer.xml b/app/src/main/res/layout/item_drawer.xml new file mode 100644 index 0000000..4f0e9b7 --- /dev/null +++ b/app/src/main/res/layout/item_drawer.xml @@ -0,0 +1,36 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_profile.xml b/app/src/main/res/layout/item_profile.xml new file mode 100644 index 0000000..3c12f6e --- /dev/null +++ b/app/src/main/res/layout/item_profile.xml @@ -0,0 +1,34 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_session.xml b/app/src/main/res/layout/item_session.xml new file mode 100644 index 0000000..04bc00c --- /dev/null +++ b/app/src/main/res/layout/item_session.xml @@ -0,0 +1,28 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/nav_header.xml b/app/src/main/res/layout/nav_header.xml new file mode 100644 index 0000000..b3fdd97 --- /dev/null +++ b/app/src/main/res/layout/nav_header.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/panel_right.xml b/app/src/main/res/layout/panel_right.xml new file mode 100644 index 0000000..d32b743 --- /dev/null +++ b/app/src/main/res/layout/panel_right.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/screen_api_key.xml b/app/src/main/res/layout/screen_api_key.xml new file mode 100644 index 0000000..5ba01f3 --- /dev/null +++ b/app/src/main/res/layout/screen_api_key.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/screen_appearance.xml b/app/src/main/res/layout/screen_appearance.xml new file mode 100644 index 0000000..43136ee --- /dev/null +++ b/app/src/main/res/layout/screen_appearance.xml @@ -0,0 +1,31 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/screen_base.xml b/app/src/main/res/layout/screen_base.xml new file mode 100644 index 0000000..a83b50c --- /dev/null +++ b/app/src/main/res/layout/screen_base.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/screen_clear_history.xml b/app/src/main/res/layout/screen_clear_history.xml new file mode 100644 index 0000000..82b8e6e --- /dev/null +++ b/app/src/main/res/layout/screen_clear_history.xml @@ -0,0 +1,32 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/screen_profile_edit.xml b/app/src/main/res/layout/screen_profile_edit.xml new file mode 100644 index 0000000..0077274 --- /dev/null +++ b/app/src/main/res/layout/screen_profile_edit.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/screen_profiles.xml b/app/src/main/res/layout/screen_profiles.xml new file mode 100644 index 0000000..1d6a8d2 --- /dev/null +++ b/app/src/main/res/layout/screen_profiles.xml @@ -0,0 +1,25 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/screen_session.xml b/app/src/main/res/layout/screen_session.xml new file mode 100644 index 0000000..9092799 --- /dev/null +++ b/app/src/main/res/layout/screen_session.xml @@ -0,0 +1,23 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/drawer_menu.xml b/app/src/main/res/menu/drawer_menu.xml new file mode 100644 index 0000000..54b2f49 --- /dev/null +++ b/app/src/main/res/menu/drawer_menu.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 39c539f..a459d7c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -42,11 +42,21 @@ Нет сессий OK Профили + Управление профилями ПРОФИЛИ Новый профиль - Управление профилями Редактировать Выбрано Удалить все профили Профиль: %s + Внешний вид + Сессия при запуске + Очистить историю + Удалить все сессии и сообщения? + Открывать последнюю сессию + Начинать новую сессию + Светлая + Тёмная + Назад + Системная \ No newline at end of file diff --git a/build.gradle b/build.gradle index 79bc0f9..788c34b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,6 @@ // Top-level build file plugins { id 'com.android.application' version '8.2.0' apply false - id 'org.jetbrains.kotlin.android' version '1.9.0' apply false + id 'org.jetbrains.kotlin.android' version '1.9.22' apply false + id 'com.google.devtools.ksp' version '1.9.22-1.0.17' apply false } \ No newline at end of file