How to add the views on the second line if there is no space on the first line? - kotlin

I am trying to split some words on two lines to create a sentence. When there is no more space on the first line, the words should automatically go to the second line, but no matter what I have tried so far, only the first line is used, while the second line remains empty all the time.
Here is a screen capture.
MainActivity:
class MainActivity : AppCompatActivity(), RemoveAnswerListener {
private var binding: ActivityMainBinding? = null
var listAnswers = mutableListOf<Answer>()
private lateinit var actualAnswer: List<String>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding?.root)
actualAnswer = listOf(
"What",
"did",
"you",
"want",
"to",
"ask",
"me",
"yesterday?"
)
val answerOption = listOf(
"me",
"yesterday?",
"did",
"to",
"want",
"you",
"ask",
"What"
)
answerOption.forEach {
val key = TextView(binding?.answerBox?.context)
with(key){
binding?.answerBox?.addView(this)
setBackgroundColor(Color.WHITE)
text = it
textSize = 18F
setPadding(40, 20, 40, 20)
val margin = key.layoutParams as FlexboxLayout.LayoutParams
margin.setMargins(30, 30, 30, 30)
layoutParams = margin
setOnClickListener {
moveToAnswer(it)
}
}
}
}
private fun moveToAnswer(view: View) {
if(listAnswers.size < actualAnswer.size){
view.setOnClickListener(null)
listAnswers.add(Answer(view, view.x, view.y, (view as TextView).text.toString(), this#MainActivity))
val topPosition = binding?.lineFirst?.y?.minus(120F)
// val topPosition1 = binding?.lineSecond?.y?.minus(120F)
var leftPosition = binding?.lineFirst?.x
// var leftPosition1 = binding?.lineSecond?.x
if (listAnswers.size > 0) {
var allWidth = 0F
listAnswers.forEach {
allWidth += (it.view?.width)?.toFloat()!! + 20F
}
allWidth -= (view.width).toFloat()
if (allWidth + (view.width).toFloat() + 20F < binding?.lineFirst!!.width) {
leftPosition = binding?.lineFirst?.x?.plus(allWidth)
}else{
// leftPosition1 = binding?.lineSecond?.x?.plus(allWidth)
}
}
val completeMove = object: Animator.AnimatorListener{
override fun onAnimationRepeat(animation: Animator) {}
override fun onAnimationCancel(animation: Animator) {}
override fun onAnimationStart(animation: Animator) {}
override fun onAnimationEnd(animation: Animator) {
}
}
if (leftPosition != null) {
if (topPosition != null) {
view.animate()
.setListener(completeMove)
.x(leftPosition)
.y(topPosition)
}
}
}
}
}
And this is the data class "Answer":
data class Answer(var view: View? = null,
var actualPositionX: Float = 0F,
var actualPositionY: Float = 0F,
var text: String = "",
var removeListener: RemoveAnswerListener? = null
){
init {
view?.setOnClickListener {
it.animate()
.x(actualPositionX)
.y(actualPositionY)
removeListener?.onRemove(this)
}
}
}
interface RemoveAnswerListener{
fun onRemove(answer: Answer)
}

You don't seem to be offsetting the View to the second line anywhere? You just initialise its position to the start of the first line:
val topPosition = binding?.lineFirst?.y?.minus(120F)
var leftPosition = binding?.lineFirst?.x
And then you adjust the x position if there's room for it, otherwise it stays at the start
if (allWidth + (view.width).toFloat() + 20F < binding?.lineFirst!!.width) {
leftPosition = binding?.lineFirst?.x?.plus(allWidth)
}
(unless I'm misunderstanding things - I don't know what lineFirst is, a containing view for the first line I guess)
So you're not moving the view down, or to lineSecond or whatever - and you're not adjusting the x position based on the contents of the second line either.
Honestly if I were you, I'd look into the Flow helper for ConstraintLayout - it works like flexbox and basically moves elements below as they fill up the horizontal space, so it automatically does what you're doing here. Here's a guide on it that should give you the idea. Might save you a lot of hassle!

Related

My response data return " kotlinx.coroutines.flow.SafeFlow#1493a74"

I am trying to do dictionary app using kotlin language. I built the project with mvvm and clean architecture. I have been trying to pull vocabulary information from the internet using jsoap. I am using flow for data. I couldnt find where the issiue is. Normally, the words should appear on the screen or I should be able to see the data when I println on the console.But I can't see it on the screen or on the console, probably because the data coming from the internet is as follows.
kotlinx.coroutines.flow.SafeFlow#1493a74
I am sharing my codes below
ExtractedData
data class ExtractedData(
var id :Int = 0,
var word:String = "",
var meaning :String = ""
)
I created ExtractedData class to represent vocabulary or word data from internet
WordInfoRepositoryImpl
class WordInfoRepositoryImpl #Inject constructor(
private val api:DictionaryApi
) : WordInfoRepository {
//get words with meanings on the internet using jsoap
override fun getEventsList(): Flow<Resource<MutableList<ExtractedData>>> = flow {
emit(Resource.Loading())
val listData = mutableListOf<ExtractedData>()
try {
val url = "https://ielts.com.au/australia/prepare/article-100-new-english-words-and-phrases-updated-2020"
val doc = withContext(Dispatchers.IO){
Jsoup.connect(url).get()//-->Here it gives the following warning even though I have it in withContext `Inappropriate blocking method call`
}
val table = doc.select("table")
val rows = table.select("tr")
val eventsSize = rows.size
for (i in 1 until eventsSize) {
val row = rows[i]
val cols = row.select("td")
val word = cols[0].text()
val meaning = cols[1].text()
listData.add(ExtractedData(i,word,meaning))
}
}
catch (e: IOException) {
emit(Resource.Error("IO Exception"))
}
catch (e : HttpException) {
emit(Resource.Error("HTTP EXCEPTION"))
}
emit(Resource.Success(listData))
}
}
getEventsList is in my WordInfoRepositoryImpl class in my data layer here I am pulling data from internet using jsoap
WordInfoRepository
interface WordInfoRepository {
fun getEventsList(): Flow<Resource<MutableList<ExtractedData>>>
}
this is the interface that I reference wordInforepositoryImpl in the data layer in my interface domain layer
GetWordsAndMeaningsOnTheInternetUseCase
class GetWordsAndMeaningsOnTheInternetUseCase#Inject constructor(
private val repository: WordInfoRepository
){
operator fun invoke() : Flow<Resource<MutableList<ExtractedData>>> {
return repository.getEventsList()
}
}
GetWordsAndMeaningsOnTheInternetUseCase is my usecase in my domain layer
ViewModel
#HiltViewModel
class MostUsedWordScreenViewModel #Inject constructor(
private val getWordsAndMeaningsOnTheInternetUseCase: GetWordsAndMeaningsOnTheInternetUseCase
) : ViewModel() {
private var searchJob: Job? = null
private val _state = mutableStateOf(MostUsedWordState())
val state: State<MostUsedWordState> = _state
init {
fetchData()
}
private fun fetchData() {
searchJob?.cancel()
searchJob = viewModelScope.launch(IO) {
getWordsAndMeaningsOnTheInternetUseCase().onEach { result ->
when (result) {
is Resource.Success -> {
_state.value = state.value.copy(
mostWordUsedItems = result.data ?: mutableListOf(),
isLoading = false
)
}
is Resource.Error -> {
_state.value = state.value.copy(
mostWordUsedItems = result.data ?: mutableListOf(),
isLoading = false
)
}
is Resource.Loading -> {
_state.value = state.value.copy(
mostWordUsedItems = result.data ?: mutableListOf(),
isLoading = true
)
}
}
}
}
}
}
MostUsedWordScreen
#Composable
fun MostUsedWordScreen(viewModel: MostUsedWordScreenViewModel = hiltViewModel()) {
val state = viewModel.state.value
println("state --- >>> "+state.mostWordUsedItems)
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
items(state.mostWordUsedItems.size) { i ->
val wordInfo = state.mostWordUsedItems[i]
if(i > 0) {
Spacer(modifier = Modifier.height(8.dp))
}
MostUsedWordItem(word = wordInfo)
if(i < state.mostWordUsedItems.size - 1) {
Divider()
}
}
}
}
#Composable
fun MostUsedWordItem(word : ExtractedData ) {
// println("this is MostUsedWordItem")
Column(modifier = Modifier
.padding(5.dp)
.fillMaxWidth()) {
Text(text = word.word,
modifier = Modifier.padding(3.dp),
textAlign = TextAlign.Center,
fontSize = 18.sp,
)
}
}
It is included in the MostUsedWordScreenViewModel and MostUsedWordScreen presententation layer
Where I println("state --- >>> "+state.mostWordUsedItems) in MostUsedWordScreen, the state console shows as empty like this System.out: state --- >>> []
I tried to explain as detailed as I can, I hope you can understand.
A Flow doesn't do anything until you call a terminal operator on it. You called onEach, which is not a terminal operator. You should use collect. Or you can avoid the nesting inside a launch block by using onEach and launchIn, which does the same thing as launching a coroutine and calling collect() on the flow. You don't need to specify Dispatchers.IO here because nothing in your Flow is blocking. You correctly wrapped the blocking call in withContext(Dispatchers.IO), and the warning is a false positive. That's a well-known bug in their compiler inspection.
searchJob = getWordsAndMeaningsOnTheInternetUseCase().onEach { result ->
when (result) {
is Resource.Success -> {
_state.value = state.value.copy(
mostWordUsedItems = result.data ?: mutableListOf(),
isLoading = false
)
}
is Resource.Error -> {
_state.value = state.value.copy(
mostWordUsedItems = result.data ?: mutableListOf(),
isLoading = false
)
}
is Resource.Loading -> {
_state.value = state.value.copy(
mostWordUsedItems = result.data ?: mutableListOf(),
isLoading = true
)
}
}
}.launchIn(viewModelScope)
By the way, you need to move your emit(Success...) inside your try block. The way it is now, when there is an error, the error will immediately get replaced by a Success with empty list.
Side note, I recommend avoiding passing MutableLists around between classes. You have no need for them and it's a code smell. Sharing mutable state between classes is error-prone. I don't think there is any justification for using a Flow<MutableList> instead of a Flow<List>.
You rarely even need a MutableList locally in a function. For example, you could have done in your try block:
val listData = List(eventsSize - 1) {
val row = rows[it + 1]
val cols = row.select("td")
val word = cols[0].text()
val meaning = cols[1].text()
ExtractedData(i,word,meaning)
}
emit(Resource.Success(listData))

