diff --git a/compose/.gradle/8.0/executionHistory/executionHistory.bin b/compose/.gradle/8.0/executionHistory/executionHistory.bin index 65d6285..3ce9ac9 100644 Binary files a/compose/.gradle/8.0/executionHistory/executionHistory.bin and b/compose/.gradle/8.0/executionHistory/executionHistory.bin differ diff --git a/compose/.gradle/8.0/executionHistory/executionHistory.lock b/compose/.gradle/8.0/executionHistory/executionHistory.lock index 15f3bc6..e89ba80 100644 Binary files a/compose/.gradle/8.0/executionHistory/executionHistory.lock and b/compose/.gradle/8.0/executionHistory/executionHistory.lock differ diff --git a/compose/.gradle/8.0/fileHashes/fileHashes.bin b/compose/.gradle/8.0/fileHashes/fileHashes.bin index c27337d..331bcfc 100644 Binary files a/compose/.gradle/8.0/fileHashes/fileHashes.bin and b/compose/.gradle/8.0/fileHashes/fileHashes.bin differ diff --git a/compose/.gradle/8.0/fileHashes/fileHashes.lock b/compose/.gradle/8.0/fileHashes/fileHashes.lock index fc3efbf..d5fcbb6 100644 Binary files a/compose/.gradle/8.0/fileHashes/fileHashes.lock and b/compose/.gradle/8.0/fileHashes/fileHashes.lock differ diff --git a/compose/.gradle/8.0/fileHashes/resourceHashesCache.bin b/compose/.gradle/8.0/fileHashes/resourceHashesCache.bin index bd04dcf..143cac9 100644 Binary files a/compose/.gradle/8.0/fileHashes/resourceHashesCache.bin and b/compose/.gradle/8.0/fileHashes/resourceHashesCache.bin differ diff --git a/compose/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/compose/.gradle/buildOutputCleanup/buildOutputCleanup.lock index cacd50e..8625175 100644 Binary files a/compose/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/compose/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/compose/.gradle/buildOutputCleanup/outputFiles.bin b/compose/.gradle/buildOutputCleanup/outputFiles.bin index 9346326..cf52a3a 100644 Binary files a/compose/.gradle/buildOutputCleanup/outputFiles.bin and b/compose/.gradle/buildOutputCleanup/outputFiles.bin differ diff --git a/compose/.gradle/file-system.probe b/compose/.gradle/file-system.probe index e3bf28d..8713283 100644 Binary files a/compose/.gradle/file-system.probe and b/compose/.gradle/file-system.probe differ diff --git a/compose/.idea/modules.xml b/compose/.idea/modules.xml new file mode 100644 index 0000000..8edfb25 --- /dev/null +++ b/compose/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/compose/.idea/modules/app/pmu-demo.app.androidTest.iml b/compose/.idea/modules/app/pmu-demo.app.androidTest.iml new file mode 100644 index 0000000..f94dd0a --- /dev/null +++ b/compose/.idea/modules/app/pmu-demo.app.androidTest.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/compose/.idea/modules/app/pmu-demo.app.iml b/compose/.idea/modules/app/pmu-demo.app.iml new file mode 100644 index 0000000..c8f76d2 --- /dev/null +++ b/compose/.idea/modules/app/pmu-demo.app.iml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/compose/app/src/main/java/ru/ulstu/is/pmu/composeui/navigation/MainNavbar.kt b/compose/app/src/main/java/ru/ulstu/is/pmu/composeui/navigation/MainNavbar.kt index 73ba94f..7872ce1 100644 --- a/compose/app/src/main/java/ru/ulstu/is/pmu/composeui/navigation/MainNavbar.kt +++ b/compose/app/src/main/java/ru/ulstu/is/pmu/composeui/navigation/MainNavbar.kt @@ -17,6 +17,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExposedDropdownMenuBox @@ -54,7 +55,6 @@ import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import ru.ulstu.`is`.pmu.R -import ru.ulstu.`is`.pmu.tank.composeui.StudentView import ru.ulstu.`is`.pmu.ui.theme.CustomDark import ru.ulstu.`is`.pmu.ui.theme.CustomOrange import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme @@ -66,6 +66,7 @@ import ru.ulstu.`is`.pmu.tank.composeui.TankList import ru.ulstu.`is`.pmu.tanks.composeui.Account import ru.ulstu.`is`.pmu.tanks.composeui.Constructor import ru.ulstu.`is`.pmu.tanks.composeui.Hangar +import ru.ulstu.`is`.pmu.ui.theme.CustomYellow @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -149,12 +150,6 @@ fun Navhost( ) { Constructor(navController) } composable(Screen.Hangar.route) { Hangar(navController) } composable(Screen.Account.route) { Account(navController) } - composable( - Screen.StudentView.route, - arguments = listOf(navArgument("id") { type = NavType.IntType }) - ) { backStackEntry -> - backStackEntry.arguments?.let { StudentView(it.getInt("id")) } - } } } @@ -251,6 +246,12 @@ fun MainNavbar(navController: NavController) { if(currentScreen?.route == Screen.TankList.route){ DropDownList(navController) Button( + modifier = Modifier + .width(200.dp) + .height(50.dp), + colors = ButtonDefaults.buttonColors( + containerColor = CustomOrange, + contentColor = CustomDark), onClick = { val route = Screen.Constructor.route.replace("{id}", 0.toString()) navController.navigate(route) diff --git a/compose/app/src/main/java/ru/ulstu/is/pmu/composeui/navigation/Screen.kt b/compose/app/src/main/java/ru/ulstu/is/pmu/composeui/navigation/Screen.kt index 1149120..ad80cf7 100644 --- a/compose/app/src/main/java/ru/ulstu/is/pmu/composeui/navigation/Screen.kt +++ b/compose/app/src/main/java/ru/ulstu/is/pmu/composeui/navigation/Screen.kt @@ -41,7 +41,6 @@ enum class Screen( companion object { val bottomBarItems = listOf( TankList, - Constructor, Hangar, Account ) diff --git a/compose/app/src/main/java/ru/ulstu/is/pmu/tank/composeui/StudentView.kt b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/composeui/StudentView.kt deleted file mode 100644 index c60a51c..0000000 --- a/compose/app/src/main/java/ru/ulstu/is/pmu/tank/composeui/StudentView.kt +++ /dev/null @@ -1,74 +0,0 @@ -package ru.ulstu.`is`.pmu.tank.composeui - -import android.content.res.Configuration -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import ru.ulstu.`is`.pmu.R -import ru.ulstu.`is`.pmu.tank.model.getStudents -import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun StudentView(id: Int) { - val student = getStudents()[id] - Column( - Modifier - .fillMaxWidth() - .padding(all = 10.dp) - ) { - OutlinedTextField(modifier = Modifier.fillMaxWidth(), - value = student.firstName, onValueChange = {}, readOnly = true, - label = { - Text(stringResource(id = R.string.student_firstname)) - } - ) - OutlinedTextField(modifier = Modifier.fillMaxWidth(), - value = student.lastName, onValueChange = {}, readOnly = true, - label = { - Text(stringResource(id = R.string.student_lastname)) - } - ) - OutlinedTextField(modifier = Modifier.fillMaxWidth(), - value = student.group, onValueChange = {}, readOnly = true, - label = { - Text(stringResource(id = R.string.student_group)) - } - ) - OutlinedTextField(modifier = Modifier.fillMaxWidth(), - value = student.phone, onValueChange = {}, readOnly = true, - label = { - Text(stringResource(id = R.string.student_phone)) - } - ) - OutlinedTextField(modifier = Modifier.fillMaxWidth(), - value = student.email, onValueChange = {}, readOnly = true, - label = { - Text(stringResource(id = R.string.student_email)) - } - ) - } -} - -@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) -@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -fun StudentViewPreview() { - PmudemoTheme { - Surface( - color = MaterialTheme.colorScheme.background - ) { - StudentView(id = 0) - } - } -} \ No newline at end of file diff --git a/compose/app/src/main/java/ru/ulstu/is/pmu/tank/composeui/edit/TankEditViewModel.kt b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/composeui/edit/TankEditViewModel.kt index 4140e7b..94dae88 100644 --- a/compose/app/src/main/java/ru/ulstu/is/pmu/tank/composeui/edit/TankEditViewModel.kt +++ b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/composeui/edit/TankEditViewModel.kt @@ -1,5 +1,6 @@ package ru.ulstu.`is`.pmu.tank.composeui.edit +import android.graphics.Bitmap import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -7,6 +8,7 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.application.ui.getEmptyBitmap import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch @@ -44,10 +46,11 @@ class TankEditViewModel( suspend fun saveTank() { if (validateInput()) { + val image: Bitmap = tankUiState.tankDetails.image!! if (tankUid > 0) { - tankRepository.updateTank(tankUiState.tankDetails.toTank(tankUid)) + tankRepository.updateTank(tankUiState.tankDetails.toTank(tankUid), image = image) } else { - tankRepository.insertTank(tankUiState.tankDetails.toTank()) + tankRepository.insertTank(tankUiState.tankDetails.toTank(), image = image) } } } @@ -56,9 +59,9 @@ class TankEditViewModel( return with(uiState) { name.isNotBlank() && price > 0 - && image > 0 && levelId!! > 0 && nationId!! > 0 + && image != null } } } @@ -71,26 +74,28 @@ data class TankUiState( data class TankDetails( val name: String = "", val price: Int = 0, - val image: Int = 0, val levelId: Long? = 0, val nationId: Long? = 0, + val miniature: Bitmap = getEmptyBitmap(), + val image: Bitmap? = null, + val imageId: Long = -1 ) fun TankDetails.toTank(uid: Long = 0): Tank = Tank( tankId = uid, name = name, price = price, - image = image, levelId = levelId, - nationId = nationId + nationId = nationId, + imageId = imageId ) fun Tank.toDetails(): TankDetails = TankDetails( name = name, price = price, - image = image, levelId = levelId, - nationId = nationId + nationId = nationId, + imageId = imageId ) fun Tank.toUiState(isEntryValid: Boolean = false): TankUiState = TankUiState( diff --git a/compose/app/src/main/java/ru/ulstu/is/pmu/tank/dao/TankDao.kt b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/dao/TankDao.kt index cb27a49..37f720f 100644 --- a/compose/app/src/main/java/ru/ulstu/is/pmu/tank/dao/TankDao.kt +++ b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/dao/TankDao.kt @@ -23,8 +23,9 @@ interface TankDao { //получаем все танки пользователя по его Id @Query( - "SELECT t.tankId, t.name, t.price, t.image, l.level, n.nationName FROM UserTankCrossRef AS ut " + + "SELECT t.tankId, t.name, t.price, t.image_id, l.level, n.nationName FROM UserTankCrossRef AS ut " + "LEFT JOIN tanks as t on ut.tankId = t.tankId " + + "LEFT JOIN tank_images as ti on t.image_id = ti.image_id " + "LEFT JOIN levels as l on t.levelId = l.uid " + "LEFT JOIN nations as n on t.nationId = n.uid " + "WHERE ut.userId = :uid GROUP BY t.nationId, t.levelId ORDER BY t.nationId" @@ -39,4 +40,7 @@ interface TankDao { @Delete suspend fun delete(tank: Tank) + + @Query("DELETE FROM tanks WHERE tankId = :id") + suspend fun delete(id: Long) } \ No newline at end of file diff --git a/compose/app/src/main/java/ru/ulstu/is/pmu/tank/dao/TankImageDao.kt b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/dao/TankImageDao.kt new file mode 100644 index 0000000..c62772e --- /dev/null +++ b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/dao/TankImageDao.kt @@ -0,0 +1,26 @@ +package ru.ulstu.`is`.pmu.tank.dao + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Update +import ru.ulstu.`is`.pmu.tank.model.TankImage + +@Dao +interface TankImageDao { + @Insert + suspend fun insert(productImage: TankImage): Long + + @Insert + suspend fun insertMany(productImages: List) + + @Update + suspend fun update(productImage: TankImage) + + @Delete + suspend fun delete(productImage: TankImage) + + @Query("DELETE FROM tank_images WHERE image_id = :id") + suspend fun delete(id: Long) +} \ No newline at end of file diff --git a/compose/app/src/main/java/ru/ulstu/is/pmu/tank/database/AppContainer.kt b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/database/AppContainer.kt index a467a71..829bfe8 100644 --- a/compose/app/src/main/java/ru/ulstu/is/pmu/tank/database/AppContainer.kt +++ b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/database/AppContainer.kt @@ -25,7 +25,9 @@ class AppDataContainer(private val context: Context) : AppContainer { OfflineNationRepository(AppDatabase.getInstance(context).nationDao()) } override val tankRepository: TankRepository by lazy { - OfflineTankRepository(AppDatabase.getInstance(context).tankDao()) + OfflineTankRepository( + AppDatabase.getInstance(context).tankDao(), + tankImageDao = AppDatabase.getInstance(context).tankImageDao()) } override val userRepository: UserRepository by lazy { OfflineUserRepository(AppDatabase.getInstance(context).userDao()) diff --git a/compose/app/src/main/java/ru/ulstu/is/pmu/tank/database/AppDatabase.kt b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/database/AppDatabase.kt index 60f989d..246f672 100644 --- a/compose/app/src/main/java/ru/ulstu/is/pmu/tank/database/AppDatabase.kt +++ b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/database/AppDatabase.kt @@ -12,6 +12,7 @@ import ru.ulstu.`is`.pmu.R import ru.ulstu.`is`.pmu.tank.dao.LevelDao import ru.ulstu.`is`.pmu.tank.dao.NationDao import ru.ulstu.`is`.pmu.tank.dao.TankDao +import ru.ulstu.`is`.pmu.tank.dao.TankImageDao import ru.ulstu.`is`.pmu.tank.dao.UserDao import ru.ulstu.`is`.pmu.tank.model.Level import ru.ulstu.`is`.pmu.tank.model.Nation @@ -27,14 +28,15 @@ abstract class AppDatabase : RoomDatabase() { abstract fun levelDao(): LevelDao abstract fun tankDao() : TankDao abstract fun userDao() : UserDao + abstract fun tankImageDao() : TankImageDao companion object { - private const val DB_NAME: String = "12-db" + private const val DB_NAME: String = "13-db" @Volatile private var INSTANCE: AppDatabase? = null - private suspend fun populateDatabase() { + private suspend fun populateDatabase(context: Context) { INSTANCE?.let { database -> // Nations val nationDao = database.nationDao() @@ -84,19 +86,23 @@ abstract class AppDatabase : RoomDatabase() { levelDao.insert(level9) levelDao.insert(level10) + //images + val tankImageDao = database.tankImageDao() + tankImageDao.insertMany(PrepopulateStore.getProductImages(context)) + //Tanks val tankDao = database.tankDao() - val tank1 = Tank(20L,"МС-1", 1000, R.drawable.t_34_85, level1.uid, nation1.uid) - val tank2 = Tank(21L, "Т-34-85", 960000, R.drawable.t_34_85, level6.uid, nation1.uid) - val tank10 = Tank(22L, "Pershing", 1260000, R.drawable.sherman, level8.uid, nation3.uid) - val tank6 = Tank(23L, "Ferdinand", 2500000, R.drawable.tiger_1, level8.uid, nation2.uid) - val tank3 = Tank(24L, "ИС-2", 1230000, R.drawable.t_34_85, level7.uid, nation1.uid) - val tank4 = Tank(25L, "ИСУ-152", 2350000, R.drawable.t_34_85, level8.uid, nation1.uid) - val tank5 = Tank(26L, "Tiger 1", 1430000, R.drawable.tiger_1, level7.uid, nation2.uid) - val tank7 = Tank(27L, "Tiger 2", 2500000, R.drawable.tiger_1, level8.uid, nation2.uid) - val tank8 = Tank(28L, "Panther", 1350000, R.drawable.tiger_1, level7.uid, nation2.uid) - val tank9 = Tank(29L, "M4A2E3", 990000, R.drawable.sherman, level6.uid, nation3.uid) - val tank11 = Tank(30L, "Hellcat", 940000, R.drawable.sherman, level7.uid, nation3.uid) + val tank1 = Tank(20L,"МС-1", 1000, 1L, level1.uid, nation1.uid) + val tank2 = Tank(21L, "Т-34-85", 960000, 1L, level6.uid, nation1.uid) + val tank10 = Tank(22L, "Pershing", 1260000,3L, level8.uid, nation3.uid) + val tank6 = Tank(23L, "Ferdinand", 2500000, 2L, level8.uid, nation2.uid) + val tank3 = Tank(24L, "ИС-2", 1230000, 1L, level7.uid, nation1.uid) + val tank4 = Tank(25L, "ИСУ-152", 2350000, 1L, level8.uid, nation1.uid) + val tank5 = Tank(26L, "Tiger 1", 1430000,2L, level7.uid, nation2.uid) + val tank7 = Tank(27L, "Tiger 2", 2500000, 2L, level8.uid, nation2.uid) + val tank8 = Tank(28L, "Panther", 1350000, 2L, level7.uid, nation2.uid) + val tank9 = Tank(29L, "M4A2E3", 990000, 3L, level6.uid, nation3.uid) + val tank11 = Tank(30L, "Hellcat", 940000, 3L, level7.uid, nation3.uid) tankDao.insert(tank1) tankDao.insert(tank2) @@ -135,7 +141,7 @@ abstract class AppDatabase : RoomDatabase() { override fun onCreate(db: SupportSQLiteDatabase) { super.onCreate(db) CoroutineScope(Dispatchers.IO).launch { - populateDatabase() + populateDatabase(appContext) } } }) diff --git a/compose/app/src/main/java/ru/ulstu/is/pmu/tank/database/Converters.kt b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/database/Converters.kt new file mode 100644 index 0000000..eca090b --- /dev/null +++ b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/database/Converters.kt @@ -0,0 +1,44 @@ +package ru.ulstu.`is`.pmu.tank.database + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import androidx.room.TypeConverter +import java.io.ByteArrayOutputStream +import java.util.Date + +class Converters { + + /* + @TypeConverter + fun toUserRole(value: Int): UserRole { + return enumValues()[value] + } + + @TypeConverter + fun fromUserRole(value: UserRole): Int { + return value.ordinal + } + */ + + @TypeConverter + fun toDate(value: Long?): Date? { + return value?.let { Date(it) } + } + + @TypeConverter + fun fromDate(date: Date?): Long? { + return date?.time + } + + @TypeConverter + fun fromBitmap(bitmap: Bitmap): ByteArray { + val outputStream = ByteArrayOutputStream() + bitmap.compress(Bitmap.CompressFormat.PNG, 1, outputStream) + return outputStream.toByteArray() + } + + @TypeConverter + fun toBitmap(byteArray: ByteArray): Bitmap { + return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) + } +} \ No newline at end of file diff --git a/compose/app/src/main/java/ru/ulstu/is/pmu/tank/database/PrepopulateStore.kt b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/database/PrepopulateStore.kt new file mode 100644 index 0000000..df98e57 --- /dev/null +++ b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/database/PrepopulateStore.kt @@ -0,0 +1,33 @@ +package ru.ulstu.`is`.pmu.tank.database + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import com.application.ui.miniatureBound +import com.application.ui.resize +import com.application.ui.tankImageBound +import ru.ulstu.`is`.pmu.tank.model.TankImage + +class PrepopulateStore { + companion object { + fun getProductImages(context: Context): List { + return listOf( + TankImage(id = 1, data = getProductImage(context, 1)), + TankImage(id = 2, data = getProductImage(context, 2)), + TankImage(id = 3, data = getProductImage(context, 3)) + ) + } + + private fun getProductMiniature(context: Context, imageId: Int): Bitmap { + val inputStream = context.assets.open("${imageId}.jpg") + val bitmap = BitmapFactory.decodeStream(inputStream) + return bitmap.resize(miniatureBound) + } + + private fun getProductImage(context: Context, imageId: Int): Bitmap { + val inputStream = context.assets.open("${imageId}.jpg") + val bitmap = BitmapFactory.decodeStream(inputStream) + return bitmap.resize(tankImageBound) + } + } +} \ No newline at end of file diff --git a/compose/app/src/main/java/ru/ulstu/is/pmu/tank/model/Tank.kt b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/model/Tank.kt index 65156de..72d2ad9 100644 --- a/compose/app/src/main/java/ru/ulstu/is/pmu/tank/model/Tank.kt +++ b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/model/Tank.kt @@ -2,12 +2,22 @@ package ru.ulstu.`is`.pmu.tank.model import androidx.room.ColumnInfo import androidx.room.Entity +import androidx.room.ForeignKey import androidx.room.Ignore import androidx.room.PrimaryKey import ru.ulstu.`is`.pmu.R @Entity( - tableName = "tanks" + tableName = "tanks", + foreignKeys = [ + ForeignKey( + entity = TankImage::class, + parentColumns = ["image_id"], + childColumns = ["image_id"], + onDelete = ForeignKey.CASCADE, + onUpdate = ForeignKey.RESTRICT + ) + ] ) data class Tank( @PrimaryKey(autoGenerate = true) @@ -16,21 +26,21 @@ data class Tank( val name: String, @ColumnInfo(name = "price") val price: Int, - @ColumnInfo(name="image") - val image: Int, + @ColumnInfo(name = "image_id", index = true) + val imageId: Long, @ColumnInfo(name = "levelId", index = true) val levelId: Long?, @ColumnInfo(name = "nationId", index = true) - val nationId: Long? + val nationId: Long?, ) { @Ignore constructor( name: String, price: Int, - image: Int, + imageId: Long, level: Level, nation: Nation - ) : this(null, name, price, image, level.uid, nation.uid) + ) : this(null, name, price, imageId, level.uid, nation.uid) companion object { fun getTank(index: Long = 0): Tank { @@ -38,7 +48,7 @@ data class Tank( index, "Первый танк", 100000, - R.drawable.t_34_85, + 1L, 1, 1 ) diff --git a/compose/app/src/main/java/ru/ulstu/is/pmu/tank/model/TankImage.kt b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/model/TankImage.kt new file mode 100644 index 0000000..ed3cd9b --- /dev/null +++ b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/model/TankImage.kt @@ -0,0 +1,36 @@ +package ru.ulstu.`is`.pmu.tank.model + +import android.graphics.Bitmap +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "tank_images") +data class TankImage( + @ColumnInfo(name = "image_id") + @PrimaryKey(autoGenerate = true) + val id: Long?, + val data: Bitmap +) { + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + + if (javaClass != other?.javaClass) { + return false + } + + other as TankImage + + if (id != other.id) { + return false + } + + return true + } + + override fun hashCode(): Int { + return (id ?: -1) as Int + } +} \ No newline at end of file diff --git a/compose/app/src/main/java/ru/ulstu/is/pmu/tank/model/TankWithNationAndLevel.kt b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/model/TankWithNationAndLevel.kt index 5197ea5..d67cf6e 100644 --- a/compose/app/src/main/java/ru/ulstu/is/pmu/tank/model/TankWithNationAndLevel.kt +++ b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/model/TankWithNationAndLevel.kt @@ -1,5 +1,6 @@ package ru.ulstu.`is`.pmu.tank.model +import android.graphics.Bitmap import androidx.room.ColumnInfo import androidx.room.Embedded import androidx.room.Entity @@ -13,7 +14,7 @@ data class TankWithNationAndLevel ( @ColumnInfo(name = "price") val price: Int, @ColumnInfo(name="image") - val image: Int, + val image: Bitmap, val level: Int, val nationName: String ) { diff --git a/compose/app/src/main/java/ru/ulstu/is/pmu/tank/repository/OfflineTankRepository.kt b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/repository/OfflineTankRepository.kt index e5f33e6..474f7de 100644 --- a/compose/app/src/main/java/ru/ulstu/is/pmu/tank/repository/OfflineTankRepository.kt +++ b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/repository/OfflineTankRepository.kt @@ -1,20 +1,49 @@ package ru.ulstu.`is`.pmu.tank.repository +import android.graphics.Bitmap import kotlinx.coroutines.flow.Flow import ru.ulstu.`is`.pmu.tank.dao.TankDao +import ru.ulstu.`is`.pmu.tank.dao.TankImageDao import ru.ulstu.`is`.pmu.tank.model.Tank +import ru.ulstu.`is`.pmu.tank.model.TankImage import ru.ulstu.`is`.pmu.tank.model.TankWithNationAndLevel -class OfflineTankRepository(private val tankDao: TankDao) : TankRepository { +class OfflineTankRepository( + private val tankDao: TankDao, + private val tankImageDao: TankImageDao +) : TankRepository { override fun getAll(): Flow> = tankDao.getAll() override fun getTank(uid: Long): Flow = tankDao.getTankUid(uid) override fun getUserTanks(uid: Long): Flow> = tankDao.getUserTanks(uid) - override suspend fun insertTank(tank: Tank) = tankDao.insert(tank) + override suspend fun insertTank(tank: Tank, image: Bitmap) { + val imageId = tankImageDao.insert( + TankImage( + id = null, + data = image + ) + ) - override suspend fun updateTank(tank: Tank) = tankDao.update(tank) + tankDao.insert(tank.copy(imageId = imageId)) + } + + override suspend fun updateTank(tank: Tank, image: Bitmap) { + val imageId = tankImageDao.insert( + TankImage( + id = null, + data = image + ) + ) + + tankDao.update(tank.copy(imageId = imageId)) + tankImageDao.delete(tank.imageId) + } override suspend fun deleteTank(tank: Tank) = tankDao.delete(tank) + + override suspend fun delete(tankId: Long) { + tankDao.delete(tankId) + } } \ No newline at end of file diff --git a/compose/app/src/main/java/ru/ulstu/is/pmu/tank/repository/TankRepository.kt b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/repository/TankRepository.kt index bc9b953..6690a66 100644 --- a/compose/app/src/main/java/ru/ulstu/is/pmu/tank/repository/TankRepository.kt +++ b/compose/app/src/main/java/ru/ulstu/is/pmu/tank/repository/TankRepository.kt @@ -1,5 +1,6 @@ package ru.ulstu.`is`.pmu.tank.repository +import android.graphics.Bitmap import kotlinx.coroutines.flow.Flow import ru.ulstu.`is`.pmu.tank.model.Tank import ru.ulstu.`is`.pmu.tank.model.TankWithNationAndLevel @@ -8,7 +9,8 @@ interface TankRepository { fun getAll(): Flow> fun getTank(uid: Long): Flow fun getUserTanks(uid: Long): Flow> - suspend fun insertTank(tank: Tank) - suspend fun updateTank(tank: Tank) + suspend fun insertTank(tank: Tank, image: Bitmap) + suspend fun updateTank(tank: Tank, image: Bitmap) suspend fun deleteTank(tank: Tank) + suspend fun delete(tankId: Long) } \ No newline at end of file diff --git a/compose/app/src/main/java/ru/ulstu/is/pmu/tanks/composeui/Constructor.kt b/compose/app/src/main/java/ru/ulstu/is/pmu/tanks/composeui/Constructor.kt index 8bc8491..3d794f3 100644 --- a/compose/app/src/main/java/ru/ulstu/is/pmu/tanks/composeui/Constructor.kt +++ b/compose/app/src/main/java/ru/ulstu/is/pmu/tanks/composeui/Constructor.kt @@ -1,6 +1,7 @@ package ru.ulstu.`is`.pmu.tanks.composeui import android.content.res.Configuration +import android.graphics.Bitmap import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -39,6 +40,9 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController +import com.application.ui.miniatureBound +import com.application.ui.resize +import com.application.ui.tankImageBound import kotlinx.coroutines.launch import ru.ulstu.`is`.pmu.R import ru.ulstu.`is`.pmu.tank.composeui.edit.LevelDropDownViewModel @@ -54,6 +58,7 @@ import ru.ulstu.`is`.pmu.tank.composeui.edit.toUiState import ru.ulstu.`is`.pmu.tank.model.Level import ru.ulstu.`is`.pmu.tank.model.Nation import ru.ulstu.`is`.pmu.tank.model.Tank +import ru.ulstu.`is`.pmu.tanks.composeui.image.CuteImageUploader import ru.ulstu.`is`.pmu.ui.AppViewModelProvider import ru.ulstu.`is`.pmu.ui.theme.CustomDark import ru.ulstu.`is`.pmu.ui.theme.CustomOrange @@ -73,6 +78,7 @@ fun Constructor( nationDropDownViewModel.setCurrentNation(tankEditViewModel.tankUiState.tankDetails.nationId ?: 1) Constructor( + tankViewModel = tankEditViewModel, tankUiState = tankEditViewModel.tankUiState, levelUiState = levelDropDownViewModel.levelUiState, levelsListUiState = levelDropDownViewModel.levelsListUiState, @@ -192,6 +198,7 @@ private fun NationDropDown( @OptIn(ExperimentalMaterial3Api::class) @Composable private fun Constructor( + tankViewModel: TankEditViewModel, tankUiState: TankUiState, levelUiState: LevelUiState, levelsListUiState: LevelsListUiState, @@ -208,6 +215,15 @@ private fun Constructor( //для работы выпадающего списка наций var expandedNation by remember { mutableStateOf(false) } + fun handleImageUpload(bitmap: Bitmap) { + tankViewModel.updateUiState( + tankUiState.tankDetails.copy( + image = bitmap.resize(tankImageBound), + miniature = bitmap.resize(miniatureBound) + ) + ) + } + Column( verticalArrangement = Arrangement.spacedBy(35.dp), modifier = Modifier.padding(0.dp, 15.dp) @@ -234,6 +250,8 @@ private fun Constructor( modifier = Modifier.padding(0.dp, 5.dp) ) { Text(text="Название:", fontSize = 30.sp, color = Color.Black, fontWeight = FontWeight.Bold) + Spacer(Modifier.height(25.dp)) + Text(text="Изображение:", fontSize = 30.sp, color = Color.Black, fontWeight = FontWeight.Bold) Spacer(Modifier.height(30.dp)) Text(text="Уровень:", fontSize = 30.sp, color = Color.Black, fontWeight = FontWeight.Bold) Spacer(Modifier.height(30.dp)) @@ -250,6 +268,11 @@ private fun Constructor( .width(200.dp), ) Spacer(Modifier.height(10.dp)) + CuteImageUploader( + bitmap = tankUiState.tankDetails.image, + onResult = { bitmap: Bitmap -> handleImageUpload(bitmap) } + ) + Spacer(Modifier.height(10.dp)) // Выпадающий список уровней LevelDropDown( levelUiState = levelUiState, @@ -421,6 +444,7 @@ fun ConstructorEditPreview() { color = CustomDark ) { Constructor( + tankViewModel = viewModel(factory = AppViewModelProvider.Factory), tankUiState = Tank.getTank().toUiState(true), levelUiState = Level.DEMO_LEVEL.toUiState(), levelsListUiState = LevelsListUiState(listOf()), diff --git a/compose/app/src/main/java/ru/ulstu/is/pmu/tanks/composeui/image/CuteImage.kt b/compose/app/src/main/java/ru/ulstu/is/pmu/tanks/composeui/image/CuteImage.kt new file mode 100644 index 0000000..4f792ce --- /dev/null +++ b/compose/app/src/main/java/ru/ulstu/is/pmu/tanks/composeui/image/CuteImage.kt @@ -0,0 +1,25 @@ +package ru.ulstu.`is`.pmu.tanks.composeui.image + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.layout.ContentScale + +@Composable +fun CuteImage( + imageBitmap: ImageBitmap, + modifier: Modifier = Modifier +) { + Image( + bitmap = imageBitmap, + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = modifier + .aspectRatio(1F) + .clip(RoundedCornerShape(Dimensions.cornerRadius)) + ) +} \ No newline at end of file diff --git a/compose/app/src/main/java/ru/ulstu/is/pmu/tanks/composeui/image/CuteImageButton.kt b/compose/app/src/main/java/ru/ulstu/is/pmu/tanks/composeui/image/CuteImageButton.kt new file mode 100644 index 0000000..2f014b2 --- /dev/null +++ b/compose/app/src/main/java/ru/ulstu/is/pmu/tanks/composeui/image/CuteImageButton.kt @@ -0,0 +1,31 @@ +package ru.ulstu.`is`.pmu.tanks.composeui.image + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.unit.dp + +@Composable +fun CuteImageButton( + imageBitmap: ImageBitmap, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + val cornerShape = RoundedCornerShape(Dimensions.cornerRadius) + Button( + onClick = { onClick() }, + modifier = modifier, + shape = cornerShape, + contentPadding = PaddingValues(0.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent + ) + ) { + CuteImage(imageBitmap) + } +} \ No newline at end of file diff --git a/compose/app/src/main/java/ru/ulstu/is/pmu/tanks/composeui/image/CuteImageUploader.kt b/compose/app/src/main/java/ru/ulstu/is/pmu/tanks/composeui/image/CuteImageUploader.kt new file mode 100644 index 0000000..299c328 --- /dev/null +++ b/compose/app/src/main/java/ru/ulstu/is/pmu/tanks/composeui/image/CuteImageUploader.kt @@ -0,0 +1,132 @@ +package ru.ulstu.`is`.pmu.tanks.composeui.image + +import android.content.res.Configuration +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import ru.ulstu.`is`.pmu.R +import ru.ulstu.`is`.pmu.ui.theme.CustomDark +import ru.ulstu.`is`.pmu.ui.theme.CustomOrange +import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme + +@Composable +fun CuteImageUploader( + bitmap: Bitmap?, + onResult: (Bitmap) -> Unit +) { + val context = LocalContext.current + val title: String = if (bitmap == null) { + stringResource(R.string.not_uploaded) + } else { + stringResource(R.string.size, bitmap.width, bitmap.height) + } + + val launcher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.GetContent(), + onResult = { uri: Uri? -> + uri?.let { + val inputStream = context.contentResolver.openInputStream(uri) + val newBitmap: Bitmap = BitmapFactory.decodeStream(inputStream) + onResult(newBitmap) + } + } + ) + + fun handleUploadButtonClick() { + launcher.launch("image/*") + } + + Row( + modifier = Modifier.height(IntrinsicSize.Min) + ) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .background( + color = MaterialTheme.colorScheme.surface, + shape = RoundedCornerShape(Dimensions.cornerRadius) + ) + .weight(0.25F) + .aspectRatio(1F) + ) { + if (bitmap != null) { + CuteImage( + imageBitmap = bitmap.asImageBitmap(), + modifier = Modifier.width(100.dp) + ) + } else { + Icon( + painter = painterResource(R.drawable.ic_camera), + contentDescription = null, + tint = MaterialTheme.colorScheme.onBackground + ) + } + } + Spacer(modifier = Modifier.width(10.dp)) + Column( + modifier = Modifier + .background( + color = MaterialTheme.colorScheme.surface, + shape = RoundedCornerShape(Dimensions.cornerRadius) + ) + .fillMaxHeight() + .padding(10.dp) + .weight(0.75F) + ) { + Text( + text = title, + color = MaterialTheme.colorScheme.onBackground + ) + Spacer(modifier = Modifier.weight(1F)) + Button( + modifier = Modifier + .width(200.dp) + .height(50.dp), + colors = ButtonDefaults.buttonColors( + containerColor = CustomOrange, + contentColor = CustomDark + ), + onClick = { handleUploadButtonClick() }, + ){ + Text(text = "Загрузить изображение") + } + } + } +} + +@Preview(name = "Light Mode", uiMode = Configuration.UI_MODE_NIGHT_NO) +@Preview(name = "Dark Mode", uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun CuteImageLoaderPreview() { + PmudemoTheme { + CuteImageUploader(null) {} + } +} \ No newline at end of file diff --git a/compose/app/src/main/java/ru/ulstu/is/pmu/tanks/composeui/image/CuteZoomableImage.kt b/compose/app/src/main/java/ru/ulstu/is/pmu/tanks/composeui/image/CuteZoomableImage.kt new file mode 100644 index 0000000..df32c91 --- /dev/null +++ b/compose/app/src/main/java/ru/ulstu/is/pmu/tanks/composeui/image/CuteZoomableImage.kt @@ -0,0 +1,61 @@ +package ru.ulstu.`is`.pmu.tanks.composeui.image + +import androidx.compose.foundation.Image +import androidx.compose.foundation.gestures.detectTransformGestures +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.unit.IntSize + +@Composable +fun CuteZoomableImage( + imageBitmap: ImageBitmap +) { + val size = remember { mutableStateOf(IntSize.Zero) } + val scale = remember { mutableFloatStateOf(1f) } + val offsetX = remember { mutableFloatStateOf(0f) } + val offsetY = remember { mutableFloatStateOf(0f) } + + Box( + modifier = Modifier + .clip(RectangleShape) + .fillMaxSize() + .pointerInput(Unit) { + detectTransformGestures { _, pan, zoom, _ -> + scale.floatValue = maxOf(1f, minOf(3f, scale.floatValue * zoom)) + val maxX = (size.value.width * (scale.floatValue - 1)) / 2 + val minX = -maxX + offsetX.floatValue = maxOf(minX, minOf(maxX, offsetX.floatValue + pan.x)) + val maxY = (size.value.height * (scale.floatValue - 1)) / 2 + val minY = -maxY + offsetY.floatValue = maxOf(minY, minOf(maxY, offsetY.floatValue + pan.y)) + } + }.onSizeChanged { + size.value = it + } + ) { + Image( + bitmap = imageBitmap, + contentDescription = null, + modifier = Modifier + .align(Alignment.Center) + .graphicsLayer( + scaleX = scale.floatValue, + scaleY = scale.floatValue, + translationX = offsetX.floatValue, + translationY = offsetY.floatValue + ) + ) + } +} \ No newline at end of file diff --git a/compose/app/src/main/java/ru/ulstu/is/pmu/tanks/composeui/image/Dimensions.kt b/compose/app/src/main/java/ru/ulstu/is/pmu/tanks/composeui/image/Dimensions.kt new file mode 100644 index 0000000..5753a45 --- /dev/null +++ b/compose/app/src/main/java/ru/ulstu/is/pmu/tanks/composeui/image/Dimensions.kt @@ -0,0 +1,7 @@ +package ru.ulstu.`is`.pmu.tanks.composeui.image + +import androidx.compose.ui.unit.dp + +object Dimensions { + val cornerRadius = 10.dp +} \ No newline at end of file diff --git a/compose/app/src/main/java/ru/ulstu/is/pmu/ui/ImageTools.kt b/compose/app/src/main/java/ru/ulstu/is/pmu/ui/ImageTools.kt new file mode 100644 index 0000000..e6d71fc --- /dev/null +++ b/compose/app/src/main/java/ru/ulstu/is/pmu/ui/ImageTools.kt @@ -0,0 +1,26 @@ +package com.application.ui + +import android.graphics.Bitmap +import kotlin.math.sqrt + +const val miniatureBound: Int = 250 +const val tankImageBound: Int = 800 + +fun getEmptyBitmap(): Bitmap { + return Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) +} + +fun Bitmap.resize(bound: Int): Bitmap { + val width = this.width + val height = this.height + val size: Int = width * height + val newSize: Int = bound * bound + val factor: Double = if (size > newSize) { + sqrt(size.toDouble() / newSize) + } else { + 1.0 + } + val newWidth: Int = (width / factor).toInt() + val newHeight: Int = (height / factor).toInt() + return Bitmap.createScaledBitmap(this, newWidth, newHeight, false) +} \ No newline at end of file diff --git a/compose/app/src/main/res/drawable/ic_camera.xml b/compose/app/src/main/res/drawable/ic_camera.xml new file mode 100644 index 0000000..e47edf5 --- /dev/null +++ b/compose/app/src/main/res/drawable/ic_camera.xml @@ -0,0 +1,21 @@ + + + + + \ No newline at end of file diff --git a/compose/app/src/main/res/drawable/t_34_85.jpeg b/compose/app/src/main/res/drawable/t_34_85.jpeg deleted file mode 100644 index c50d054..0000000 Binary files a/compose/app/src/main/res/drawable/t_34_85.jpeg and /dev/null differ diff --git a/compose/app/src/main/res/values/strings.xml b/compose/app/src/main/res/values/strings.xml index aca823a..c8d7550 100644 --- a/compose/app/src/main/res/values/strings.xml +++ b/compose/app/src/main/res/values/strings.xml @@ -31,4 +31,10 @@

Здесь могла быть Ваша реклама!

\n\n

Наш сайт ulstu.ru

+ + + Image + Not uploaded + Size: %1$dx%2$d + Upload image \ No newline at end of file