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
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.2"
}
defaultConfig {
applicationId "com.isolaatti"
minSdk 24
@ -137,4 +145,19 @@ dependencies {
// OSS screen
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
import android.app.Application
import android.content.ContentResolver
import android.content.Context
import com.isolaatti.MyApplication
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.repository.ImagesRepositoryImpl
import com.isolaatti.images.common.domain.repository.ImagesRepository
@ -28,7 +28,12 @@ class Module {
}
@Provides
fun provideImagesRepository(imagesApi: ImagesApi, contentResolver: ContentResolver): ImagesRepository {
return ImagesRepositoryImpl(imagesApi, contentResolver)
fun provideImagesDraftDao(database: AppDatabase): ImagesDraftsDao {
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.PrimaryKey
import java.io.Serializable
@Entity(tableName = "image_drafts")
data class ImageDraftEntity(
@ -9,4 +10,4 @@ data class ImageDraftEntity(
val id: Long,
val uri: String,
val postId: Long? = null
)
) : Serializable

View File

@ -3,7 +3,6 @@ package com.isolaatti.images.common.data.remote
data class ImageDto(
val id: String,
val userId: Int,
val name: String,
val squadId: String?,
val username: String,
val idOnFirebase: String

View File

@ -6,6 +6,8 @@ import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
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.ImagesApi
import com.isolaatti.images.common.domain.entity.Image
@ -20,7 +22,7 @@ import java.io.ByteArrayOutputStream
import java.io.InputStream
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 {
companion object {
@ -104,4 +106,12 @@ class ImagesRepositoryImpl @Inject constructor(private val imagesApi: ImagesApi,
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(
val id: String,
val userId: Int,
val name: String,
val username: String
): Deletable(), Serializable {
val imageUrl: String get() = UrlGen.imageUrl(id)
@ -19,7 +18,7 @@ data class Image(
val markdown: String get() = Generators.generateImage(imageUrl)
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 {
@ -30,7 +29,6 @@ data class Image(
if (id != other.id) return false
if (userId != other.userId) return false
if (name != other.name) return false
if (username != other.username) return false
return true
@ -39,7 +37,6 @@ data class Image(
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + userId
result = 31 * result + name.hashCode()
result = 31 * result + username.hashCode()
return result
}

View File

@ -1,6 +1,7 @@
package com.isolaatti.images.common.domain.repository
import android.net.Uri
import com.isolaatti.images.common.data.entity.ImageDraftEntity
import com.isolaatti.images.common.domain.entity.Image
import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow
@ -9,4 +10,6 @@ interface ImagesRepository {
fun getImagesOfUser(userId: Int, lastId: String? = null): Flow<Resource<List<Image>>>
fun deleteImages(images: List<Image>): Flow<Resource<Boolean>>
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.os.Build
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
class ImageChooserContract : ActivityResultContract<ImageChooserContract.Requester, Image?>() {
class ImageChooserContract : ActivityResultContract<ImageChooserContract.Requester, ImageDraftEntity?>() {
enum class Requester {
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 }
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
intent?.getSerializableExtra(ImageChooserActivity.OUTPUT_EXTRA_IMAGE) as Image?
} else {
intent?.getSerializableExtra(ImageChooserActivity.OUTPUT_EXTRA_IMAGE, Image::class.java)
}
if(intent == null) {
return null
}
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.imageDescription.text = viewModel.selectedImage?.name
binding.chooseImageButton.setOnClickListener {
showLoading(true)

View File

@ -20,7 +20,6 @@ class PictureViewerMainFragment : Fragment() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
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.setCurrentItem(position, false)
binding.viewpager.registerOnPageChangeCallback(onPageChangeCallback)
binding.imageDescription.text = images[position].name
binding.imageAuthor.text = images[position].username
}

View File

@ -123,10 +123,10 @@ class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedC
}
private val imageChooserLauncher = registerForActivityResult(ImageChooserContract()) { image ->
Log.d("BottomSheetPostComment", "${image?.markdown}")
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
import android.net.Uri
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
@ -21,6 +22,8 @@ import com.isolaatti.utils.Resource
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@ -48,6 +51,8 @@ class CreatePostViewModel @Inject constructor(
val liveContent: MutableLiveData<String> = MutableLiveData()
var content: String = ""
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()
@ -188,4 +193,13 @@ class CreatePostViewModel @Inject constructor(
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.os.Bundle
import android.view.View
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.MediatorLiveData
@ -41,6 +42,7 @@ class CreatePostActivity : IsolaattiBaseActivity() {
var postId: Long = 0L
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
intent.extras?.let {
@ -85,14 +87,6 @@ class CreatePostActivity : IsolaattiBaseActivity() {
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() {

View File

@ -6,14 +6,10 @@ import androidx.viewpager2.adapter.FragmentStateAdapter
class CreatePostFragmentStateAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {
override fun getItemCount(): Int {
return 2
return 1
}
override fun createFragment(position: Int): Fragment {
return if(position == 0) {
PostEditingFragment()
} else {
MarkdownPreviewFragment()
}
return PostEditingFragment()
}
}

View File

@ -1,12 +1,22 @@
package com.isolaatti.posting.posts.ui
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.content.res.AppCompatResources
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.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
@ -14,6 +24,7 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import com.isolaatti.MyApplication
import com.isolaatti.R
import com.isolaatti.audio.audio_selector.ui.AudioSelectorContract
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.player.AudioPlayerConnector
import com.isolaatti.audio.recorder.ui.AudioRecorderContract
import com.isolaatti.common.IsolaattiTheme
import com.isolaatti.databinding.FragmentMarkdownEditingBinding
import com.isolaatti.images.common.components.ImagesRow
import com.isolaatti.images.image_chooser.ui.ImageChooserContract
import com.isolaatti.posting.link_creator.presentation.LinkCreatorViewModel
import com.isolaatti.posting.link_creator.ui.LinkCreatorFragment
import com.isolaatti.posting.posts.presentation.CreatePostViewModel
import java.io.File
import java.util.Calendar
class PostEditingFragment : Fragment(){
companion object {
@ -47,11 +62,10 @@ class PostEditingFragment : Fragment(){
}
private val imageChooserLauncher = registerForActivityResult(ImageChooserContract()) { image ->
Log.d(LOG_TAG, "${image?.markdown}")
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 {
override fun durationChanged(duration: Int, audio: Playable) {
binding.audioItem.audioProgress.max = duration
@ -112,6 +145,32 @@ class PostEditingFragment : Fragment(){
setupListeners()
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() {
@ -127,12 +186,6 @@ class PostEditingFragment : Fragment(){
viewModel.validation.postValue(!text.isNullOrEmpty())
viewModel.content = text.toString()
}
binding.addImageButton.setOnClickListener {
insertImage()
}
binding.addLinkButton.setOnClickListener {
insertLink()
}
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)
intent?.getLongExtra(POST_ID, 0)?.let {

View File

@ -97,7 +97,7 @@ class ProfileMainFragment : Fragment() {
private val chooseImageLauncher = registerForActivityResult(ImageChooserContract()) { image ->
// here change profile picture
if(image != null) {
viewModel.setProfileImage(image)
}
}
@ -255,7 +255,6 @@ class ProfileMainFragment : Fragment() {
Image(
profile.profileImageId ?: "",
profile.userId,
getString(R.string.user_profile_picture, profile.name),
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"
app:layout_constraintTop_toTopOf="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
android:id="@+id/toolbar"
android:layout_width="match_parent"
@ -28,15 +29,6 @@
</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
android:id="@+id/pager"
@ -47,7 +39,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tabs" />
app:layout_constraintTop_toBottomOf="@+id/appBarLayout" />
<ProgressBar
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"/>
</com.google.android.material.textfield.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/add_image_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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>
<androidx.compose.ui.platform.ComposeView
android:id="@+id/images_row_compose"
android:layout_width="match_parent"
android:layout_height="160dp"/>
<com.google.android.material.card.MaterialCardView
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="post">Post</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="drafts">Drafts</string>
<string name="report_profile">Report profile</string>