how to add a points system to an app preferences DataStore Jetpack Compose

I'm working on a Quiz app and I'm trying to add a points system to the app so that everytime the user gets a question right he gets a +1pts.
and for storing the points I use jetpack compose preferences Datastore.
the problem is whenever I want to add a point to the already saved points it doesn't work.
this is my PointsData
class PointsData(private val context: Context) {
//create the preference datastore
companion object{
private val Context.datastore : DataStore<Preferences> by preferencesDataStore("points")
val CURRENT_POINTS_KEY = intPreferencesKey("points")
}
//get the current points
val getpoints: Flow<Int> =context.datastore.data.map { preferences->
preferences[CURRENT_POINTS_KEY] ?: 0
}
// to save current points
suspend fun SaveCurrentPoints(numPoints : Int){
context.datastore.edit {preferences ->
preferences[PointsData.CURRENT_POINTS_KEY] = numPoints
}
}
}
save points methode
class SavePoints {
companion object {
#Composable
fun savepoints(numPointsToSave : Int) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val datastore = PointsData(context)
LaunchedEffect(1) {
scope.launch {
datastore.SaveCurrentPoints(numPointsToSave)
}
}
}
}
}
and whenever i want to get the number of points from my DataStore i use
val pointsdatastore = PointsData(context)
val currentpoints = pointsdatastore.getpoints.collectAsState(initial = 0)
//display it as text for example
Text(text = currentpoints.value.toString(), fontSize = 30.sp, fontWeight = FontWeight.Bold,
color = Color.White)
and to do the operation i want (add +1 o the already existing points i do this
val pointsdatastore = PointsData(context)
val currentpoints = pointsdatastore.getpoints.collectAsState(initial = 0)
SavePoints.savepoints(numPointsToSave = currentpoints.value + 1)
but it doesn't seem to work because the number of points always stays 1.
if you know whats the problem please help.
I found the answer my self but for anyone who is stuck in same situation the solution is to another method in PointsData(look at the question provided code)
the method is:
suspend fun incrementpoints(){
context.datastore.edit { preferences->
val currentpoints = preferences[CURRENT_POINTS_KEY] ?: 0
preferences[CURRENT_POINTS_KEY] = currentpoints + 1
}
}
(if you want decrement not increment you can just change + to - )
and now in the PointsMethod(look at the question provided code) you should add
#Composable
fun incrementpoints() {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val datastore = PointsData(context)
LaunchedEffect(key1 = 1) {
scope.launch {
datastore.incrementpoints()
}
}
}

Infinite looping row of images

How can I create a scrolling row that scrolls automatically at a fixed speed that loops around the content of a list of images?
I have a lazy row of images as defined below, but haven't found a good way to loop it (like a carousel).
var images: List<String> = listOf()
repeat(8) {
images = images.plus("https://place-puppy.com/300x300")
}
val state = rememberLazyListState()
LazyRow(
modifier = modifier.fillMaxWidth(),
state = state
) {
items(count = images.size) { i ->
val image = images.get(i)
Column(
modifier = Modifier
.width(40.dp)
.aspectRatio(1f)
) {
Image(
painter = rememberImagePainter(image),
contentDescription = null,
modifier = Modifier
.fillMaxSize()
}
}
}
firstly, create an infinite auto-scrolling effect that will be running as long as the composable is active & displayed:
LazyRow() {
....
}
LaunchedEffect(Unit) {
autoScroll(lazyListState)
}
private tailrec suspend fun autoScroll(lazyListState: LazyListState) {
lazyListState.scroll(MutatePriority.PreventUserInput) {
scrollBy(SCROLL_DX)
}
delay(DELAY_BETWEEN_SCROLL_MS)
autoScroll(lazyListState)
}
private const val DELAY_BETWEEN_SCROLL_MS = 8L
private const val SCROLL_DX = 1f
Secondly, update positions of items in the list accordingly:
val lazyListState = rememberLazyListState()
LazyRow(
state = lazyListState,
modifier = modifier,
) {
items(images) {
...
if (it == itemsListState.last()) {
val currentList = images
val secondPart = currentList.subList(0, lazyListState.firstVisibleItemIndex)
val firstPart = currentList.subList(lazyListState.firstVisibleItemIndex, currentList.size)
rememberCoroutineScope().launch {
lazyListState.scrollToItem(0, maxOf(0, lazyListState.firstVisibleItemScrollOffset - SCROLL_DX.toInt()))
}
images = firstPart + secondPart
}
}
}
That should give you the looping behavior.
Credits: https://proandroiddev.com/infinite-auto-scrolling-lists-with-recyclerview-lazylists-in-compose-1c3b44448c8

LazyColumn with nested LazyRows - memory issue

In my app there's a LazyColumn that contains nested LazyRows. I have a memory issue - when there are 30-40 rows and about 10-20 elements per row in the grid, it's possible to reach Out-of-Memory (OOM) by simply scrolling the list vertically up and down about 20 times. An item is a Card with some Boxes and texts. It seems that the resulting composable for each of the items is stored, even when the item is out of composition.
Here is a sample that demonstrates this. It shows a simple grid of 600 elements (they are just Text) and on my emulator gets to a memory usage of about 200 MB. (I use Android TV emulator with landscape, 120 elements are visible at once).
MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
LazyColumnTestTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
runTest()
}
}
}
}
}
#Composable
fun runTest() {
var itemsState: MutableState<List<TestDataBlock>> = remember {
mutableStateOf(listOf())
}
LaunchedEffect(Unit) {
delay(1000)
itemsState.value = MutableList<TestDataBlock>(30) { rowIndex ->
val id = rowIndex
TestDataBlock(id = id.toString(), data = 1)
}
}
List(dataItems = itemsState.value)
}
#Preview
#Composable
fun List(
dataItems: List<TestDataBlock> = listOf(TestDataBlock("1",1), TestDataBlock("2",2))
) {
LazyColumn(
modifier = Modifier
.fillMaxSize()
) {
itemsIndexed(items = dataItems,
key = { _, item ->
item.id
}
) { _, rowItem ->
drawElement(rowItem)
}
}
}
#Composable
fun drawElement(rowItem: TestDataBlock) {
Text(text = "${rowItem.id}")
LazyRow() {
itemsIndexed(items = rowItem.testDataItems,
key = { _, item ->
item.id
}
) { _, item ->
Text(text = "${item.id }", color = Color.Black, modifier = Modifier.width(100.dp))
}
}
}
TestDataBlock.kt
#Immutable
data class TestDataBlock(
val id: String,
val data: Int,
) {
val testDataItems: List<TestDataItem> = (0..20).toList().map{ TestDataItem(it.toString()) }
}
TestDataItem.kt
#Immutable
data class TestDataItem(
val id: String
)

