feat: Refactor SearchResultsList component for improved loading and empty state handling
- Updated UI to use AnimatedVisibility for loading indicator and empty state message. - Enhanced user feedback with a centered loading spinner and message during search. - Improved layout and spacing for search results and user items. - Adjusted colors and sizes for better visual consistency. chore: Update gradle.properties to use new Java 17 path - Changed Java home path to reflect new installation location. docs: Add comprehensive README for project setup and development - Included quick start instructions, available emulators, technologies used, and useful commands. - Documented project structure and debugging tips. - Added license information and contact details for the development team. build: Add development scripts for quick rebuild and installation - Created dev.sh for fast rebuild and install without cleaning. - Added run.sh for a complete build and install process with emulator checks. - Introduced watch-dev.sh for automatic rebuild on file changes.
This commit is contained in:
69
.vscode/tasks.json
vendored
69
.vscode/tasks.json
vendored
@@ -29,6 +29,75 @@
|
|||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "./gradlew installDebug",
|
"command": "./gradlew installDebug",
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Start Emulator (Pixel 9 Pro)",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "$ANDROID_HOME/emulator/emulator -avd Pixel_9_Pro_API_35 &",
|
||||||
|
"problemMatcher": [],
|
||||||
|
"isBackground": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Start Emulator (Pixel 6)",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "$ANDROID_HOME/emulator/emulator -avd Pixel_6_API_Baklava &",
|
||||||
|
"problemMatcher": [],
|
||||||
|
"isBackground": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Start Emulator (Pixel 4)",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "$ANDROID_HOME/emulator/emulator -avd Pixel_4_API_35 &",
|
||||||
|
"problemMatcher": [],
|
||||||
|
"isBackground": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Check Connected Devices",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "adb devices",
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Build & Install on Device",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "./gradlew assembleDebug && ./gradlew installDebug",
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "🔥 Quick Dev - Fast Rebuild",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "./dev.sh",
|
||||||
|
"problemMatcher": [],
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "👀 Watch Mode - Auto Rebuild",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "./watch-dev.sh",
|
||||||
|
"problemMatcher": [],
|
||||||
|
"isBackground": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Launch App on Device",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "adb shell am start -n com.rosetta.messenger/.MainActivity",
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "View App Logs",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "adb logcat | grep -E 'rosetta|MainActivity'",
|
||||||
|
"problemMatcher": [],
|
||||||
|
"isBackground": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Uninstall App",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "adb uninstall com.rosetta.messenger",
|
||||||
|
"problemMatcher": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
181
README.md
Normal file
181
README.md
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
# 🚀 Rosetta Android
|
||||||
|
|
||||||
|
Безопасный мессенджер на базе криптографии для Android.
|
||||||
|
|
||||||
|
## ✅ Статус проекта
|
||||||
|
|
||||||
|
- ✅ Проект настроен и готов к разработке
|
||||||
|
- ✅ Все зависимости установлены
|
||||||
|
- ✅ Сборка проходит успешно
|
||||||
|
- ✅ APK файлы генерируются корректно
|
||||||
|
|
||||||
|
## 🎯 Быстрый старт
|
||||||
|
|
||||||
|
### 🚀 Запуск одной командой:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./run.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Этот скрипт автоматически запустит эмулятор, соберет приложение, установит и запустит его.
|
||||||
|
|
||||||
|
### ⚡ Быстрая разработка (Hot Reload):
|
||||||
|
|
||||||
|
**Вариант 1: Ручная пересборка**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./dev.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Быстрая пересборка и установка без очистки проекта (~10-20 секунд)
|
||||||
|
|
||||||
|
**Вариант 2: Автоматическая пересборка**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Установите fswatch (один раз)
|
||||||
|
brew install fswatch
|
||||||
|
|
||||||
|
# Запустите watch mode
|
||||||
|
./watch-dev.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
После запуска скрипт будет автоматически:
|
||||||
|
|
||||||
|
- Отслеживать изменения в `.kt` файлах
|
||||||
|
- Пересобирать проект
|
||||||
|
- Устанавливать на эмулятор
|
||||||
|
- Перезапускать приложение
|
||||||
|
|
||||||
|
💡 **Теперь просто редактируйте код и сохраняйте - изменения появятся на эмуляторе!**
|
||||||
|
|
||||||
|
### 🛠 Или вручную:
|
||||||
|
|
||||||
|
1. **Запустите эмулятор:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ANDROID_HOME/emulator/emulator -avd Pixel_9_Pro_API_35 &
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Соберите и установите:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew assembleDebug
|
||||||
|
./gradlew installDebug
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Запустите приложение:**
|
||||||
|
```bash
|
||||||
|
adb shell am start -n com.rosetta.messenger/.MainActivity
|
||||||
|
```
|
||||||
|
|
||||||
|
### Через VS Code:
|
||||||
|
|
||||||
|
1. Нажмите `Cmd+Shift+P`
|
||||||
|
2. Выберите "Tasks: Run Task"
|
||||||
|
3. Выберите нужную задачу (например, "Build & Install on Device")
|
||||||
|
|
||||||
|
## 📱 Доступные эмуляторы
|
||||||
|
|
||||||
|
- Pixel_9_Pro_API_35 (рекомендуется)
|
||||||
|
- Pixel_6_API_Baklava
|
||||||
|
- Pixel_4_API_35
|
||||||
|
- Pixel_2_API_35
|
||||||
|
|
||||||
|
## 🛠 Технологии
|
||||||
|
|
||||||
|
- **Язык:** Kotlin
|
||||||
|
- **UI:** Jetpack Compose + Material3
|
||||||
|
- **Навигация:** Navigation Component
|
||||||
|
- **Хранилище:** Room Database + DataStore
|
||||||
|
- **Безопасность:**
|
||||||
|
- BitcoinJ для криптографии
|
||||||
|
- Biometric Authentication
|
||||||
|
- BouncyCastle для шифрования
|
||||||
|
- **Анимации:** Lottie
|
||||||
|
|
||||||
|
## 📦 Сборка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Debug версия
|
||||||
|
./gradlew assembleDebug
|
||||||
|
|
||||||
|
# Release версия
|
||||||
|
./gradlew assembleRelease
|
||||||
|
|
||||||
|
# Очистка проекта
|
||||||
|
./gradlew clean
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📖 Документация
|
||||||
|
|
||||||
|
Подробная документация находится в [DEVELOPMENT.md](./DEVELOPMENT.md)
|
||||||
|
|
||||||
|
## 🐛 Отладка
|
||||||
|
|
||||||
|
Просмотр логов:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb logcat | grep rosetta
|
||||||
|
```
|
||||||
|
|
||||||
|
Или через VS Code Task: "View App Logs"
|
||||||
|
|
||||||
|
## 📂 Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
app/src/main/
|
||||||
|
├── java/com/rosetta/messenger/
|
||||||
|
│ ├── MainActivity.kt # Главная активность
|
||||||
|
│ ├── crypto/ # Криптография
|
||||||
|
│ ├── data/ # Управление данными
|
||||||
|
│ └── ui/ # UI компоненты
|
||||||
|
│ ├── auth/ # Авторизация
|
||||||
|
│ ├── chats/ # Чаты
|
||||||
|
│ ├── onboarding/ # Онбординг
|
||||||
|
│ └── theme/ # Тема
|
||||||
|
├── res/ # Ресурсы
|
||||||
|
└── AndroidManifest.xml # Манифест
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Полезные команды
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Проверить подключенные устройства
|
||||||
|
adb devices
|
||||||
|
|
||||||
|
# Удалить приложение
|
||||||
|
adb uninstall com.rosetta.messenger
|
||||||
|
|
||||||
|
# Очистить данные приложения
|
||||||
|
adb shell pm clear com.rosetta.messenger
|
||||||
|
|
||||||
|
# Остановить приложение
|
||||||
|
adb shell am force-stop com.rosetta.messenger
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚙️ Настройка VS Code
|
||||||
|
|
||||||
|
Расширения установлены автоматически:
|
||||||
|
|
||||||
|
- Kotlin Language (fwcd.kotlin)
|
||||||
|
- Language Support for Java (redhat.java)
|
||||||
|
- Gradle for Java (vscjava.vscode-gradle)
|
||||||
|
|
||||||
|
## 🎨 Особенности
|
||||||
|
|
||||||
|
- 🔐 Генерация seed-фразы (12/24 слова)
|
||||||
|
- 🔑 Создание криптографических ключей
|
||||||
|
- 🔒 Биометрическая аутентификация
|
||||||
|
- 💾 Защищенное хранилище данных
|
||||||
|
- 🎨 Современный UI с Material3
|
||||||
|
- 🌙 Поддержка темной темы
|
||||||
|
- ✨ Плавные анимации с Lottie
|
||||||
|
|
||||||
|
## 📝 Лицензия
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Разработка:** Rosetta Messenger Team
|
||||||
|
**Контакт:** k1ngsterr1
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("org.jetbrains.kotlin.android")
|
id("org.jetbrains.kotlin.android")
|
||||||
|
id("kotlin-kapt")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -79,12 +80,17 @@ dependencies {
|
|||||||
// Room for database
|
// Room for database
|
||||||
implementation("androidx.room:room-runtime:2.6.1")
|
implementation("androidx.room:room-runtime:2.6.1")
|
||||||
implementation("androidx.room:room-ktx:2.6.1")
|
implementation("androidx.room:room-ktx:2.6.1")
|
||||||
annotationProcessor("androidx.room:room-compiler:2.6.1")
|
kapt("androidx.room:room-compiler:2.6.1")
|
||||||
|
|
||||||
// Biometric authentication
|
// Biometric authentication
|
||||||
implementation("androidx.biometric:biometric:1.1.0")
|
implementation("androidx.biometric:biometric:1.1.0")
|
||||||
|
|
||||||
|
// Testing dependencies
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
|
testImplementation("io.mockk:mockk:1.13.8")
|
||||||
|
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
|
||||||
|
testImplementation("androidx.arch.core:core-testing:2.2.0")
|
||||||
|
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||||
androidTestImplementation(platform("androidx.compose:compose-bom:2023.10.01"))
|
androidTestImplementation(platform("androidx.compose:compose-bom:2023.10.01"))
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.*
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Bookmark
|
import androidx.compose.material.icons.filled.Bookmark
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
@@ -22,205 +21,197 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.rosetta.messenger.network.SearchUser
|
import com.rosetta.messenger.network.SearchUser
|
||||||
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
|
||||||
import com.rosetta.messenger.ui.components.VerifiedBadge
|
import com.rosetta.messenger.ui.components.VerifiedBadge
|
||||||
|
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Компонент отображения результатов поиска пользователей
|
* Компонент отображения результатов поиска пользователей Аналогичен результатам поиска в React
|
||||||
* Аналогичен результатам поиска в React Native приложении
|
* Native приложении
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun SearchResultsList(
|
fun SearchResultsList(
|
||||||
searchResults: List<SearchUser>,
|
searchResults: List<SearchUser>,
|
||||||
isSearching: Boolean,
|
isSearching: Boolean,
|
||||||
currentUserPublicKey: String,
|
currentUserPublicKey: String,
|
||||||
isDarkTheme: Boolean,
|
isDarkTheme: Boolean,
|
||||||
onUserClick: (SearchUser) -> Unit,
|
onUserClick: (SearchUser) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val backgroundColor = if (isDarkTheme) Color(0xFF2A2A2A) else Color.White
|
|
||||||
val borderColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8)
|
|
||||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
|
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
|
||||||
|
|
||||||
Column(
|
Box(modifier = modifier.fillMaxSize()) {
|
||||||
modifier = modifier
|
AnimatedVisibility(
|
||||||
.fillMaxWidth()
|
visible = isSearching,
|
||||||
.padding(horizontal = 16.dp)
|
enter = fadeIn(animationSpec = tween(durationMillis = 200)),
|
||||||
.clip(RoundedCornerShape(bottomStart = 12.dp, bottomEnd = 12.dp))
|
exit = fadeOut(animationSpec = tween(durationMillis = 200))
|
||||||
.background(backgroundColor)
|
) {
|
||||||
) {
|
// Индикатор загрузки в центре
|
||||||
// Разделительная линия сверху
|
Column(
|
||||||
Divider(
|
modifier = Modifier.fillMaxSize(),
|
||||||
color = borderColor,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
thickness = 1.dp
|
verticalArrangement = Arrangement.Center
|
||||||
)
|
) {
|
||||||
|
CircularProgressIndicator(
|
||||||
when {
|
modifier = Modifier.size(32.dp),
|
||||||
isSearching -> {
|
|
||||||
// Индикатор загрузки
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = 20.dp),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
color = if (isDarkTheme) Color(0xFF9E9E9E) else PrimaryBlue,
|
color = if (isDarkTheme) Color(0xFF9E9E9E) else PrimaryBlue,
|
||||||
strokeWidth = 2.dp
|
strokeWidth = 3.dp
|
||||||
)
|
)
|
||||||
}
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
Text(text = "Searching...", fontSize = 14.sp, color = secondaryTextColor)
|
||||||
}
|
}
|
||||||
searchResults.isEmpty() -> {
|
}
|
||||||
// Пустые результаты - подсказка
|
|
||||||
Box(
|
AnimatedVisibility(
|
||||||
modifier = Modifier
|
visible = !isSearching && searchResults.isEmpty(),
|
||||||
.fillMaxWidth()
|
enter = fadeIn(animationSpec = tween(durationMillis = 300)),
|
||||||
.padding(vertical = 20.dp),
|
exit = fadeOut(animationSpec = tween(durationMillis = 200))
|
||||||
contentAlignment = Alignment.Center
|
) {
|
||||||
) {
|
// Подсказка в центре экрана
|
||||||
Text(
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
text = "You can search by username or public key.",
|
Text(
|
||||||
fontSize = 12.sp,
|
text = "Search by username or public key",
|
||||||
|
fontSize = 15.sp,
|
||||||
color = secondaryTextColor
|
color = secondaryTextColor
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else -> {
|
}
|
||||||
// Список результатов
|
|
||||||
LazyColumn(
|
AnimatedVisibility(
|
||||||
modifier = Modifier
|
visible = !isSearching && searchResults.isNotEmpty(),
|
||||||
.fillMaxWidth()
|
enter = fadeIn(animationSpec = tween(durationMillis = 300)),
|
||||||
.heightIn(max = 300.dp)
|
exit = fadeOut(animationSpec = tween(durationMillis = 200))
|
||||||
) {
|
) {
|
||||||
itemsIndexed(searchResults) { index, user ->
|
// Список результатов без серой плашки
|
||||||
SearchResultItem(
|
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||||
|
itemsIndexed(searchResults) { index, user ->
|
||||||
|
SearchResultItem(
|
||||||
user = user,
|
user = user,
|
||||||
isOwnAccount = user.publicKey == currentUserPublicKey,
|
isOwnAccount = user.publicKey == currentUserPublicKey,
|
||||||
isDarkTheme = isDarkTheme,
|
isDarkTheme = isDarkTheme,
|
||||||
isLastItem = index == searchResults.size - 1,
|
isLastItem = index == searchResults.size - 1,
|
||||||
onClick = { onUserClick(user) }
|
onClick = { onUserClick(user) }
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Элемент результата поиска - пользователь */
|
||||||
* Элемент результата поиска - пользователь
|
|
||||||
*/
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SearchResultItem(
|
private fun SearchResultItem(
|
||||||
user: SearchUser,
|
user: SearchUser,
|
||||||
isOwnAccount: Boolean,
|
isOwnAccount: Boolean,
|
||||||
isDarkTheme: Boolean,
|
isDarkTheme: Boolean,
|
||||||
isLastItem: Boolean,
|
isLastItem: Boolean,
|
||||||
onClick: () -> Unit
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||||
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
|
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
|
||||||
val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8)
|
val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8)
|
||||||
|
|
||||||
// Получаем цвета аватара
|
// Получаем цвета аватара
|
||||||
val avatarColors = getAvatarColor(
|
val avatarColors =
|
||||||
if (isOwnAccount) "SavedMessages" else (user.title.ifEmpty { user.publicKey }),
|
getAvatarColor(
|
||||||
isDarkTheme
|
if (isOwnAccount) "SavedMessages" else (user.title.ifEmpty { user.publicKey }),
|
||||||
)
|
isDarkTheme
|
||||||
|
)
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier =
|
||||||
.fillMaxWidth()
|
Modifier.fillMaxWidth()
|
||||||
.clickable(onClick = onClick)
|
.clickable(onClick = onClick)
|
||||||
.padding(horizontal = 12.dp, vertical = 10.dp),
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
// Аватар
|
// Аватар
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier =
|
||||||
.size(40.dp)
|
Modifier.size(48.dp)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(if (isOwnAccount) PrimaryBlue else avatarColors.backgroundColor),
|
.background(
|
||||||
contentAlignment = Alignment.Center
|
if (isOwnAccount) PrimaryBlue
|
||||||
|
else avatarColors.backgroundColor
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
if (isOwnAccount) {
|
if (isOwnAccount) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Bookmark,
|
Icons.Default.Bookmark,
|
||||||
contentDescription = "Saved Messages",
|
contentDescription = "Saved Messages",
|
||||||
tint = Color.White,
|
tint = Color.White,
|
||||||
modifier = Modifier.size(20.dp)
|
modifier = Modifier.size(20.dp)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Text(
|
Text(
|
||||||
text = if (user.title.isNotEmpty()) {
|
text =
|
||||||
getInitials(user.title)
|
if (user.title.isNotEmpty()) {
|
||||||
} else {
|
getInitials(user.title)
|
||||||
user.publicKey.take(2).uppercase()
|
} else {
|
||||||
},
|
user.publicKey.take(2).uppercase()
|
||||||
fontSize = 14.sp,
|
},
|
||||||
fontWeight = FontWeight.Bold,
|
fontSize = 14.sp,
|
||||||
color = avatarColors.textColor
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = avatarColors.textColor
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
|
||||||
// Информация о пользователе
|
// Информация о пользователе
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
// Имя и значок верификации
|
// Имя и значок верификации
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = if (isOwnAccount) {
|
text =
|
||||||
"Saved Messages"
|
if (isOwnAccount) {
|
||||||
} else {
|
"Saved Messages"
|
||||||
user.title.ifEmpty { user.publicKey.take(10) }
|
} else {
|
||||||
},
|
user.title.ifEmpty { user.publicKey.take(10) }
|
||||||
fontSize = 14.sp,
|
},
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontSize = 16.sp,
|
||||||
color = textColor,
|
fontWeight = FontWeight.SemiBold,
|
||||||
maxLines = 1,
|
color = textColor,
|
||||||
overflow = TextOverflow.Ellipsis
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
|
|
||||||
// Значок верификации
|
// Значок верификации
|
||||||
if (!isOwnAccount && user.verified > 0) {
|
if (!isOwnAccount && user.verified > 0) {
|
||||||
VerifiedBadge(
|
VerifiedBadge(verified = user.verified, size = 16)
|
||||||
verified = user.verified,
|
|
||||||
size = 16
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
|
||||||
// Юзернейм или публичный ключ
|
// Юзернейм или публичный ключ
|
||||||
Text(
|
Text(
|
||||||
text = if (isOwnAccount) {
|
text =
|
||||||
"Notes"
|
if (isOwnAccount) {
|
||||||
} else {
|
"Notes"
|
||||||
"@${user.username.ifEmpty { user.publicKey.take(10) + "..." }}"
|
} else {
|
||||||
},
|
"@${user.username.ifEmpty { user.publicKey.take(10) + "..." }}"
|
||||||
fontSize = 12.sp,
|
},
|
||||||
color = secondaryTextColor,
|
fontSize = 14.sp,
|
||||||
maxLines = 1,
|
color = secondaryTextColor,
|
||||||
overflow = TextOverflow.Ellipsis
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Разделитель между элементами
|
// Разделитель между элементами
|
||||||
if (!isLastItem) {
|
if (!isLastItem) {
|
||||||
Divider(
|
Divider(
|
||||||
modifier = Modifier.padding(start = 64.dp),
|
modifier = Modifier.padding(start = 80.dp),
|
||||||
color = dividerColor,
|
color = dividerColor,
|
||||||
thickness = 0.5.dp
|
thickness = 0.5.dp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
35
dev.sh
Executable file
35
dev.sh
Executable file
@@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Quick Dev Script - Fast rebuild and install
|
||||||
|
|
||||||
|
echo "🔄 Quick rebuild and install..."
|
||||||
|
|
||||||
|
# Check if emulator is running
|
||||||
|
if ! adb devices | grep -q "emulator"; then
|
||||||
|
echo "⚠️ Эмулятор не запущен. Запускаю..."
|
||||||
|
$ANDROID_HOME/emulator/emulator -avd Pixel_9_Pro_API_35 &
|
||||||
|
adb wait-for-device
|
||||||
|
|
||||||
|
# Wait for boot complete
|
||||||
|
while [ "$(adb shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')" != "1" ]; do
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "📱 Эмулятор готов!"
|
||||||
|
|
||||||
|
# Fast install (no clean)
|
||||||
|
./gradlew installDebug --no-daemon
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "✅ Установлено!"
|
||||||
|
|
||||||
|
# Auto-launch
|
||||||
|
adb shell am start -n com.rosetta.messenger/.MainActivity
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "🎉 Приложение запущено!"
|
||||||
|
echo "📝 Для просмотра логов: adb logcat | grep rosetta"
|
||||||
|
else
|
||||||
|
echo "❌ Ошибка установки"
|
||||||
|
fi
|
||||||
@@ -7,7 +7,7 @@ android.useAndroidX=true
|
|||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
|
|
||||||
# Use Java 17 for build
|
# Use Java 17 for build
|
||||||
org.gradle.java.home=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home
|
org.gradle.java.home=/opt/homebrew/Cellar/openjdk@17/17.0.14/libexec/openjdk.jdk/Contents/Home
|
||||||
|
|
||||||
# Increase heap size for Gradle
|
# Increase heap size for Gradle
|
||||||
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError --add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
|
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError --add-opens=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-opens=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
|
||||||
|
|||||||
57
run.sh
Executable file
57
run.sh
Executable file
@@ -0,0 +1,57 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Rosetta Android - Quick Start Script
|
||||||
|
|
||||||
|
echo "🚀 Rosetta Android Quick Start"
|
||||||
|
echo "================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if emulator is already running
|
||||||
|
if adb devices | grep -q "emulator"; then
|
||||||
|
echo "✅ Эмулятор уже запущен"
|
||||||
|
else
|
||||||
|
echo "📱 Запускаем эмулятор (Pixel 9 Pro API 35)..."
|
||||||
|
$ANDROID_HOME/emulator/emulator -avd Pixel_9_Pro_API_35 &
|
||||||
|
EMULATOR_PID=$!
|
||||||
|
|
||||||
|
echo "⏳ Ожидаем загрузки эмулятора..."
|
||||||
|
adb wait-for-device
|
||||||
|
|
||||||
|
# Wait for boot to complete
|
||||||
|
while [ "$(adb shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')" != "1" ]; do
|
||||||
|
echo " Загрузка..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "✅ Эмулятор готов!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "🔨 Собираем приложение..."
|
||||||
|
./gradlew assembleDebug
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "✅ Сборка успешна!"
|
||||||
|
echo ""
|
||||||
|
echo "📦 Устанавливаем приложение..."
|
||||||
|
./gradlew installDebug
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "✅ Приложение установлено!"
|
||||||
|
echo ""
|
||||||
|
echo "🎉 Запускаем приложение..."
|
||||||
|
adb shell am start -n com.rosetta.messenger/.MainActivity
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ Готово! Приложение запущено на эмуляторе"
|
||||||
|
echo ""
|
||||||
|
echo "📋 Для просмотра логов запустите:"
|
||||||
|
echo " adb logcat | grep rosetta"
|
||||||
|
else
|
||||||
|
echo "❌ Ошибка установки приложения"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "❌ Ошибка сборки приложения"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
58
watch-dev.sh
Executable file
58
watch-dev.sh
Executable file
@@ -0,0 +1,58 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Auto-rebuild on file changes
|
||||||
|
# Requires: fswatch (install via: brew install fswatch)
|
||||||
|
|
||||||
|
echo "👀 Watching for changes..."
|
||||||
|
echo "📝 Редактируйте файлы - приложение будет пересобираться автоматически"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if fswatch is installed
|
||||||
|
if ! command -v fswatch &> /dev/null; then
|
||||||
|
echo "⚠️ fswatch не установлен!"
|
||||||
|
echo "📦 Установите через: brew install fswatch"
|
||||||
|
echo ""
|
||||||
|
echo "Или используйте ./dev.sh для ручной пересборки"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure emulator is running
|
||||||
|
if ! adb devices | grep -q "emulator"; then
|
||||||
|
echo "🚀 Запускаю эмулятор..."
|
||||||
|
$ANDROID_HOME/emulator/emulator -avd Pixel_9_Pro_API_35 &
|
||||||
|
adb wait-for-device
|
||||||
|
|
||||||
|
while [ "$(adb shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')" != "1" ]; do
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Эмулятор готов!"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Initial build
|
||||||
|
./gradlew installDebug
|
||||||
|
adb shell am start -n com.rosetta.messenger/.MainActivity
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "👂 Слежу за изменениями в app/src/main/java..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Watch for changes in Kotlin files
|
||||||
|
fswatch -o app/src/main/java | while read f; do
|
||||||
|
echo "🔄 Изменение обнаружено! Пересобираю..."
|
||||||
|
./gradlew installDebug --no-daemon
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "✅ Обновлено! Перезапускаю приложение..."
|
||||||
|
adb shell am force-stop com.rosetta.messenger
|
||||||
|
sleep 1
|
||||||
|
adb shell am start -n com.rosetta.messenger/.MainActivity
|
||||||
|
echo "🎉 Готово!"
|
||||||
|
else
|
||||||
|
echo "❌ Ошибка сборки"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "👂 Жду следующих изменений..."
|
||||||
|
done
|
||||||
Reference in New Issue
Block a user