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 = ""
)
}
}
)
}```
Related
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
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()
}
}
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>
I have initiated 2 localstorage variables within a typescript function. The second variable holding the value overwrites the first variable. How do I fix it?
OnSelectedOppPropStatsChange(TypeId: string, StrSelectedValue: string): void {
this.appService.SetLoadingShow(true);
var url = this.configs.DashboardDemandStatsURL();
var Input = {
"TypeId": TypeId,
"PS_No": this.user.PS_No,
"StrSelectedValue": StrSelectedValue
};
localStorage.setItem(this.strTypeSelected, StrSelectedValue);
localStorage.setItem(this.strTypeIdValue, TypeId);
this.appService.GetDataFromAPIPost(url, Input)
.then(response => {
this.appService.SetLoadingShow(false);
if (response.ResponseCode == this.configs.RetCodeFailure()) {
this.ShowDemandDetails = false;
this.errorMessage = response.ResponseData;
this.appService.ShowMessagePopup(this.configs.MESSAGETYPEERROR(), this.errorMessage);
}
else {
this.OpenDemandStatsDetails = JSON.parse(response.ResponseData.strDemandStatsOpen);
this.TeamFulfilledStatsDetails = JSON.parse(response.ResponseData.strDemandStatsTeam);
this.RPMFulfilledStatsDetails = JSON.parse(response.ResponseData.strDemandStatsRPM);
this.TotalRRCount = this.OpenDemandStatsDetails.length + this.TeamFulfilledStatsDetails.length + this.RPMFulfilledStatsDetails.length;
}
},
error => { this.errorMessage = <string>error; this.appService.SetLoadingShow(false) });
}
OnClickOpenRRNavigate(): void {
let SelectedItem = localStorage.getItem(this.strTypeSelected);
let SelectedType = localStorage.getItem(this.strTypeIdValue);
this.appService.SetLoadingShow(true);
var url = this.configs.DashboardDemandStatsTableURL();
var Input = {
"TypeId": SelectedType,
"PS_No": this.user.PS_No,
"StrSelectedValue": SelectedItem,
"strRRAllocationValue": this.StrOpenValue
};
this.appService.GetDataFromAPIPost(url, Input)
.then(response => {
this.appService.SetLoadingShow(false);
if (response.ResponseCode == this.configs.RetCodeFailure()) {
this.errorMessage = response.ResponseData;
this.appService.ShowMessagePopup(this.configs.MESSAGETYPEERROR(), this.errorMessage);
}
else {
this.DemandTableDetails = JSON.parse(response.ResponseData.strDemandStatsTable);
this.ShowDemandTable = true;
}
},
error => { this.errorMessage = <string>error; this.appService.SetLoadingShow(false) });
}
In the function OnClickOpenRRNavigate() , the SelectedType and SelectedItem holds the same value. How can I fix it?
I have am trying to implement something along the lines of
Slickgrid, column with a drop down select list?
my code is;
slick.editors.js ;
(function ($) {
// register namespace
$.extend(true, window, {
"Slick": {
"Editors": {
"Text": TextEditor,
"Integer": IntegerEditor,
"Date": DateEditor,
"YesNoSelect": YesNoSelectEditor,
"Checkbox": CheckboxEditor,
"PercentComplete": PercentCompleteEditor,
"LongText": LongTextEditor,
"SelectOption": SelectCellEditor
}
}
});
with the function defined futher down,
function SelectCellEditor(args) {
var $select;
var defaultValue;
var scope = this;
this.init = function () {
if (args.column.options) {
opt_values = args.column.options.split(',');
} else {
opt_values = "yes,no".split(',');
}
option_str = ""
for (i in opt_values) {
v = opt_values[i];
option_str += "<OPTION value='" + v + "'>" + v + "</OPTION>";
}
$select = $("<SELECT tabIndex='0' class='editor-select'>" + option_str + "</SELECT>");
$select.appendTo(args.container);
$select.focus();
};
this.destroy = function () {
$select.remove();
};
this.focus = function () {
$select.focus();
};
this.loadValue = function (item) {
defaultValue = item[args.column.field];
$select.val(defaultValue);
};
this.serializeValue = function () {
if (args.column.options) {
return $select.val();
} else {
return ($select.val() == "yes");
}
};
this.applyValue = function (item, state) {
item[args.column.field] = state;
};
this.isValueChanged = function () {
return ($select.val() != defaultValue);
};
this.validate = function () {
return {
valid: true,
msg: null
};
};
this.init();
}
Then in my CSHTML
var columns = [
{ id: "color", name: "Color", field: "color", options: "Red,Green,Blue,Black,White", editor: Slick.Editors.SelectOption },
{ id: "lock", name: "Lock", field: "lock", options: "Locked,Unlocked", editor: Slick.Editors.SelectOption },
];
var options = {
enableCellNavigation: true,
enableColumnReorder: false
};
$(function () {
var data = [];
for (var i = 0; i < 20; i++) {
data[i] = {
color: "Red",
lock: "Locked"
};
}
the grid shows and the colour is shown as if its a regular text in a cell, but no dropdown?.
The drop-down will appear only when you are editing that cell. Adding editable: true to your grid options should work I think.