Kotlin - Add more variant - soundPool - android

please help. I need to add 2 sounds ogg to one action, which will change randomly. How do I do that? I don't know how to do it. Thank you very much.
Thread(Runnable {
val assets = context.resources.assets
sounds[SOUND_DIE] = soundPool.load(assets.openFd("die.ogg"), 1)
sounds[SOUND_HIT] = soundPool.load(assets.openFd("hit.ogg"), 1)
sounds[SOUND_POINT] = soundPool.load(assets.openFd("point.ogg"), 1)
sounds[SOUND_SWOOSHING] = soundPool.load(assets.openFd("swooshing.ogg"), 1)
sounds[SOUND_WING] = soundPool.load(assets.openFd("wing.ogg"), 1)
}).start()
}

You can call .random() on a collection to get a random item. So instead of mapping each sound type (like SOUND_HIT) to one sound, map each one to a list of sounds (which can contain one item if that's all you have). And then for every sound you load, just add it to the appropriate list.
That way when you want to play a sound, you can go play(sounds[SOUND_SWOOSHING].random()) and it will just pick one from that sound type's list.
You can set it up the way you're doing now
sounds = mapOf(
SOUND_DIE to listOf(
soundPool.load(assets.openFd("die.ogg"), 1),
soundPool.load(assets.openFd("yargh.ogg"), 1)
),
SOUND_HIT to ...
)
but I'd recommend adding a function to handle all that loading:
fun loadSound(filename: String) = soundPool.load(assets.openFd(filename), 1)
sounds = mapOf(
SOUND_DIE to listOf(
loadSound("die.ogg"),
loadSound("yargh.ogg")
),
SOUND_HIT to ...
)
or if you want to get fancy...
val filenamesToTypes = mapOf(
"die.ogg" to SOUND_DIE,
"yargh.ogg" to SOUND_DIE,
"point.ogg" to SOUND_POINT,
...
)
// build your sounds collection by grouping all the filenames
// with the same sound type, and transform each filename to a
// loaded sound, so you get a map of SoundType -> List<Sound>
sounds = filenamesToTypes.entries.groupBy(
keySelector = { it.value },
valueTransform = { loadSound(it.key) }
)
Don't worry if that feels too complicated, the first couple of examples are neat enough and hopefully easy to follow! I just like when you can organise stuff all snappy :)

Related

How do you call a variable created earlier in a when in kotlin?

I'm trying to list the first 100 of a shuffled list. I'm telling it to shuffle if the list is at 0 and then increment. I then am trying to call that list in another section of the when but it's not working. How can I accomplish this?
when (countF) {
0 -> {
//shuffle at 0
val randomChaos = chaosList.asSequence().shuffled().take(chaosList.count()).toList()
cResult.text = randomChaos.elementAt(countF) + countF + "\n\n\n\n\n\n\n\n" + this.cResult.text
countF++
}
1-99 -> {
//show 1-99
cResult.text = randomChaos.elementAt(countF) + countF + "\n\n\n\n\n\n\n\n" + this.cResult.text
countF++
}
100 -> countF = 0
You would need to create the val randomChaos before the when enclosure for it to be available in the scope of multiple branches of the when statement.
That said, the way you're getting a random element is very convoluted. take(chaosList.count()) is completely redundant. And since you don't use multiple sequence operators, creating a sequence is also redundant. Finally, you are only pulling a single item from the random list, so it's unnecessary to create a shuffled list in the first place. Using elementAt() on a shuffled list is no different than picking any element out of that shuffled list, or simply picking a random item out of a list that isn't shuffled at all.
Also, the first two branches of your when statement currently would produce exactly the same results so they can be merged.
Based on what you described, I'm guessing you had this when statement inside a loop that tries to run it 100 times so you can list all the items. For that to work, you would need to shuffle the list one time outside the loop, and then you could iterate its elements in the loop.
However, there are functions that can make it easier to do what you're suggesting. Here's an example:
val randomChaos = chaosList.shuffled()
cResult.text = randomChaos.asSequence()
.take(100)
.withIndex()
.joinToString("\n") { (i, value) ->
"$value-$i"
}
In this case, using a Sequence helps avoid creating an intermediate list to hold the first 100 values.
var randomChaos = chaosList.shuffled()
fun cShuf() { randomChaos = chaosList.shuffled() }
cRoll.setOnClickListener() {
cResult.movementMethod = ScrollingMovementMethod()
if (countF < 1) { cShuf() }
cResult.text = randomChaos.elementAt(countF) + "\n\n\n\n\n\n\n\n" + this.cResult.text
countF++
if (countF > 100) countF = 0
}
I have figured out how to use a function to generate a new shuffe of the list once I've hit > 100 shown.
My issue with making it a function was I was trying to use val variable in the function but the variable already existed so I didn't need to use val, just the name of the variable.

Kotlin list transform data stream

I'm building a chat platform, where I'm reading my list of messages from Local Room DB (ChatModel). I need to add date separators between these messages. I've to use multiple view holders and thus created a sealed class for differentiating items
sealed class ChatUiModel {
data class ChatItem(val message: ChatModel) : ChatUiModel()
data class DateSeparatorItem(val time: String) : ChatUiModel()
}
I require to convert the list with date separate items in between 2 models of the list, I'm not proficient with Collection functions in kotlin and confused between map/flatmap etc.
.observe(viewLifecycleOwner) { messages ->
messages.map {
// if item prev.date < item next.date
ChatUiModel.DateSeparatorItem(it.date.toReadableTime())
ChatUiModel.ChatItem(it)
}
chatAdapter.submitList(messages)
}
Reached to this
val items = mutableListOf<ChatUiModel>()
val data = messages.listIterator()
for (item in data) {
if (data.hasPrevious())
if (data.previous().time < item.time)
items.add(ChatUiModel.DateSeparatorItem(item.time))
items.add(ChatUiModel.ChatItem(item))
}
Timber.i("CHAT = $items")
An easy way to prepare the list can be:
messages
.groupBy { it.date }
.map { (date, chatModels) ->
listOf(DateSeparatorItem(date)) + chatModels.map { ChatItem(it) }
}
.flatten()
try it yourself
Here we first group all the messages by their data to get a Map<Long, List<ChatModel>. Then we map each entry of the map to a new list containing the DateSeparator and the ChatItems for that date. Finally, we flatten the entire list to get the desired List<ChatUiModel>.
In the code that I linked, I have used Long for the date. If you have a String you can easily interconvert them using java.time APIs.
If your messages list is not sorted initially, add a sortedBy function before groupBy to sort it first.
(this ended up long but I thought you'd like an explanation of what's going on - you can just skip to the end for the solutions if you want)
Ok, so this is a little tricky if you're not familiar with all the utility functions and general functional manipulation - what you're basically doing is transforming incoming messages into ChatItems, but you also want to compare each message to the previous one, and output a DateSeparatorItem first where necessary, right?
A straight map isn't going to work - that just transforms each item into another item (it's mapping one value to another), and sometimes you want to transform one item into two (a date item and a chat item).
You could map each message item into a list, and make that contain either a chat item, or a date+chat. So that would give you a list of lists, which you could then flatten so you just get all those items in order, in a single list. That's basically what flatmap does!
So now you need to be able to compare multiple messages, so you can check the dates. Kotlin has this windowed function that acts like a sliding view across your collection, so it can transform [1, 2, 3, 4] into [[1, 2], [2, 3], [3, 4]], and then you can work on those groups. There's a more convenient zipWithNext function that only produces Pairs instead of arbitrarily sized Lists - i.e. [(1, 2), (2, 3), (3, 4)], but windowed has a useful option - partialWindows allows that window to keep moving to the end of the list, even as it runs out of items to fill the full window:
listOf(1, 2, 3, 4).windowed(size=3, partialWindows=true).run(::println)
>> [[1, 2, 3], [2, 3, 4], [3, 4], [4]]
If we do this for a window of size 2, we get every original message, and also the one following it if there is one (zipWithNext will stop when it runs out of complete pairs):
listOf(1, 2, 3, 4).windowed(size=2, partialWindows=true).run(::println)
>> [[1, 2], [2, 3], [3, 4], [4]]
We can use this!
Your logic right now is taking a message and comparing it to the previous one to see if a date needs inserting before the chat item - I'd suggest flipping that around, and inserting a date after the current item by checking the next item's timestamp. That's because windowed is giving you each item along with the next one, so you don't get to look at the previous one.
We're working with a list here, and we need to compare the first item to the second one (checking if there even is one), but we can be a little bit cheeky and just compare list.first() with list.last(). We know there's gonna be either one or two items - and if there's only one item in the list (i.e. it's the last message) then we're comparing it with itself, and since we're only adding the date item if the timestamps are different... well they won't be if it's the same item! So there won't be any rogue date items added at the end. Probably worth documenting the code if you do that since it might not be clear - you can write some more explicit logic if you want.
Here's a few ways to do the final thing:
Kotlin Playground example
data class Message(val text: String, val time: Int)
val messages = listOf(
Message("hey", 1),
Message("u up", 1),
Message("lol", 3),
Message("wow", 10)
)
fun withMutableList() {
messages.windowed(size=2, partialWindows=true)
// or map followed by flatten()
.flatMap { items ->
val current = items.first()
val next = items.last()
// creating a mutable list with the chat item, optionally adding a date
mutableListOf<ChatUiModel>(ChatItem(current)).apply {
if (next.time > current.time) add(DateItem(next.time))
}
}
.forEach(::println)
}
fun withNulls() {
messages.windowed(size=2, partialWindows=true)
.flatMap { items ->
val current = items.first()
val next = items.last()
// either adding a date or a null, nulls get removed later
listOf(
ChatItem(current),
if (next.time > current.time) DateItem(next.time) else null
)
}
.filterNotNull()
.forEach(::println)
}
fun withSequence() {
sequence {
messages.windowed(size=2, partialWindows=true)
.forEach { items ->
val current = items.first()
val next = items.last()
// just yielding a stream of items, nice and neat!
yield(ChatItem(current))
if (next.time > current.time) yield(DateItem(next.time))
}
}.forEach(::println)
}
all giving this output:
ChatItem(message=Message(text=hey, time=1))
ChatItem(message=Message(text=u up, time=1))
DateItem(time=3)
ChatItem(message=Message(text=lol, time=3))
DateItem(time=10)
ChatItem(message=Message(text=wow, time=10))

