Add element to XAML page whiles in Task.Delay() - asynchronous

I'm building a chatbot app with chat bubbles for incoming and outgoing messages. For the incoming messages, I've given it a Task.Delay() and now I'd like to give it an ActivityIndicator every time before the message pops up (i.e. I want to show the activity indicator whiles the message is being delayed). I've added the activity indicator to the XAML of the incoming messages control;
IncomingMessageItemControl
<ViewCell
x:Class="BluePillApp.Controls.IncomingMessageItemControl"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:pancake="clr-namespace:Xamarin.Forms.PancakeView;assembly=Xamarin.Forms.PancakeView"
mc:Ignorable="d">
<Grid x:Name="Gridoo">
<pancake:PancakeView
Margin="10,10,80,10"
Padding="15"
BackgroundColor="#53ffc6"
CornerRadius="20,20,0,20"
HasShadow="False"
HorizontalOptions="StartAndExpand">
<Label
FontSize="Medium"
Text="{Binding Text}"
TextColor="#1a1a1a" />
</pancake:PancakeView>
<ActivityIndicator IsRunning="True" IsVisible="True" />
</Grid>
</ViewCell>
The problem is, in the ChatbotMessagingPage, the send button is pressed then an outgoing message is sent before getting a reply/incoming message and I've done this in MVVM like so;
ChatbotMessagingPageViewModel
//This gets the chatbots response for each message
chatbot.MainUser.ResponseReceived += async (sender, args) =>
{
await Task.Delay(1500);
Messages.Add(new ChatMessageModel() { Text = args.Response.Text, User = App.ChatBot });
};
}
#region CLASS METHODS
/// <summary>
/// This function sends a message
/// </summary>
public void Send()
{
if (!string.IsNullOrEmpty(TextToSend))
{
var msgModel = new ChatMessageModel() { Text = TextToSend, User = App.User };
//This adds a new message to the messages collection
Messages.Add(msgModel);
var result = chatbot.Evaluate(TextToSend);
result.Invoke();
//Removes the text in the Entry after message is sent
TextToSend = string.Empty;
}
}
Everytime I press the send button the ActivityIndicator comes along with the IncomingMessage, I'd like the ActivityIndicator to come first, whiles the IncomingMessage is being delayed.

I'm guessing that that view cell is the message bubble.
When you do:
Messages.Add(new ChatMessageModel() { Text = args.Response.Text, User = App.ChatBot });
Your collection is updated and your ListView or whatever hold those ViewCelss is also updated. The ActivityIndicator is part of the ViewCell so it comes at the same time as the message.
[OPTION 1] Using an additional flag
What you can do is create a flag IsBusy or IsDelay or something and bind the visibility of the ActivityIndicator and Label to it:
<Grid x:Name="Gridoo">
<pancake:PancakeView
Margin="10,10,80,10"
Padding="15"
BackgroundColor="#53ffc6"
CornerRadius="20,20,0,20"
HasShadow="False"
HorizontalOptions="StartAndExpand">
<Label
FontSize="Medium"
Text="{Binding Text}"
TextColor="#1a1a1a"
IsVisible="{Binding IsBusy, Converter={Helpers:InverseBoolConverter}}""> />
</pancake:PancakeView>
<ActivityIndicator x:Name="activityIndicator" IsRunning="True" IsVisible="{Binding IsBusy}" />
</Grid>
Note that I've used a IValueConverter to negate the value for the label. In case you're not familiar with it, check this
What's left is to add the flag in your ViewModel:
IsBusy = true; // this will make the activity indicator visible, but not the Label
// Also note that you first need to add the message
Messages.Add(new ChatMessageModel() { Text = args.Response.Text, User = App.ChatBot });
await Task.Delay(1500);
IsBusy = false; // this will hige the activity indicator visible, and make Label visible
So basically the logic is the following:
You add the message to your chat BUT the actual text is hidden when on the other hand, the activity indicator is visible.
You apply the delay
Delay ends, you change the visibility of both views.
Note that in my example I've not declared where that flag is since I'm not sure how the rest of your code looks like. It could be added to ChatMessageModel or ChatMessageViewModel since you would need a flag for each message.
[OPTION 2] in IncomingMessageItemControl.xaml.cs
A better solution could be to handle it in the code behind of your control. The issue is the same, the activity indicator and the label comes at the same time.
To fix this you can move the delay in IncomingMessageItemControl.xaml.cs.
First, you need to add x:Name to both the activity indicator and the label.
Then you could do:
private async Task ChangeVisibilityAsync()
{
Label.IsVisibe= false;
ActivityIndicator.IsVisible = true;
await Task.Delay(1500);
Label.IsVisibe = true;
ActivityIndicator.IsVisible = false;
}

