Use custom image or text style for reusable Composable in Jetpack Compose - material-components-android

I am currently working on two Android projects, both of which use Jetpack Compose. Both projects use a self-developed library that contains custom composables.
My problem now is that I don't know how to reference project-specific text styles, images or other design characteristics in the library. The implementation of Material Design is unfortunately not sufficient for this.
To give an example:
Both app should have an error screen in case something goes wrong. The structure of the error screen should be the same in both apps. The error screen should contain an error message, and error image and a retry button. To avoid duplicate code i moved the error screen composable to the library. No problem at that point.
But the error screen should also adapt to the app theme. Currently i use a drawable in the error screen composable located inside the library module. The button using the MaterialTheme.typography.button style and the message uses MaterialTheme.typography.body1 style.
But this is not the behavior i want to achieve here. I want to use different error images for both apps and the button and message styles could be completely different depending on the app which is using the composable.
Is there any clever way to achieve this behavior?
Edit: I used the error screen as an easy to understand example for this question, but did not implemented it in the library. But I implemented a profile composable where I am facing the exact same problem. Currently I am using the approach to pass the text styles as parameters, but this is getting ugly really fast:
#Composable
fun Profile(
modifier: Modifier = Modifier,
user: User,
sections: List<Section>,
userIsLoggedIn: Boolean,
onLogin: () -> Unit,
onLogout: () -> Unit,
loginButtonTextStyle: TextStyle = MaterialTheme.typography.button.copy(
textAlign = TextAlign.Center
),
logoutButtonTextStyle: TextStyle = MaterialTheme.typography.button.copy(
color = MaterialTheme.colors.onSurface,
textAlign = TextAlign.Center
),
usernameTextStyle: TextStyle = MaterialTheme.typography.h5.copy(
color = MaterialTheme.colors.onSurface,
textAlign = TextAlign.Center
),
sectionTitleTextStyle: TextStyle = MaterialTheme.typography.subtitle1.copy(
color = MaterialTheme.colors.onSurface
),
itemTitleTextStyle: TextStyle = MaterialTheme.typography.body1,
) {
Column(
modifier = modifier
.verticalScroll(rememberScrollState())
.padding(top = 24.dp)
) {
if (userIsLoggedIn) {
UsernameHeader(
username = user.username,
textStyle = usernameTextStyle
)
} else {
LoginButton(
onLogin = onLogin,
textStyle = loginButtonTextStyle,
)
}
SectionsContainer(
sections = sections,
sectionTitleTextStyle = sectionTitleTextStyle,
itemTitleTextStyle = itemTitleTextStyle
)
if (userIsLoggedIn) {
LogoutButton(
onLogout = onLogout,
textStyle = logoutButtonTextStyle
)
}
}
}
#Composable
private fun UsernameHeader(
username: String,
textStyle: TextStyle
) {
Text(
text = "Hello $username",
style = textStyle,
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth()
)
Spacer(modifier = Modifier.height(32.dp))
}
#Composable
private fun LoginButton(
textStyle: TextStyle,
onLogin: () -> Unit
) {
Button(
onClick = onLogin,
modifier = Modifier
.padding(
start = 16.dp,
end = 16.dp,
)
) {
Text(
text = "LOGIN",
style = textStyle,
modifier = Modifier
.fillMaxWidth()
.padding(top = 7.dp, bottom = 9.dp)
)
}
Spacer(modifier = Modifier.height(24.dp))
}
#Composable
private fun LogoutButton(
textStyle: TextStyle,
onLogout: () -> Unit
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 32.dp)
) {
TextButton(
onClick = onLogout,
modifier = Modifier
.padding(
start = 24.dp,
end = 24.dp,
top = 7.dp,
bottom = 9.dp
)
) {
Text(
text = "LOGOUT",
style = textStyle,
)
}
}
}
#Composable
private fun SectionsContainer(
modifier: Modifier = Modifier,
sections: List<Section>,
sectionTitleTextStyle: TextStyle,
itemTitleTextStyle: TextStyle
) {
Column(modifier = modifier) {
for (section in sections) {
ProfileSection(
section = section,
titleTextStyle = sectionTitleTextStyle,
itemTitleTextStyle = itemTitleTextStyle,
)
}
}
}
#Composable
private fun ProfileSection(
modifier: Modifier = Modifier,
section: Section,
titleTextStyle: TextStyle,
itemTitleTextStyle: TextStyle,
) {
Column(
modifier.padding(bottom = 16.dp)
) {
Text(
text = section.title.toUpperCase(Locale.ROOT),
style = titleTextStyle,
modifier = Modifier
.padding(
start = 16.dp,
end = 16.dp,
top = 9.dp,
bottom = 10.dp
)
.fillMaxWidth()
)
for (item in section.items) {
ProfileItem(
item = item,
itemTitleTextStyle = itemTitleTextStyle
)
}
}
}
#Composable
private fun ProfileItem(
item: Item,
itemTitleTextStyle: TextStyle
) {
when (item) {
is TextItem -> ProfileTextItem(
textItem = item,
titleTextStyle = itemTitleTextStyle
)
is ImageItem -> ProfileImageItem(imageItem = item)
}
}
#Composable
private fun ProfileTextItem(
textItem: TextItem,
titleTextStyle: TextStyle
) {
Text(
text = textItem.title,
style = titleTextStyle,
modifier = Modifier
.padding(
start = 16.dp,
end = 16.dp,
top = 15.dp,
bottom = 12.dp
)
.fillMaxWidth()
)
}
#Composable
private fun ProfileImageItem(imageItem: ImageItem) {
Image(
painter = painterResource(id = imageItem.drawableResId),
contentDescription = imageItem.contentDescription,
modifier = Modifier.fillMaxWidth(),
contentScale = ContentScale.FillWidth
)
}
The models I used are quite simple:
Section:
data class Section(
val title: String,
val items: List<Item>
)
Item:
interface Item
data class TextItem(
val title: String
) : Item
data class ImageItem(
val drawableResId: Int,
val contentDescription: String?
) : Item
User:
data class User(
val username: String
)
I then use this profile composable inside an activity in both apps like this:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val systemUiController = remember { SystemUiController(window) }
CompositionLocalProvider(SystemUiControllerAmbient provides systemUiController) {
SampleApp()
}
}
}
#Composable
fun SampleApp() {
SampleTheme {
val systemUiController = SystemUiControllerAmbient.current
systemUiController.setSystemBarsColor(color = MaterialTheme.colors.surface)
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = { TopAppBarContainer() },
bottomBar = { BottomNavigationContainer() }
) { innerPadding ->
ProfileContainer(Modifier.padding(innerPadding))
}
}
}
#Composable
private fun TopAppBarContainer() {
TopAppBar(
backgroundColor = MaterialTheme.colors.surface,
contentColor = MaterialTheme.colors.onSurface
) {
Text(
text = "Profile",
style = MaterialTheme.typography.h2,
modifier = Modifier.padding(16.dp)
)
}
}
#Composable
private fun ProfileContainer(modifier: Modifier = Modifier) {
Surface(modifier = modifier) {
var userIsLoggedIn by remember { mutableStateOf(false) }
Profile(
Modifier.fillMaxWidth(),
user = User("Username"),
sections = fakeSections,
userIsLoggedIn = userIsLoggedIn,
onLogin = { userIsLoggedIn = true },
onLogout = { userIsLoggedIn = false }
)
}
}
#Composable
private fun BottomNavigationContainer() {
BottomNavigation(
backgroundColor = MaterialTheme.colors.surface,
contentColor = MaterialTheme.colors.onSurface,
) {
BottomNavigationItem(
selected = false,
onClick = { },
icon = {
Icon(
painter = painterResource(R.drawable.logo),
contentDescription = null,
modifier = Modifier.size(24.dp)
)
},
label = {
Text(
text = "Home",
style = MaterialTheme.typography.caption,
color = grey60
)
}
)
BottomNavigationItem(
selected = false,
onClick = { },
icon = {
Icon(
painter = painterResource(R.drawable.ic_assortment),
contentDescription = null,
modifier = Modifier.size(24.dp)
)
},
label = {
Text(
text = "Assortment",
style = MaterialTheme.typography.caption,
color = grey60
)
}
)
BottomNavigationItem(
selected = false,
onClick = { },
icon = {
Icon(
painter = painterResource(R.drawable.ic_heart),
contentDescription = null,
modifier = Modifier.size(24.dp)
)
},
label = {
Text(
text = "Wishlist",
style = MaterialTheme.typography.caption,
color = grey60
)
}
)
BottomNavigationItem(
selected = false,
onClick = { },
icon = {
Icon(
painter = painterResource(R.drawable.ic_cart),
contentDescription = null,
modifier = Modifier.size(24.dp)
)
},
label = {
Text(
text = "Cart",
style = MaterialTheme.typography.caption,
color = grey60
)
}
)
BottomNavigationItem(
selected = true,
onClick = { },
icon = {
Icon(
painter = painterResource(R.drawable.ic_user),
contentDescription = null,
tint = bonprixRed,
modifier = Modifier.size(24.dp)
)
},
label = {
Text(
text = "Profile",
style = MaterialTheme.typography.caption,
color = red
)
}
)
}
}
#Preview
#Composable
fun DefaultPreview() {
SampleApp()
}
}

