This commit is contained in:
erik-everardo 2024-11-10 23:48:00 -06:00
parent 7f2e16c580
commit a32757c518
23 changed files with 303 additions and 76 deletions

View File

@ -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"
} }

View 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()
}
}

View File

@ -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)
} }
} }

View File

@ -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 = {})
}

View File

@ -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

View File

@ -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

View File

@ -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")
}
} }

View File

@ -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
} }

View File

@ -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>>>
} }

View File

@ -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)
} }
} }

View File

@ -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)

View File

@ -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
} }

View File

@ -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}")
} }
} }

View File

@ -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)
}
}
} }

View File

@ -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() {

View File

@ -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()
}
} }
} }

View File

@ -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 {

View File

@ -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 {

View File

@ -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)
)) ))
} }

View 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>

View File

@ -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"

View File

@ -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"

View File

@ -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>