how to create dynamic setImageResource

How can I replace something like:
when (content[position].ImageSrc) {
1 -> holder.imageView.setImageResource(R.drawable.image_1)
2 -> holder.imageView.setImageResource(R.drawable.image_2)
3 -> holder.imageView.setImageResource(R.drawable.image_3)
else -> {
holder.imageView.setImageResource(R.drawable.image_x)
}
}
more elegant so something like:
var a = "image_"
var b = content[position].ImageSrc
var c = a + b
holder.imageView.setImageResource(R.drawable.c)
The first code is working but way to laborious, the second one isn't.
Thx:)
Make a Repository that can resolve the asset for you...
in your UI you'd do:
holder.imageView.setImageResource(
myResourceRepo.getImageResourceFor(content[position].ImageSrc)
)
Now what you do in there, it really depends, if you really have 100 resources, you have to find a way to MAP them together, either by creating a mapOf(Int, "img source") (whatever that is), where Int is the R.drawable.xxx and img source is whatever is in content[...].imageSrc (a String?)
This is how you map/associate them together.
If you want it to be more dynamic, then store your images in "raw assets" and using the AssetManager, load the image and convert it into a Drawable at runtime. This way you get to "construct" the asset name like you did
var filename = "some/path/" + content[...].imageSrc
val file = assetManager.open(filename)...
val drawable = makeDrawableFrom(file)
return drawable
Your viewHolder will then use it as
val drawable = repo.getDrawableFrom(content[x].imageSrc)
holder.imageView.setImageResource(drawable)
(NOTE: most of these are "pseudo-code", you have to implement most of it, but it's all possible and quite simple).
If you REALLY want to try to load a resource dynamically... it used to be possible with something like
val nameOfResource = "a" + "b" + "c" //construct your "dynamic" name
val drawable = context.resources.getIdentifier(nameOfResource,
"drawable", context.getPackageName())
(not sure if this still works)
I doubt your "second method" exist instead, you can reformat your first one as follows,
holder.imageView.setImageResource(
when (content[position].ImageSrc) {
1 -> R.drawable.image_1
2 -> R.drawable.image_2
3 -> R.drawable.image_3
else ->R.drawable.image_else
}
)
val imageId = resources.getIdentifier("com.packagename.app:drawable/image_${content[position].ImageSrc}", null, null);
holder.imageView.setImageResource(imageId)

How to set timers to repeat three functions (showing pic in imageview) every certain amount of time?

So currently I am making a Pokédex for my internship.
I've got it working for the biggest part but there's a couple of things I need to get fixed including the question asked above.
I have a function called Eeveelutions which has to run three other functions called showVaporeon, showJolteon & showFlareon. They have to run for like 3 seconds each and then just loop around until the person using the pokedex goes to the next or previous pokémon.
The thing I need help with is how would I set a timer, (if that's the best way to do it) to run those functions. So, showVaporeon for 3 seconds, then showJolteon for 3 seconds, then Flareon for 3 seconds and repeat.
I have searched loads of questions to find my solution but I can't find it yet and most of it is not in kotlin.
So, is there anyone who has got an easy example for me or a better solution (and example) then using a timer.
Searched forum for solutions,
messed around with timers,
messed around with threads but no solutions yet
fun showVaporeon(){
evoChart2.visibility = View.VISIBLE
Glide.with(this).load(imageBaseURL + "134" + ".png").into(evoChart2)
evolveOption2.text = "Vaporeon"
evolveOption2.text = ""
evoChart2.visibility = View.GONE
}
fun showJolteon(){
evoChart2.visibility = View.VISIBLE
Glide.with(this).load(imageBaseURL + "135" + ".png").into(evoChart2)
evolveOption2.text = "Jolteon"
evolveOption2.text = ""
evoChart2.visibility = View.GONE
}
fun showFlareon(){
evoChart2.visibility = View.VISIBLE
Glide.with(this).load(imageBaseURL + "136" + ".png").into(evoChart2)
evolveOption2.text = "Flareon"
evolveOption2.text = ""
evoChart2.visibility = View.GONE
}
So I would want evoChart2 (which is one of the three imageviews I have) to show Vaporeon for 3 seconds, then Jolteon for 3 seconds, then Flareon for 3 seconds, and then Vaporeon again for 3 seconds, Jolteon, Flareon etc.
Since you're wanting a loop, I'd suggest using a list to make it easier to iterate and add more pokemon in future.
Create a Pokemon data class if you don't have one already.
data class Pokemon(val name: String, val imageUrl: String)
Create however many instances of this class as you need and add them to a list.
val pokemonList: List<Pokemon> = listOf(vaporeon, jolteon, flareon)
We'll also need to store the index of the current pokemon we're displaying
var currentIndex = 0
Next we'll create a Runnable and schedule it to execute every three seconds, you may want to do this in onResume.
val service = Executors.newSingleThreadScheduledExecutor()
service.scheduleAtFixedRate({ displayPokemon() }, 0, 3, TimeUnit.SECONDS)
Now create the displayPokemon function that is going to be called every 3 seconds.
fun displayPokemon() {
}
Inside this function we need to know what the next pokemon we're displaying is, based on the current pokemon.
val next = currentIndex + 1
if (next >= pokemonList.size) {
// we're at the end, go back to 0
currentIndex = 0
} else {
currentIndex = next
}
val pokemon = pokemonList[currentIndex]
Now that we have the next pokemon to display we can use it to populate the view
evoChart2.visibility = View.VISIBLE
Glide.with(this).load(pokemon.imageUrl).into(evoChart2)
evolveOption2.text = pokemon.name
evolveOption2.text = ""
evoChart2.visibility = View.GONE
Finally, we don't want this to happen when the Activity/Fragment is in the background so we add the following code to onPause
service.shutdown()

Kotlin check for words in string

I have a NSFW class that scans texts like item names and descriptions against a list of known NSFW-words.
That would be the best approach to test a list of strings like
let nsfw = listof(
"badword",
"curseword",
"ass",
... 200+ more
)
against a string like:
This is the text that contains a badword // returns true
Please note that i need to check for full words. not parts of words.
so the sentence:
The grass is grean // returns false
Because grass is not a bad word.
Ive tried something like this but it doesnt check for full words.
val result = nsfw.filter { it in sentence.toLowerCase() }
You may build a regex like
\b(?:word1|word2|word3...)\b
See the regex demo. Then, use it with the Regex.containsMatchIn method:
val nsfw = listOf(
"badword",
"curseword",
"ass"
)
val s1 = "This is the text that contains a badword"
val s2 = "The grass is grean"
val rx = Regex("\\b(?:${nsfw.joinToString(separator="|")})\\b")
println(rx.containsMatchIn(s1)) // => true
println(rx.containsMatchIn(s2)) // => false
See this Kotlin demo.
Here, nsfw.joinToString(separator="|") joins the words with a pipe (the alternation operator) and the "\\b(?:${nsfw.joinToString(separator="|")})\\b" creates the correct regex.
If your words may contain special regex metacharacters, like +, ?, (, ), etc., you need to "preprocess" the nsfw values with the Regex.escape method:
val rx = Regex("\\b(?:${nsfw.map{Regex.escape(it)}.joinToString("|")})\\b")
^^^^^^^^^^^^^^^^^^^^^^
See the Kotlin demo.
AND one more thing: if the keywords may start/end with chars other than letters, digits and underscores, you cannot rely on \b word boundaries. You may
Use whitespace boundaries: val rx = Regex("(?<!\\S)(?:${nsfw.map{Regex.escape(it)}.joinToString("|")})(?!\\S)")
Use unambiguous word boundaries: val rx = Regex("(?<!\\w)(?:${nsfw.map{Regex.escape(it)}.joinToString("|")})(?!\\w)")
You can use split() on the string that you want to check, with space as a delimiter, so you create a list of its words, although this does not always guarantee that all words will be extracted successfully, since there could exist other word separators like dots or commas etc. If that suits you, do this:
val nsfw = listOf(
"badword",
"curseword",
"ass"
)
val str = "This is the text that contains a badword"
val words = str.toLowerCase().split("\\s+".toRegex())
val containsBadWords = words.firstOrNull { it in nsfw } != null
println(containsBadWords)
will print
true
If you want a list of the "bad words":
val badWords = words.filter { it in nsfw }

Categories

Resources