WIP
This commit is contained in:
parent
7f2e16c580
commit
a32757c518
@ -19,6 +19,14 @@ android {
|
|||||||
enabled = true
|
enabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
compose true
|
||||||
|
}
|
||||||
|
|
||||||
|
composeOptions {
|
||||||
|
kotlinCompilerExtensionVersion = "1.5.2"
|
||||||
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.isolaatti"
|
applicationId "com.isolaatti"
|
||||||
minSdk 24
|
minSdk 24
|
||||||
@ -137,4 +145,19 @@ dependencies {
|
|||||||
|
|
||||||
// OSS screen
|
// OSS screen
|
||||||
implementation 'com.google.android.gms:play-services-oss-licenses:17.1.0'
|
implementation 'com.google.android.gms:play-services-oss-licenses:17.1.0'
|
||||||
|
|
||||||
|
def composeBom = platform('androidx.compose:compose-bom:2024.10.01')
|
||||||
|
implementation composeBom
|
||||||
|
androidTestImplementation composeBom
|
||||||
|
implementation 'androidx.compose.material3:material3'
|
||||||
|
implementation 'androidx.compose.ui:ui-tooling-preview'
|
||||||
|
debugImplementation 'androidx.compose.ui:ui-tooling'
|
||||||
|
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
|
||||||
|
debugImplementation 'androidx.compose.ui:ui-test-manifest'
|
||||||
|
|
||||||
|
implementation 'androidx.activity:activity-compose:1.9.2'
|
||||||
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.5'
|
||||||
|
implementation 'androidx.compose.runtime:runtime-livedata'
|
||||||
|
|
||||||
|
implementation "io.coil-kt.coil3:coil-compose:3.0.1"
|
||||||
}
|
}
|
||||||
27
app/src/main/java/com/isolaatti/common/IsolaattiTheme.kt
Normal file
27
app/src/main/java/com/isolaatti/common/IsolaattiTheme.kt
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package com.isolaatti.common
|
||||||
|
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.material3.lightColorScheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.res.colorResource
|
||||||
|
import com.isolaatti.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun IsolaattiTheme(content: @Composable () -> Unit) {
|
||||||
|
val colorScheme = if(isSystemInDarkTheme()) {
|
||||||
|
darkColorScheme(
|
||||||
|
primary = colorResource(R.color.purple),
|
||||||
|
onSurface = colorResource(R.color.on_surface)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
lightColorScheme(
|
||||||
|
primary = colorResource(R.color.purple),
|
||||||
|
onSurface = colorResource(R.color.on_surface)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
MaterialTheme(colorScheme = colorScheme) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,10 +1,10 @@
|
|||||||
package com.isolaatti.images
|
package com.isolaatti.images
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.isolaatti.MyApplication
|
|
||||||
import com.isolaatti.connectivity.RetrofitClient
|
import com.isolaatti.connectivity.RetrofitClient
|
||||||
|
import com.isolaatti.database.AppDatabase
|
||||||
|
import com.isolaatti.images.common.data.dao.ImagesDraftsDao
|
||||||
import com.isolaatti.images.common.data.remote.ImagesApi
|
import com.isolaatti.images.common.data.remote.ImagesApi
|
||||||
import com.isolaatti.images.common.data.repository.ImagesRepositoryImpl
|
import com.isolaatti.images.common.data.repository.ImagesRepositoryImpl
|
||||||
import com.isolaatti.images.common.domain.repository.ImagesRepository
|
import com.isolaatti.images.common.domain.repository.ImagesRepository
|
||||||
@ -28,7 +28,12 @@ class Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideImagesRepository(imagesApi: ImagesApi, contentResolver: ContentResolver): ImagesRepository {
|
fun provideImagesDraftDao(database: AppDatabase): ImagesDraftsDao {
|
||||||
return ImagesRepositoryImpl(imagesApi, contentResolver)
|
return database.imagesDraftsDao()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun provideImagesRepository(imagesApi: ImagesApi, imagesDraftsDao: ImagesDraftsDao, contentResolver: ContentResolver): ImagesRepository {
|
||||||
|
return ImagesRepositoryImpl(imagesApi, imagesDraftsDao, contentResolver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,122 @@
|
|||||||
|
package com.isolaatti.images.common.components
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.Close
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.FilledIconButton
|
||||||
|
import androidx.compose.material3.FilledTonalIconButton
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Devices
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Popup
|
||||||
|
import androidx.compose.ui.zIndex
|
||||||
|
import coil3.compose.AsyncImage
|
||||||
|
import com.isolaatti.R
|
||||||
|
import com.isolaatti.images.common.domain.entity.Image
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ImagesRow(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
images: List<Uri>,
|
||||||
|
deletable: Boolean,
|
||||||
|
addable: Boolean,
|
||||||
|
onClick: (index: Int) -> Unit,
|
||||||
|
onDeleteClick: (index: Int) -> Unit = {},
|
||||||
|
onTakePicture: () -> Unit = {},
|
||||||
|
onUploadPicture: () -> Unit = {}
|
||||||
|
) {
|
||||||
|
var showAddPhotoPopUp by remember { mutableStateOf(false) }
|
||||||
|
LazyRow(modifier, contentPadding = PaddingValues(horizontal = 8.dp)) {
|
||||||
|
if(addable) {
|
||||||
|
item {
|
||||||
|
Card(modifier = Modifier
|
||||||
|
.padding(horizontal = 4.dp)
|
||||||
|
.size(120.dp), onClick = {
|
||||||
|
showAddPhotoPopUp = true
|
||||||
|
}) {
|
||||||
|
|
||||||
|
|
||||||
|
DropdownMenu(expanded = showAddPhotoPopUp, onDismissRequest = { showAddPhotoPopUp = false }) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.take_a_photo )) },
|
||||||
|
onClick = {
|
||||||
|
showAddPhotoPopUp = false
|
||||||
|
onTakePicture()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.upload_a_picture )) },
|
||||||
|
onClick = {
|
||||||
|
showAddPhotoPopUp = false
|
||||||
|
onUploadPicture()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Image(modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(16.dp),
|
||||||
|
painter = painterResource(id = R.drawable.baseline_add_a_photo_24),
|
||||||
|
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface),
|
||||||
|
contentDescription = null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items(count = images.size) { index ->
|
||||||
|
|
||||||
|
Card(modifier = Modifier
|
||||||
|
.padding(horizontal = 4.dp)
|
||||||
|
.size(120.dp)) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
|
||||||
|
) {
|
||||||
|
FilledTonalIconButton(
|
||||||
|
onClick = { onDeleteClick(index) },
|
||||||
|
modifier = Modifier.zIndex(2f)
|
||||||
|
) {
|
||||||
|
Icon(imageVector = Icons.Default.Close, contentDescription = null)
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncImage(
|
||||||
|
model = images[index],
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.fillMaxSize().zIndex(1f),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(device = Devices.PIXEL_5)
|
||||||
|
@Composable
|
||||||
|
fun ImagesRowPreview() {
|
||||||
|
ImagesRow(images = emptyList(), deletable = false, addable = true, onClick = {})
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@ package com.isolaatti.images.common.data.entity
|
|||||||
|
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
@Entity(tableName = "image_drafts")
|
@Entity(tableName = "image_drafts")
|
||||||
data class ImageDraftEntity(
|
data class ImageDraftEntity(
|
||||||
@ -9,4 +10,4 @@ data class ImageDraftEntity(
|
|||||||
val id: Long,
|
val id: Long,
|
||||||
val uri: String,
|
val uri: String,
|
||||||
val postId: Long? = null
|
val postId: Long? = null
|
||||||
)
|
) : Serializable
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package com.isolaatti.images.common.data.remote
|
|||||||
data class ImageDto(
|
data class ImageDto(
|
||||||
val id: String,
|
val id: String,
|
||||||
val userId: Int,
|
val userId: Int,
|
||||||
val name: String,
|
|
||||||
val squadId: String?,
|
val squadId: String?,
|
||||||
val username: String,
|
val username: String,
|
||||||
val idOnFirebase: String
|
val idOnFirebase: String
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import android.graphics.BitmapFactory
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import com.isolaatti.images.common.data.dao.ImagesDraftsDao
|
||||||
|
import com.isolaatti.images.common.data.entity.ImageDraftEntity
|
||||||
import com.isolaatti.images.common.data.remote.DeleteImagesDto
|
import com.isolaatti.images.common.data.remote.DeleteImagesDto
|
||||||
import com.isolaatti.images.common.data.remote.ImagesApi
|
import com.isolaatti.images.common.data.remote.ImagesApi
|
||||||
import com.isolaatti.images.common.domain.entity.Image
|
import com.isolaatti.images.common.domain.entity.Image
|
||||||
@ -20,7 +22,7 @@ import java.io.ByteArrayOutputStream
|
|||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ImagesRepositoryImpl @Inject constructor(private val imagesApi: ImagesApi, private val contentResolver: ContentResolver) :
|
class ImagesRepositoryImpl @Inject constructor(private val imagesApi: ImagesApi, private val imagesDraftsDao: ImagesDraftsDao, private val contentResolver: ContentResolver) :
|
||||||
ImagesRepository {
|
ImagesRepository {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -104,4 +106,12 @@ class ImagesRepositoryImpl @Inject constructor(private val imagesApi: ImagesApi,
|
|||||||
imageInputStream?.close()
|
imageInputStream?.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getDetachedDraftImages(): Flow<Resource<List<ImageDraftEntity>>> {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDraftImagesOfPost(postId: Long): Flow<Resource<List<ImageDraftEntity>>> {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -9,7 +9,6 @@ import java.io.Serializable
|
|||||||
data class Image(
|
data class Image(
|
||||||
val id: String,
|
val id: String,
|
||||||
val userId: Int,
|
val userId: Int,
|
||||||
val name: String,
|
|
||||||
val username: String
|
val username: String
|
||||||
): Deletable(), Serializable {
|
): Deletable(), Serializable {
|
||||||
val imageUrl: String get() = UrlGen.imageUrl(id)
|
val imageUrl: String get() = UrlGen.imageUrl(id)
|
||||||
@ -19,7 +18,7 @@ data class Image(
|
|||||||
val markdown: String get() = Generators.generateImage(imageUrl)
|
val markdown: String get() = Generators.generateImage(imageUrl)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromDto(imageDto: ImageDto) = Image(imageDto.id, imageDto.userId, imageDto.name, imageDto.username)
|
fun fromDto(imageDto: ImageDto) = Image(imageDto.id, imageDto.userId, imageDto.username)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@ -30,7 +29,6 @@ data class Image(
|
|||||||
|
|
||||||
if (id != other.id) return false
|
if (id != other.id) return false
|
||||||
if (userId != other.userId) return false
|
if (userId != other.userId) return false
|
||||||
if (name != other.name) return false
|
|
||||||
if (username != other.username) return false
|
if (username != other.username) return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@ -39,7 +37,6 @@ data class Image(
|
|||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = id.hashCode()
|
var result = id.hashCode()
|
||||||
result = 31 * result + userId
|
result = 31 * result + userId
|
||||||
result = 31 * result + name.hashCode()
|
|
||||||
result = 31 * result + username.hashCode()
|
result = 31 * result + username.hashCode()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package com.isolaatti.images.common.domain.repository
|
package com.isolaatti.images.common.domain.repository
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import com.isolaatti.images.common.data.entity.ImageDraftEntity
|
||||||
import com.isolaatti.images.common.domain.entity.Image
|
import com.isolaatti.images.common.domain.entity.Image
|
||||||
import com.isolaatti.utils.Resource
|
import com.isolaatti.utils.Resource
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@ -9,4 +10,6 @@ interface ImagesRepository {
|
|||||||
fun getImagesOfUser(userId: Int, lastId: String? = null): Flow<Resource<List<Image>>>
|
fun getImagesOfUser(userId: Int, lastId: String? = null): Flow<Resource<List<Image>>>
|
||||||
fun deleteImages(images: List<Image>): Flow<Resource<Boolean>>
|
fun deleteImages(images: List<Image>): Flow<Resource<Boolean>>
|
||||||
fun uploadImage(name: String, imageUri: Uri, squadId: String?): Flow<Resource<Image>>
|
fun uploadImage(name: String, imageUri: Uri, squadId: String?): Flow<Resource<Image>>
|
||||||
|
fun getDetachedDraftImages(): Flow<Resource<List<ImageDraftEntity>>>
|
||||||
|
fun getDraftImagesOfPost(postId: Long): Flow<Resource<List<ImageDraftEntity>>>
|
||||||
}
|
}
|
||||||
@ -5,9 +5,11 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.activity.result.contract.ActivityResultContract
|
import androidx.activity.result.contract.ActivityResultContract
|
||||||
|
import androidx.core.content.IntentCompat
|
||||||
|
import com.isolaatti.images.common.data.entity.ImageDraftEntity
|
||||||
import com.isolaatti.images.common.domain.entity.Image
|
import com.isolaatti.images.common.domain.entity.Image
|
||||||
|
|
||||||
class ImageChooserContract : ActivityResultContract<ImageChooserContract.Requester, Image?>() {
|
class ImageChooserContract : ActivityResultContract<ImageChooserContract.Requester, ImageDraftEntity?>() {
|
||||||
|
|
||||||
enum class Requester {
|
enum class Requester {
|
||||||
UserPost, SquadPost
|
UserPost, SquadPost
|
||||||
@ -18,13 +20,13 @@ class ImageChooserContract : ActivityResultContract<ImageChooserContract.Request
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun parseResult(resultCode: Int, intent: Intent?): Image? {
|
override fun parseResult(resultCode: Int, intent: Intent?): ImageDraftEntity? {
|
||||||
if(resultCode != Activity.RESULT_OK) { return null }
|
if(resultCode != Activity.RESULT_OK) { return null }
|
||||||
|
|
||||||
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
if(intent == null) {
|
||||||
intent?.getSerializableExtra(ImageChooserActivity.OUTPUT_EXTRA_IMAGE) as Image?
|
return null
|
||||||
} else {
|
|
||||||
intent?.getSerializableExtra(ImageChooserActivity.OUTPUT_EXTRA_IMAGE, Image::class.java)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return IntentCompat.getSerializableExtra(intent, ImageChooserActivity.OUTPUT_EXTRA_IMAGE, ImageDraftEntity::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -38,7 +38,6 @@ class ImageChooserPreview : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.image.load(viewModel.selectedImage?.imageUrl)
|
binding.image.load(viewModel.selectedImage?.imageUrl)
|
||||||
binding.imageDescription.text = viewModel.selectedImage?.name
|
|
||||||
|
|
||||||
binding.chooseImageButton.setOnClickListener {
|
binding.chooseImageButton.setOnClickListener {
|
||||||
showLoading(true)
|
showLoading(true)
|
||||||
|
|||||||
@ -20,7 +20,6 @@ class PictureViewerMainFragment : Fragment() {
|
|||||||
override fun onPageSelected(position: Int) {
|
override fun onPageSelected(position: Int) {
|
||||||
super.onPageSelected(position)
|
super.onPageSelected(position)
|
||||||
binding.imageAuthor.text = images[position].username
|
binding.imageAuthor.text = images[position].username
|
||||||
binding.imageDescription.text = images[position].name
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,7 +42,6 @@ class PictureViewerMainFragment : Fragment() {
|
|||||||
binding.viewpager.adapter = adapter
|
binding.viewpager.adapter = adapter
|
||||||
binding.viewpager.setCurrentItem(position, false)
|
binding.viewpager.setCurrentItem(position, false)
|
||||||
binding.viewpager.registerOnPageChangeCallback(onPageChangeCallback)
|
binding.viewpager.registerOnPageChangeCallback(onPageChangeCallback)
|
||||||
binding.imageDescription.text = images[position].name
|
|
||||||
binding.imageAuthor.text = images[position].username
|
binding.imageAuthor.text = images[position].username
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -123,10 +123,10 @@ class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedC
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val imageChooserLauncher = registerForActivityResult(ImageChooserContract()) { image ->
|
private val imageChooserLauncher = registerForActivityResult(ImageChooserContract()) { image ->
|
||||||
Log.d("BottomSheetPostComment", "${image?.markdown}")
|
|
||||||
|
|
||||||
if(image != null) {
|
if(image != null) {
|
||||||
viewBinding.newCommentTextField.editText?.setText("${viewBinding.newCommentTextField.editText?.text}\n\n${image.markdown}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package com.isolaatti.posting.posts.presentation
|
package com.isolaatti.posting.posts.presentation
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
@ -21,6 +22,8 @@ import com.isolaatti.utils.Resource
|
|||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
@ -48,6 +51,8 @@ class CreatePostViewModel @Inject constructor(
|
|||||||
val liveContent: MutableLiveData<String> = MutableLiveData()
|
val liveContent: MutableLiveData<String> = MutableLiveData()
|
||||||
var content: String = ""
|
var content: String = ""
|
||||||
set(value) {field = value; liveContent.value = value} // TODO remove this and use only liveContent
|
set(value) {field = value; liveContent.value = value} // TODO remove this and use only liveContent
|
||||||
|
private val _photos: MutableStateFlow<List<Uri>> = MutableStateFlow(emptyList())
|
||||||
|
val photos: StateFlow<List<Uri>> get() = _photos
|
||||||
|
|
||||||
val audioAttachment: MutableLiveData<Playable?> = MutableLiveData()
|
val audioAttachment: MutableLiveData<Playable?> = MutableLiveData()
|
||||||
|
|
||||||
@ -188,4 +193,13 @@ class CreatePostViewModel @Inject constructor(
|
|||||||
audioAttachment.value = null
|
audioAttachment.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addPicture(uri: Uri) {
|
||||||
|
_photos.value = listOf(uri) + _photos.value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removePicture(index: Int) {
|
||||||
|
_photos.value = _photos.value.toMutableList().apply {
|
||||||
|
removeAt(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -4,6 +4,7 @@ import android.app.Activity
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.MediatorLiveData
|
import androidx.lifecycle.MediatorLiveData
|
||||||
@ -41,6 +42,7 @@ class CreatePostActivity : IsolaattiBaseActivity() {
|
|||||||
var postId: Long = 0L
|
var postId: Long = 0L
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
intent.extras?.let {
|
intent.extras?.let {
|
||||||
@ -85,14 +87,6 @@ class CreatePostActivity : IsolaattiBaseActivity() {
|
|||||||
|
|
||||||
binding.pager.adapter = CreatePostFragmentStateAdapter(this)
|
binding.pager.adapter = CreatePostFragmentStateAdapter(this)
|
||||||
|
|
||||||
|
|
||||||
TabLayoutMediator(binding.tabs, binding.pager) { tab, position ->
|
|
||||||
when(position) {
|
|
||||||
0 -> tab.setText(R.string.create_a_new_discussion)
|
|
||||||
1 -> tab.setText(R.string.preview)
|
|
||||||
}
|
|
||||||
}.attach()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setListeners() {
|
private fun setListeners() {
|
||||||
|
|||||||
@ -6,14 +6,10 @@ import androidx.viewpager2.adapter.FragmentStateAdapter
|
|||||||
|
|
||||||
class CreatePostFragmentStateAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {
|
class CreatePostFragmentStateAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
return 2
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createFragment(position: Int): Fragment {
|
override fun createFragment(position: Int): Fragment {
|
||||||
return if(position == 0) {
|
return PostEditingFragment()
|
||||||
PostEditingFragment()
|
|
||||||
} else {
|
|
||||||
MarkdownPreviewFragment()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,12 +1,22 @@
|
|||||||
package com.isolaatti.posting.posts.ui
|
package com.isolaatti.posting.posts.ui
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.activity.result.PickVisualMediaRequest
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
import androidx.core.widget.doOnTextChanged
|
import androidx.core.widget.doOnTextChanged
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
@ -14,6 +24,7 @@ import androidx.fragment.app.viewModels
|
|||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleEventObserver
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import com.isolaatti.MyApplication
|
||||||
import com.isolaatti.R
|
import com.isolaatti.R
|
||||||
import com.isolaatti.audio.audio_selector.ui.AudioSelectorContract
|
import com.isolaatti.audio.audio_selector.ui.AudioSelectorContract
|
||||||
import com.isolaatti.audio.common.domain.Audio
|
import com.isolaatti.audio.common.domain.Audio
|
||||||
@ -21,11 +32,15 @@ import com.isolaatti.audio.common.domain.Playable
|
|||||||
import com.isolaatti.audio.drafts.domain.AudioDraft
|
import com.isolaatti.audio.drafts.domain.AudioDraft
|
||||||
import com.isolaatti.audio.player.AudioPlayerConnector
|
import com.isolaatti.audio.player.AudioPlayerConnector
|
||||||
import com.isolaatti.audio.recorder.ui.AudioRecorderContract
|
import com.isolaatti.audio.recorder.ui.AudioRecorderContract
|
||||||
|
import com.isolaatti.common.IsolaattiTheme
|
||||||
import com.isolaatti.databinding.FragmentMarkdownEditingBinding
|
import com.isolaatti.databinding.FragmentMarkdownEditingBinding
|
||||||
|
import com.isolaatti.images.common.components.ImagesRow
|
||||||
import com.isolaatti.images.image_chooser.ui.ImageChooserContract
|
import com.isolaatti.images.image_chooser.ui.ImageChooserContract
|
||||||
import com.isolaatti.posting.link_creator.presentation.LinkCreatorViewModel
|
import com.isolaatti.posting.link_creator.presentation.LinkCreatorViewModel
|
||||||
import com.isolaatti.posting.link_creator.ui.LinkCreatorFragment
|
import com.isolaatti.posting.link_creator.ui.LinkCreatorFragment
|
||||||
import com.isolaatti.posting.posts.presentation.CreatePostViewModel
|
import com.isolaatti.posting.posts.presentation.CreatePostViewModel
|
||||||
|
import java.io.File
|
||||||
|
import java.util.Calendar
|
||||||
|
|
||||||
class PostEditingFragment : Fragment(){
|
class PostEditingFragment : Fragment(){
|
||||||
companion object {
|
companion object {
|
||||||
@ -47,11 +62,10 @@ class PostEditingFragment : Fragment(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val imageChooserLauncher = registerForActivityResult(ImageChooserContract()) { image ->
|
private val imageChooserLauncher = registerForActivityResult(ImageChooserContract()) { image ->
|
||||||
Log.d(LOG_TAG, "${image?.markdown}")
|
|
||||||
|
|
||||||
if(image != null) {
|
if(image != null) {
|
||||||
viewModel.content += "\n\n ${image.markdown}"
|
|
||||||
binding.filledTextField.editText?.setText(viewModel.content)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -61,6 +75,25 @@ class PostEditingFragment : Fragment(){
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val choosePictureLauncher = registerForActivityResult(ActivityResultContracts.PickVisualMedia()) {
|
||||||
|
if(it != null) {
|
||||||
|
viewModel.addPicture(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun makePhotoUri(): Uri {
|
||||||
|
val cacheFile = File(requireContext().filesDir, "temp_picture_${Calendar.getInstance().timeInMillis}")
|
||||||
|
return FileProvider.getUriForFile(requireContext(), "${MyApplication.myApp.packageName}.provider", cacheFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var cameraPhotoUri: Uri? = null
|
||||||
|
|
||||||
|
private val takePhotoLauncher = registerForActivityResult(ActivityResultContracts.TakePicture()) {
|
||||||
|
if(it && cameraPhotoUri != null) {
|
||||||
|
viewModel.addPicture(cameraPhotoUri!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
private val audioListener = object: AudioPlayerConnector.Listener {
|
private val audioListener = object: AudioPlayerConnector.Listener {
|
||||||
override fun durationChanged(duration: Int, audio: Playable) {
|
override fun durationChanged(duration: Int, audio: Playable) {
|
||||||
binding.audioItem.audioProgress.max = duration
|
binding.audioItem.audioProgress.max = duration
|
||||||
@ -112,6 +145,32 @@ class PostEditingFragment : Fragment(){
|
|||||||
|
|
||||||
setupListeners()
|
setupListeners()
|
||||||
setupObservers()
|
setupObservers()
|
||||||
|
|
||||||
|
binding.imagesRowCompose.apply {
|
||||||
|
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||||
|
setContent {
|
||||||
|
val pictures by viewModel.photos.collectAsState()
|
||||||
|
IsolaattiTheme {
|
||||||
|
ImagesRow(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
images = pictures,
|
||||||
|
deletable = true,
|
||||||
|
addable = true,
|
||||||
|
onClick = {},
|
||||||
|
onDeleteClick = {
|
||||||
|
viewModel.removePicture(it)
|
||||||
|
},
|
||||||
|
onTakePicture = {
|
||||||
|
cameraPhotoUri = makePhotoUri()
|
||||||
|
takePhotoLauncher.launch(cameraPhotoUri)
|
||||||
|
},
|
||||||
|
onUploadPicture = {
|
||||||
|
choosePictureLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
@ -127,12 +186,6 @@ class PostEditingFragment : Fragment(){
|
|||||||
viewModel.validation.postValue(!text.isNullOrEmpty())
|
viewModel.validation.postValue(!text.isNullOrEmpty())
|
||||||
viewModel.content = text.toString()
|
viewModel.content = text.toString()
|
||||||
}
|
}
|
||||||
binding.addImageButton.setOnClickListener {
|
|
||||||
insertImage()
|
|
||||||
}
|
|
||||||
binding.addLinkButton.setOnClickListener {
|
|
||||||
insertLink()
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.addAudioButton.setOnClickListener {
|
binding.addAudioButton.setOnClickListener {
|
||||||
|
|
||||||
|
|||||||
@ -128,7 +128,7 @@ class PostViewerActivity : IsolaattiBaseActivity() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent?) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
|
|
||||||
intent?.getLongExtra(POST_ID, 0)?.let {
|
intent?.getLongExtra(POST_ID, 0)?.let {
|
||||||
|
|||||||
@ -97,7 +97,7 @@ class ProfileMainFragment : Fragment() {
|
|||||||
private val chooseImageLauncher = registerForActivityResult(ImageChooserContract()) { image ->
|
private val chooseImageLauncher = registerForActivityResult(ImageChooserContract()) { image ->
|
||||||
// here change profile picture
|
// here change profile picture
|
||||||
if(image != null) {
|
if(image != null) {
|
||||||
viewModel.setProfileImage(image)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,7 +255,6 @@ class ProfileMainFragment : Fragment() {
|
|||||||
Image(
|
Image(
|
||||||
profile.profileImageId ?: "",
|
profile.profileImageId ?: "",
|
||||||
profile.userId,
|
profile.userId,
|
||||||
getString(R.string.user_profile_picture, profile.name),
|
|
||||||
profile.uniqueUsername)
|
profile.uniqueUsername)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
5
app/src/main/res/drawable/baseline_add_a_photo_24.xml
Normal file
5
app/src/main/res/drawable/baseline_add_a_photo_24.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M3,4V1h2v3h3v2H5v3H3V6H0V4H3zM6,10V7h3V4h7l1.83,2H21c1.1,0 2,0.9 2,2v12c0,1.1 -0.9,2 -2,2H5c-1.1,0 -2,-0.9 -2,-2V10H6zM13,19c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5s-5,2.24 -5,5S10.24,19 13,19zM9.8,14c0,1.77 1.43,3.2 3.2,3.2s3.2,-1.43 3.2,-3.2s-1.43,-3.2 -3.2,-3.2S9.8,12.23 9.8,14z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
@ -14,7 +14,8 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent">
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
android:fitsSystemWindows="true">
|
||||||
<com.google.android.material.appbar.MaterialToolbar
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
android:id="@+id/toolbar"
|
android:id="@+id/toolbar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -28,15 +29,6 @@
|
|||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
|
||||||
android:id="@+id/tabs"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/appBarLayout">
|
|
||||||
|
|
||||||
</com.google.android.material.tabs.TabLayout>
|
|
||||||
|
|
||||||
<androidx.viewpager2.widget.ViewPager2
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
android:id="@+id/pager"
|
android:id="@+id/pager"
|
||||||
@ -47,7 +39,7 @@
|
|||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/tabs" />
|
app:layout_constraintTop_toBottomOf="@+id/appBarLayout" />
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/progress_bar_loading"
|
android:id="@+id/progress_bar_loading"
|
||||||
|
|||||||
@ -23,23 +23,11 @@
|
|||||||
android:hint="@string/what_do_you_want_to_talk_about_you_can_record_an_audio_if_you_want"/>
|
android:hint="@string/what_do_you_want_to_talk_about_you_can_record_an_audio_if_you_want"/>
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<androidx.compose.ui.platform.ComposeView
|
||||||
android:id="@+id/add_image_button"
|
android:id="@+id/images_row_compose"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="160dp"/>
|
||||||
style="@style/Widget.Material3.Button.IconButton"
|
|
||||||
app:icon="@drawable/baseline_image_24" />
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/add_link_button"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
style="@style/Widget.Material3.Button.IconButton"
|
|
||||||
app:icon="@drawable/baseline_add_link_24" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@ -58,7 +58,7 @@
|
|||||||
<string name="there_is_no_more_content_to_show">There is no more content to show</string>
|
<string name="there_is_no_more_content_to_show">There is no more content to show</string>
|
||||||
<string name="post">Post</string>
|
<string name="post">Post</string>
|
||||||
<string name="add_image">Add image</string>
|
<string name="add_image">Add image</string>
|
||||||
<string name="what_do_you_want_to_talk_about_you_can_record_an_audio_if_you_want">What do you want to talk about? You can record an audio if you want.</string>
|
<string name="what_do_you_want_to_talk_about_you_can_record_an_audio_if_you_want">What do you want to talk about?</string>
|
||||||
<string name="posted_successfully">Posted!</string>
|
<string name="posted_successfully">Posted!</string>
|
||||||
<string name="drafts">Drafts</string>
|
<string name="drafts">Drafts</string>
|
||||||
<string name="report_profile">Report profile</string>
|
<string name="report_profile">Report profile</string>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user