Related

How to display two text boxes as pop up using DisplayPromptAsync method in xamarin?

When I use -
string result = await DisplayPromptAsync("Question 1", "What's your name?");
It shows only one textbox in the pop-up. But how to display two or more textboxes in the pop-up?
Any help would be greatly appreciated.
As IvanIčin said that you can use Rg.Plugins.Popup to create custom popup.
Firstly, install Rg.Plugins.Popup bu nuget package..., then creating popup page
<pages:PopupPage
x:Class="FormsSample.popup.popup2"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:Rg.Plugins.Popup.Pages;assembly=Rg.Plugins.Popup">
<pages:PopupPage.Content>
<StackLayout
Padding="20,0"
BackgroundColor="CadetBlue"
HorizontalOptions="FillAndExpand"
VerticalOptions="Center">
<Label Text="Question 1" />
<Label Text="this is one question!" />
<Entry />
<Entry />
<Button
x:Name="btnsub"
Clicked="btnsub_Clicked"
Text="subit" />
</StackLayout>
</pages:PopupPage.Content>
</pages:PopupPage>
public partial class popup2 : PopupPage
{
public popup2()
{
InitializeComponent();
}
private void btnsub_Clicked(object sender, EventArgs e)
{
}
}
To call this Popup Page from contentpage button.click event.
private async void btnPopupButton_Clicked(object sender, EventArgs e)
{
await PopupNavigation.Instance.PushAsync(new popup2());
}
You can see the screenshot:
You can't as it is not intended to. You can create a custom pop-up either by using some pop-up plug-in or by creating your custom code based on the native prompts (similar to what Xamarin.Forms do).
Just for the record having one input field is very generous from Xamarin as the native Android or iOS developers don't have such a prompt with the input field out of the box (though it isn't too hard to create it but still it goes much beyond one line of code).

Xamarin.Forms: Bind SwipeView to code and get events