Related

Is it possible to create 5 oscillators (an array?) working simultaneously and control their frequency and amplitude independently of each other?

import SwiftUI
import AudioKit
class ToneGenerator {
let engine = AudioEngine()
let osc = PlaygroundOscillator()
init(){
engine.output = osc
try! engine.start()
}
}
struct ContentView: View {
let toneGenerator = ToneGenerator()
var freq01: Float = 200
var volume01: Float = 0.5
#State var isPressed = false
var body: some View {
Text("BEEP")
.font((.title))
.simultaneousGesture(
DragGesture(minimumDistance: 0)
.onChanged({ _ in
isPressed = true
toneGenerator.osc.frequency = freq01
toneGenerator.osc.amplitude = volume01
toneGenerator.osc.start()
})
.onEnded({ _ in
isPressed = false
toneGenerator.osc.stop()
})
)
}
}
I tried to create several generators, but did not understand how to do it.
All the materials I found on the Internet are related to the Playground and do not work in Xcode.
I'm not 100% sure what you're trying to achieve, but how about something like this. The key is to use $toneGenerators[index] to create a binding to the tone generator so you can change the volume and frequency.
class ToneGenerator: Identifiable {
let id = UUID()
let engine = AudioEngine()
var osc = PlaygroundOscillator()
init(){
engine.output = osc
try! engine.start()
}
}
struct ContentView: View {
#State var toneGenerators: [ToneGenerator] = []
let freqs = [Float(100), 200, 300, 400, 500]
var body: some View {
VStack {
HStack {
Button("BEEP") {
let toneGenerator = ToneGenerator()
toneGenerator.osc.frequency = Float.random(in: 100...1000)
toneGenerator.osc.amplitude = 0.5
toneGenerator.osc.start()
toneGenerators.append(toneGenerator)
}
.font((.title))
Button("STOP") {
toneGenerators.forEach { toneGenerator in
toneGenerator.osc.stop()
}
toneGenerators = []
}
.tint(.red)
.font((.title))
}
Text(toneGenerators.count, format: .number) + Text(" generators")
Grid {
GridRow {
Text("Freq").bold()
Text("Volume").bold()
}
ForEach(toneGenerators) { toneGenerator in
let index = toneGenerators.firstIndex(where: { $0.id == toneGenerator.id })!
GridRow {
Slider(value: $toneGenerators[index].osc.frequency, in: 100...1000)
Slider(value: $toneGenerators[index].osc.amplitude, in: 0...1)
}
}
}
.padding()
Spacer()
}
}
}