How do I display a new image in tornadofx imageview?

I want to display a WritableImage in imageview, but I want that image to change when the user loads in a new file from the file browser. I know that there is a bind() function for strings that change over time, but I could not find a similar option for images. I could solve the problem for images that are the same size as the default loaded one (with writing through the pixels), but that only works if they are the same size, since I cant modify the size of the image that I displayed.
My Kotlin code so far:
class PhotoView : View("Preview") {
val mainController: mainController by inject()
override val root = hbox {
imageview{
image = mainController.moddedImg
}
hboxConstraints {
prefWidth = 1000.0
prefHeight = 1000.0
}
}
class ControlView: View(){
val mainController: mainController by inject()
override val root = hbox{
label("Controls")
button("Make BW!"){
action{
mainController.makeBW()
}
}
button("Choose file"){
action{
mainController.setImage()
mainController.update()
}
}
}
}
class mainController: Controller() {
private val ef = arrayOf(FileChooser.ExtensionFilter("Image files (*.png, *.jpg)", "*.png", "*.jpg"))
private var sourceImg=Image("pic.png")
var moddedImg = WritableImage(sourceImg.pixelReader, sourceImg.width.toInt(), sourceImg.height.toInt())
fun setImage() {
val fn: List<File> =chooseFile("Choose a photo", ef, FileChooserMode.Single)
sourceImg = Image(fn.first().toURI().toString())
print(fn.first().toURI().toString())
}
fun makeBW() {
val pixelReader = sourceImg.pixelReader
val pixelWriter = moddedImg.pixelWriter
// Determine the color of each pixel in a specified row
for (i in 0 until sourceImg.width.toInt()) {
for (j in 0 until sourceImg.height.toInt()) {
val color = pixelReader.getColor(i, j)
pixelWriter.setColor(i, j, color.grayscale())
}
}
}
fun update() {
val pixelReader = sourceImg.pixelReader
val pixelWriter = moddedImg.pixelWriter
// Determine the color of each pixel in a specified row
for (i in 0 until sourceImg.width.toInt()) {
for (j in 0 until sourceImg.height.toInt()) {
val color = pixelReader.getColor(i, j)
pixelWriter.setColor(i, j, color)
}
}
}
}
ImageView has a property for the image that you can bind:
class PhotoView : View("Preview") {
val main: MainController by inject()
val root = hbox {
imageview { imageProperty().bind(main.currentImageProperty) }
...
}
...
}
class MainController : Controller() {
val currentImageProperty = SimpleObjectProperty<Image>(...)
var currentImage by currentImageProperty // Optional
...
}
From there, any time you set the currentImage in MainController, it will update in the PhotoView.