I am quite new to xamarin.forms.
So I set up a swipeview (https://github.com/markolazic88/SwipeCardView/blob/master/docs/index.md)
But I can't seem to understand what they mean by "glueing" xaml with C#' together.
In the docs it says there are eventhandlers like this one:
void OnSwiped(object sender, SwipedCardEventArgs e)
{
switch (e.Direction)
{
case SwipeCardDirection.None:
break;
case SwipeCardDirection.Right:
break;
case SwipeCardDirection.Left:
break;
case SwipeCardDirection.Up:
break;
case SwipeCardDirection.Down:
break;
}
}
But just adding this function into the connect class doesn't work. Nothing fires. I was however able to access the swipeview like this:
public Screen_SwipeView()
{
InitializeComponent();
List<string> data = new List<string>() { "a", "b", "c" };
swipeview_swipeview.ItemsSource = data;
}
But this method isn't mentions in the docs.
How can I get the eventhandlers to fire. I feel like I am missing the bridge between the xaml component and the code.
I also tried making a class and letting it inherit from this components but that only threw errors.
You can also define the SwipeView properties (LeftItems, RightItems, TopItems, BottomItems) in your Xaml and then bind their command properties to a Command object define in your viewmodel. Example :
<SwipeView>
<SwipeView.RightItems>
<SwipeItems>
<SwipeItem Text="Annuler"
IconImageSource="deleteitem.png"
BackgroundColor="LightPink"
Command="{Binding Source={x:Reference yourViewName}, Path=BindingContext.DeleteCommand}"
CommandParameter="{Binding .}" />
</SwipeItems>
</SwipeView.RightItems>
</SwipeView>
Easy one:
swipeview_swipeview.Dragging += OnDragging;
swipeview_swipeview.Swiped += OnSwiped;

Equivalent of ItemAppearing in xamarin UWP

I have a ListView bind to list of BitmapImage.
I want to get the Index of current image in focus when I scroll thru this list.
But, I notice that ItemAppearing property is not there in UWP but it is there in Xamarin Forms.
How can I get the index of the current item in view?
Thanks!
<ScrollViewer Grid.Row="0" ZoomMode="{x:Bind ZoomMode, Mode=OneWay}" HorizontalScrollMode="Enabled" HorizontalScrollBarVisibility="Auto">
<ListView HorizontalAlignment="Center" ItemsSource="{x:Bind ImagePages, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="BitmapImage">
<Image Source="{x:Bind }" Margin="0 2" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListView>
</ScrollViewer>
For starters, the ItemAppearing property is not the behavior you are looking for. The ItemAppearing event for the ListView in Xamarin Forms is fired when the list item is rendered. For a small list this event will be fired for all items immediately. The equivalent event in UWP is ListView.ChoosingItemContainer event which like the ItemAppearing event, unless the ListView is virtualized is fired for all items in the list. Even for a large virtualized list, it is fired for several pages of items.
This is not what you want. As I understand it, you want to know the image that is visible at the top of the list view when the list is scrolled. Here is how to do that.
First of all. Get rid of the ScrollViewer. The ListView already has a ScrollViewer inside of it.
<ListView x:Name="listViewImage" Grid.Row="0" HorizontalAlignment="Center" ItemsSource="{x:Bind ImagePages, Mode=OneWay}"
Loaded="listViewImage_Loaded">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="BitmapImage">
<Image Source="{x:Bind }" Margin="0 2"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListView>
Note that I have named the ListView and I have added a Loaded event handler. In this handler, find the ScrollViewer inside the ListView and attach a handler to the ViewChanged event.
private void listViewImage_Loaded(object sender, RoutedEventArgs e)
{
Border b = VisualTreeHelper.GetChild(listViewImage, 0) as Border;
ScrollViewer sv = b.Child as ScrollViewer;
sv.ViewChanged += Sv_ViewChanged;
}
In the view changed handler, find the first visible ListViewItem and get its index in the collection. This is what you want.
private void Sv_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
ScrollViewer sv = sender as ScrollViewer;
GeneralTransform gt = sv.TransformToVisual(this);
Point p = gt.TransformPoint(new Point(0, 0));
List<UIElement> list = new List<UIElement>(VisualTreeHelper.FindElementsInHostCoordinates(p, sv));
ListViewItem item = list.OfType<ListViewItem>().FirstOrDefault();
if(item != null)
{
int index = listViewImage.IndexFromContainer(item);
Debug.WriteLine("Visible item at top of list is " + index);
}
}

How to detect list view scrolling direction in xamarin forms