How to sort List ForEach Swiftui

Hi there
I'm newbie for SwiftUI, and I want to sort the "expireDate" , then use forEach to display the view according to the expireDate, how to???
sorry for my messy code, coding is really not easy.
will be much appreciate if someone can help
Here is the data
import Foundation
struct CardData: Identifiable, Codable {
let id: UUID
var cardName: String
var cardNumber: String
var expireDate: Date
var theme: Theme
var history: [History] = []
init(id: UUID = UUID(), cardName: String, cardNumber: String, expireDate: Date, theme: Theme) {
self.id = id
self.cardName = cardName
self.cardNumber = cardNumber
self.expireDate = expireDate
self.theme = theme
}
}
extension CardData {
struct Data {
var cardName: String = ""
var cardNumber: String = ""
var expireDate: Date = Date.now
var theme: Theme = .orange
}
var data: Data {
Data(cardName: cardName, cardNumber: cardNumber, expireDate: expireDate, theme: theme)
}
mutating func update(from data: Data) {
cardName = data.cardName
cardNumber = data.cardNumber
expireDate = data.expireDate
theme = data.theme
}
init(data: Data) {
cardName = data.cardName
cardNumber = data.cardNumber
expireDate = data.expireDate
theme = data.theme
id = UUID()
}
}
And here is the view
import SwiftUI
struct CardView: View {
#Binding var datas: [CardData]
#Environment(\.scenePhase) private var scenePhase
#State private var isPresentingNewCardView = false
#State private var newCardData = CardData.Data()
let saveAction: () -> Void
#EnvironmentObject var launchScreenManager: LaunchScreenManager
#State private var confirmationShow = false
var body: some View {
List {
ForEach($datas) { $data in
NavigationLink(destination: DetailView(cardData: $data)){
CardDataView(cardData: data)
}
.listRowBackground(data.theme.mainColor)
}
.onDelete(perform: deleteItems)
}
.navigationTitle("Expiry Date")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
Button(action: {
isPresentingNewCardView = true
}) {
Image(systemName: "plus")
}
.accessibilityLabel("New data")
}
.sheet(isPresented: $isPresentingNewCardView) {
NavigationView {
DetailEditView(data: $newCardData)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Dismiss") {
isPresentingNewCardView = false
newCardData = CardData.Data()
}
}
ToolbarItem(placement: .confirmationAction) {
Button("Add") {
let newData = CardData(data: newCardData)
datas.append(newData)
isPresentingNewCardView = false
newCardData = CardData.Data()
}
}
}
}
}
.onChange(of: scenePhase) { phase in
if phase == .inactive { saveAction() }
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
launchScreenManager.dismiss()
}
}
}
func deleteItems(at offsets: IndexSet) {
datas.remove(atOffsets: offsets)
}
}
Hi there
I'm newbie for SwiftUI, and I want to sort the "expireDate" , then use forEach to display the view according to the expireDate, how to???
sorry for my messy code, coding is really not easy.
will be much appreciate if someone can help
You can sort the datas in place, before you use it in the ForEach,
when you create the datas for example. Like this:
datas.sort(by: { $0.expireDate > $1.expireDate}).
Or
you can sort the datas just in the ForEach,
like this, since you have bindings,
ForEach($datas.sorted(by: { $0.expireDate.wrappedValue > $1.expireDate.wrappedValue})) { $data ...}
Note with this ForEach($datas.sorted(by: ...), when you do your func deleteItems(at offsets: IndexSet),
you will have to get the index in the sorted array, and delete the equivalent in the original one.
EDIT-1:
updated func deleteItems:
func deleteItems(at offsets: IndexSet) {
let sortedArr = datas.sorted(by: { $0.expireDate > $1.expireDate})
for ndx in offsets {
if let cardIndex = datas.firstIndex(where: { $0.id == sortedArr[ndx].id }) {
datas.remove(at: cardIndex)
}
}
}
Note you may want to put let sortedArr = datas.sorted(by: { $0.expireDate > $1.expireDate}) somewhere else (eg. .onAppear) instead of evaluating this, every time you use deleteItems

Use of DropdownMenu with Jetpack Compose (Material)

I decided to use an OutlinedTextField and DropdownMenu so the user can fill an amount and select a currency.
This looks pretty nice in the preview, but when this code is being run on the device (virtual or physical) the DropdownMenu is being squeezed on the right, and therefore, the dropdown menu isn't actionable anymore.
#Composable
fun Money() {
Row() {
Amount()
Currency()
}
}
#Preview
#Composable
fun Currency() {
var mExpanded by remember { mutableStateOf(false) }
val mCurrencies = listOf("USD", "CHF", "EUR", "MAD") //, "Hyderabad", "Bengaluru", "PUNE")
var mSelectedText by remember { mutableStateOf("") }
var mTextFieldSize by remember { mutableStateOf(Size.Zero) }
val icon = if (mExpanded)
Icons.Filled.KeyboardArrowUp
else
Icons.Filled.KeyboardArrowDown
OutlinedTextField(
value = mSelectedText,
onValueChange = { mSelectedText = it },
modifier = Modifier
.fillMaxWidth()
.onGloballyPositioned { coordinates ->
// This value is used to assign to
// the DropDown the same width
mTextFieldSize = coordinates.size.toSize()
},
label = { Text("Currency") },
trailingIcon = {
Icon(icon, "contentDescription",
Modifier.clickable { mExpanded = !mExpanded })
}
)
DropdownMenu(
expanded = mExpanded,
onDismissRequest = { mExpanded = false },
modifier = Modifier
.width(with(LocalDensity.current) { mTextFieldSize.width.toDp() })
) {
mCurrencies.forEach { label ->
DropdownMenuItem(onClick = {
mSelectedText = label
mExpanded = false
}) {
Text(text = label)
}
}
}
}
#Composable
fun Amount() {
var amount by remember {
mutableStateOf("")
}
OutlinedTextField(
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
value = amount,
onValueChange = { amount = it },
label = { Text(text = "Amount") },
singleLine = true,
trailingIcon = {
if (amount.isNotBlank())
IconButton(onClick = { amount = "" }) {
Icon(
imageVector = Icons.Filled.Clear,
contentDescription = ""
)
}
}
)
}```

Alphabetical Scrollbar in jetpack compose

Does anyone know how to create an alphabetical fast scroller, displaying all the letters in Jetpack Compose?
Similar to this one:
Recyclerview Alphabetical Scrollbar
I have made a list which is scrollable, but I have no clue in how to make the letters on the side and make it "jump" to the right letter.
here is my coding so far:
package com.example.ValpeHagen.ui.theme
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.ValpeHagen.model.DataProvider
import com.example.ValpeHagen.model.DataProvider2
import com.example.androiddevchallenge.data.model.Rase
class Valpen {
val rase = DataProvider2.rase
#Composable
fun VerticalHorizontalScroll(rase: Rase) {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color.White)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.height(60.dp)
.background(Grass),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = "Velg Rase",
color = Color.White,
fontSize = 30.sp,
fontWeight = FontWeight.Bold,)
}
LazyColumn {
item {
Text(
text = "Hurtigvalg",
color = Color.Black,
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(10.dp)
)
}
//Horizontal Scroll view
item {
LazyRow {
itemsIndexed(items = DataProvider.puppyList) { index, itemPuppy ->
Card(
modifier = Modifier
.width(110.dp)
.height(140.dp)
.padding(10.dp, 5.dp, 5.dp, 0.dp)
.clip(RoundedCornerShape(10.dp))
.background(Color.White),
elevation = 5.dp
) {
Column(
modifier = Modifier.padding(5.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Image(
painter = painterResource(id = DataProvider.puppy.puppyImageId),
contentDescription = "profile Image",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(60.dp)
.clip(CircleShape)
)
Spacer(modifier = Modifier.padding(5.dp))
Text(
text = DataProvider.puppy.breeds,
color = Color.Black,
fontWeight = FontWeight.Bold,
fontSize = 16.sp)
}
}
}
}
}
val mylist = listOf("Affenpinscher", "Wheaten terrier" , "dachshund",
"Fransk bulldog","Affenpinscher", "Wheaten terrier" , "dachshund",
"Fransk bulldog","Affenpinscher", "Wheaten terrier" , "dachshund",
"Fransk bulldog","Affenpinscher", "Wheaten terrier" , "dachshund",
"Fransk bulldog","Affenpinscher", "Wheaten terrier" , "dachshund",
"Fransk bulldog","Affenpinscher", "Wheaten terrier" , "dachshund",
"Fransk bulldog","Affenpinscher", "Wheaten terrier" , "dachshund",
"Fransk bulldog")
item {
Text(
text = "Alle hunderaser",
color = Color.Black,
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(vertical = 10.dp, horizontal = 10.dp)
.clip(RoundedCornerShape(10.dp))
.background(Color.White))
}
item {
val list = listOf("A", "B", "C", "D",
"E","F","G","H","I","J","K","L","M","N",
"O","P","Q","R","S","T","U","V","W","X", "Y","Z","Æ","Ø","Å")
val expanded = remember { mutableStateOf(false) }
val currentValue = remember { mutableStateOf(list[0]) }
Surface(modifier = Modifier.fillMaxSize()) {
Box(modifier = Modifier.fillMaxWidth()) {
Row(modifier = Modifier
.clickable {
expanded.value = !expanded.value
}
.align(Alignment.CenterStart)) {
Text(text = currentValue.value,
color = Grass,
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(horizontal = 14.dp)
)
Icon(imageVector = Icons.Filled.ArrowDropDown, contentDescription = null)
DropdownMenu(expanded = expanded.value, onDismissRequest = {
expanded.value = false
}) {
list.forEach {
DropdownMenuItem(onClick = {
currentValue.value = it
expanded.value = false
}) {
Text(text = it,
color = Grass,
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(vertical = 10.dp,
horizontal = 10.dp))
}
}
}
}
}
}
}
}
}
}
}
To display letters all you need is a Column with needed items. To scroll a lazy column to a needed item, you can use lazy column state.
Here's a basic example:
val items = remember { LoremIpsum().values.first().split(" ").sortedBy { it.lowercase() } }
val headers = remember { items.map { it.first().uppercase() }.toSet().toList() }
Row {
val listState = rememberLazyListState()
LazyColumn(
state = listState,
modifier = Modifier.weight(1f)
) {
items(items) {
Text(it)
}
}
val offsets = remember { mutableStateMapOf<Int, Float>() }
var selectedHeaderIndex by remember { mutableStateOf(0) }
val scope = rememberCoroutineScope()
fun updateSelectedIndexIfNeeded(offset: Float) {
val index = offsets
.mapValues { abs(it.value - offset) }
.entries
.minByOrNull { it.value }
?.key ?: return
if (selectedHeaderIndex == index) return
selectedHeaderIndex = index
val selectedItemIndex = items.indexOfFirst { it.first().uppercase() == headers[selectedHeaderIndex] }
scope.launch {
listState.scrollToItem(selectedItemIndex)
}
}
Column(
verticalArrangement = Arrangement.SpaceEvenly,
modifier = Modifier
.fillMaxHeight()
.background(Color.Gray)
.pointerInput(Unit) {
detectTapGestures {
updateSelectedIndexIfNeeded(it.y)
}
}
.pointerInput(Unit) {
detectVerticalDragGestures { change, _ ->
updateSelectedIndexIfNeeded(change.position.y)
}
}
) {
headers.forEachIndexed { i, header ->
Text(
header,
modifier = Modifier.onGloballyPositioned {
offsets[i] = it.boundsInParent().center.y
}
)
}
}
}

How to trigger two different custom auto completer using (.) period and Ctrl_space in ace editor

I have two completer. one for Math custom functions and another for string custom functions. i want to trigger string functions using (.)period and for ctrl_space for math function.
Now if i press (.) or ctrl_space, it showing all the functions. but i want differentiate between two of them.Thanks..
var stringFunctions = ["Trim","Length","ToLower","ToUpper","ToNumber","ToString"]
var mathFunctions = ["Abs","Ceil","Exp","Floor","Log","ln","Pow","Round","Sqrt","cos","sin","tan","cosh","sinh","tanh","acos","asin","atan","Max","Min","Sum","Std","Var","Average","Norm","Median"]
var myCompleter1 = {
// identifierRegexps: [/[a-zA-Z_0-9\.\$\-\u00A2-\uFFFF]/],
getCompletions: function(editor, session, pos, prefix, callback) {
console.info("myCompleter prefix:", this.prefix);
callback(
null,
myList.filter(entry=>{
return entry.includes(this.prefix);
}).map(entry=>{
return {
value: entry
};
})
);
}
}
var myCompleter2 = {
// identifierRegexps: [/[a-zA-Z_0-9\.\$\-\u00A2-\uFFFF]/],
getCompletions: function(editor, session, pos, prefix, callback) {
console.info("myCompleter prefix:", this.prefix);
callback(
null,
myList.filter(entry=>{
return entry.includes(this.prefix);
}).map(entry=>{
return {
value: entry
};
})
);
}
}
langTools.addCompleter(myCompleter1);
langTools.addCompleter(myCompleter2);```
You can check the text of the line in the completer to decide if it is after dot or not
var stringFunctions = ["Trim", "Length", "ToLower", "ToUpper", "ToNumber", "ToString"]
var mathFunctions = ["Abs", "Ceil", "Exp", "Floor", "Log", "ln", "Pow", "Round",
"Sqrt", "cos", "sin", "tan", "cosh", "sinh", "tanh", "acos", "asin", "atan",
"Max", "Min", "Sum", "Std", "Var", "Average", "Norm", "Median"
]
var myCompleter1 = {
getCompletions: function(editor, session, pos, prefix, callback) {
var line = session.getLine(pos.row)
var lineStart = line.slice(0, pos.column - prefix.length)
var myList = !/\.\s*$/.test(lineStart) ? mathFunctions : stringFunctions;
callback(null, myList.map(entry => {
return {
value: entry,
score: 1000
};
}));
}
}
var langTools = ace.require("ace/ext/language_tools")
langTools.addCompleter(myCompleter1);
var editor = ace.edit("editor", {
maxLines: 20,
minLines: 5,
enableBasicAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: false,
})
// automatically open popup after dot or word characters
var doLiveAutocomplete = function(e) {
var editor = e.editor;
var hasCompleter = editor.completer && editor.completer.activated;
if (e.command.name === "insertstring") {
// Only autocomplete if there's a prefix that can be matched
if (/[\w.]/.test(e.args)) {
editor.execCommand("startAutocomplete")
}
}
};
editor.commands.on('afterExec', doLiveAutocomplete);
<script src=https://ajaxorg.github.io/ace-builds/src-noconflict/ace.js></script>
<script src=https://ajaxorg.github.io/ace-builds/src-noconflict/ext-language_tools.js></script>
<div id=editor></div>

Resources