I have tried to detect the scrolling direction of the list view. My requirement is need to implement different functionality while list view scrolling up and scrolling down. Please suggest any idea for detecting list view scrolling direction. I have tried below syntax in my list view.
Sample code:
<StackLayout>
<Label x:Name="Direction" />
<ListView ItemsSource="{Binding Items}" HasUnevenRows = "true" ItemAppearing="Handle_ItemAppearing" IsPullToRefreshEnabled = "true">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text = "{Binding}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
I don't think you can do it by default, you can only act on a item that is appearing or disappearing. So, you either need to work with that by creating some code which gets the index of (dis)appearing items and and see if the indexes are getting higher or lower to determine whether someone is scrolling up or down. Or you need to hook up a custom renderer, but I'm not sure the native controls have anything to detect this either.
I've whipped up a very basic example for you, you can find the full code here.
Basically hook into the event available, keep track of the last index in a class variable and compare it to the current index of the item that is appearing.
private void Handle_ItemAppearing (object sender, Xamarin.Forms.ItemVisibilityEventArgs e)
{
var currentIdx = Items.IndexOf ((string)e.Item);
if (currentIdx > _lastItemAppearedIdx)
Direction.Text = "Up";
else
Direction.Text = "Down";
_lastItemAppearedIdx = Items.IndexOf ((string)e.Item);
}
In this code I simply show it in a Label, but of course you can create some enum to return or fire an event or something to make the code some more reusable. Here is the code in action:
Recently came through this problem and fixed it this way:
<StackLayout>
<Label x:Name="Direction" />
<ListView ItemsSource="{Binding Items}" HasUnevenRows = "true" ItemAppearing="Handle_ItemAppearing" ItemDisappearing="Handle_ItemDisappearing" IsPullToRefreshEnabled = "true">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text = "{Binding}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
string ScrollingDirection;
int visibleTabIndex;
int disappearingTabIndex;
public async void Handle_ItemAppearing(object sender, ItemVisibilityEventArgs e)
{
var visibleTab = e.Item;
visibleTabIndex = MyItemsList.IndexOf(visibleTab);
if (disappearingTabIndex > visibleTabIndex) ScrollingDirection = "DOWN";
else ScrollingDirection = "UP";
}
public async void Handle_ItemDisappearing(object sender, ItemVisibilityEventArgs e)
{
var invisibleTab = e.Item as TicketsList;
disappearingTabIndex = tvm.Tickets.IndexOf(invisibleTab);
}

How to intercept WebView Navigating event in ViewModel

My app has a WebView for displaying some contact information. It has a link to a website that I want to load externally using Device.OpenUri(). I'm using FreshMvvm and I want to intercept the Navigating event from the WebView in the ViewModel and cancel the default action which would load the external page into the WebView.
I've tried using the Corcav.Behaviors plugin which does call my ViewModel command:
<WebView
HorizontalOptions="Fill"
VerticalOptions="FillAndExpand"
Source="{Binding WebViewSource}">
<b:Interaction.Behaviors>
<b:BehaviorCollection>
<b:EventToCommand
EventName="Navigating"
Command="{Binding NavigatingCommand}"
CommandParameter="{Binding}"/> <!-- what goes here -->
</b:BehaviorCollection>
</b:Interaction.Behaviors>
</WebView>
But I'm not sure what the CommandParameter should be - I need the URI of the link that was tapped, and I don't know how to then prevent the default behaviour from occurring.
Is this the best approach or should I be looking at an alternative?
Having revisited this recently for another project I stumbled across the answer. The updated XAML is:
<WebView
x:Name="webView"
HorizontalOptions="Fill"
VerticalOptions="FillAndExpand"
Source="{Binding WebViewSource}">
<behaviors:Interaction.Behaviors>
<behaviors:BehaviorCollection>
<behaviors:EventToCommand
EventName="Navigating"
Command="{Binding NavigatingCommand}"
PassEventArgument="True" />
</behaviors:BehaviorCollection>
</behaviors:Interaction.Behaviors>
</WebView>
The code in the ViewModel, that matches the tapped url against a list of valid options before opening the link in the device's browser, is:
public Command<WebNavigatingEventArgs> NavigatingCommand
{
get
{
return navigatingCommand ?? (navigatingCommand = new Command<WebNavigatingEventArgs>(
(param) =>
{
if (param != null && -1 < Array.IndexOf(_uris, param.Url))
{
Device.OpenUri(new Uri(param.Url));
param.Cancel = true;
}
},
(param) => true
));
}
}
You can´t navigate with a WebView, you must use a custom render (hybridwebview).
Here is an explanation:
https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/custom-renderer/hybridwebview/

Resources