I have 4 pages, that they almost do the same thing.
Schedule test
schedule lecture
new test
new lecture
My NewLecturePage looks like this.
<ContentPage.Behaviors>
<mct:EventToCommandBehavior
Command="{Binding AppearingCommand}"
EventName="Appearing" />
</ContentPage.Behaviors>
<Grid
Margin="10"
RowDefinitions="Auto,80,*">
<Label
FontAttributes="Bold"
FontSize="Header"
HorizontalTextAlignment="Center"
Text="Create a new lecture" />
<inputLayout:SfTextInputLayout
Grid.Row="1"
Hint="Name of the leture"
HorizontalOptions="StartAndExpand"
OutlineCornerRadius="8">
<Entry Text="{Binding RoomName}" />
</inputLayout:SfTextInputLayout>
<expander:SfExpander
Grid.Row="2"
HorizontalOptions="Start">
<expander:SfExpander.Header>
<Grid
ColumnDefinitions="50,60"
ColumnSpacing="10"
RowDefinitions="60">
<Label
FontFamily="Mat"
FontSize="Header"
HorizontalTextAlignment="Center"
Text="{Static helpers:IconFont.Group}"
VerticalTextAlignment="Center" />
<Label
Grid.Column="1"
Text="Students"
VerticalTextAlignment="Center" />
</Grid>
</expander:SfExpander.Header>
<expander:SfExpander.Content>
<CollectionView
ItemsSource="{Binding Users}"
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:User">
<Border Margin="2">
<Border.StrokeShape>
<RoundRectangle CornerRadius="8" />
</Border.StrokeShape>
<Grid
Margin="5,5,5,0"
ColumnDefinitions="40,*"
RowDefinitions="*,*">
<Label
Grid.Column="1"
Text="{Binding Name}" />
<Label
Grid.Row="3"
Grid.Column="1"
Text="{Binding Email}" />
<chechbox:SfCheckBox
Grid.RowSpan="3"
HorizontalOptions="End"
IsChecked="{Binding IsParticipant}"
VerticalOptions="End">
<chechbox:SfCheckBox.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Source={RelativeSource AncestorType={Type vm:NewLecturePageViewModel}}, Path=HandleCheckBoxCommand}"
CommandParameter="{Binding .}" />
</chechbox:SfCheckBox.GestureRecognizers>
</chechbox:SfCheckBox>
</Grid>
</Border>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</expander:SfExpander.Content>
</expander:SfExpander>
<Button
Grid.Row="3"
Command="{Binding StartMeetingCommand}"
Text="Start meeting"
VerticalOptions="EndAndExpand" />
</Grid>
And my view Model looks like this.
public partial class NewLecturePageViewModel(IAppService appService, IDataService<User> dataService, IMeetingService meetingService,
IAuthenticationService authenticationService) : BaseViewModel {
public ObservableCollection<User> Users { get; set; } = [];
public ObservableCollection<User> Invited { get; set; } = [];
[ObservableProperty]
string? roomName;
[RelayCommand]
async Task Appearing() {
await GetStudents();
}
private async Task GetStudents() {
Users.Clear();
var data = await dataService.GetByRole<User>("Users", Roles.Student.ToString());
foreach(var filterUser in data) {
Users.Add(filterUser);
}
}
[RelayCommand]
void HandleCheckBox(User user) {
if(user.IsParticipant) {
Invited.Add(user);
} else {
Invited.Remove(user);
}
}
[RelayCommand]
async Task StartMeeting() {
if(string.IsNullOrEmpty(RoomName)) {
await appService.DisplayAlert("Error", "You cannot create a Lecture without a name", "OK");
return;
}
var loogedUser = await authenticationService.GetLoggedInUser();
var databaseUer = await dataService.GetByUidAsync<User>("Users", loogedUser!.Uid);
var meetingOptions = new MeetingOptions {
EnableAdvancedChat = true,
EnableEmojiReactions = true,
EnableNoiseCancellationUi = true,
EnableHandRaising = true,
EnablePrejoinUi = true,
EnablePipUi = true,
EnableScreenshare = true,
EnableVideoProcessingUi = true,
EnablePeopleUi = false,
EnableChat = true
// Set other meeting option properties as needed
};
try {
var roomURL = await meetingService.CreateMeetingAsync(RoomName, meetingOptions, Constants.DAILY);
foreach(var userInvited in Invited) {
string userEmail = userInvited.Email;
await EmailHelper.SendEmail(userEmail, RoomName, databaseUer, roomURL, null);
}
} catch(Exception ex) {
// Handle any exceptions that might occur during the meeting creation
Console.WriteLine($"Error creating meeting: {ex.Message}");
}
}
}
This work perfect, I can put a roomName, invite users (I use syncfusion checkbox, because I don't want to handle the check for each platform) and create a meeting.
So, if everything does the exact same thig why don't we create a reusable component
<StackLayout>
<inputLayout:SfTextInputLayout
Hint="{Binding Hint, Source={x:Reference UserExpander}}"
HorizontalOptions="StartAndExpand"
OutlineCornerRadius="8">
<Entry Text="{Binding RomeName, Source={x:Reference UserExpander}}" />
</inputLayout:SfTextInputLayout>
<expander:SfExpander>
<expander:SfExpander.Header>
<Grid
ColumnDefinitions="50,60"
ColumnSpacing="10">
<Label
FontFamily="Mat"
FontSize="Header"
HorizontalTextAlignment="Center"
Text="{Static helpers:IconFont.Group}"
VerticalTextAlignment="Center" />
<Label
Grid.Column="1"
Text="Students" />
</Grid>
</expander:SfExpander.Header>
<expander:SfExpander.Content>
<CollectionView
ItemsSource="{Binding Users, Source={x:Reference UserExpander}}"
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate>
<Border Margin="2">
<Border.StrokeShape>
<RoundRectangle CornerRadius="8" />
</Border.StrokeShape>
<Grid
Margin="5,5,5,0"
ColumnDefinitions="40,*"
RowDefinitions="*,*">
<Label
Grid.Column="1"
Text="{Binding Name}" />
<Label
Grid.Row="3"
Grid.Column="1"
Text="{Binding Email}" />
<chechbox:SfCheckBox
Grid.RowSpan="3"
HorizontalOptions="End"
IsChecked="{Binding IsParticipant}"
VerticalOptions="End">
<chechbox:SfCheckBox.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Source={x:Reference UserExpander}, Path=HandleCheckBox}"
CommandParameter="{Binding .}" />
</chechbox:SfCheckBox.GestureRecognizers>
</chechbox:SfCheckBox>
</Grid>
</Border>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</expander:SfExpander.Content>
</expander:SfExpander>
</StackLayout>
</ContentView>
public partial class UserDetailsExpander : ContentView {
public UserDetailsExpander() {
InitializeComponent();
}
public static readonly BindableProperty HintProperty = BindableProperty.Create(
nameof(Hint), typeof(string), typeof(UserDetailsExpander));
public string Hint {
get => (string)GetValue(HintProperty);
set => SetValue(HintProperty, value);
}
public static readonly BindableProperty RomeNameProperty = BindableProperty.Create(
nameof(RomeName), typeof(string), typeof(UserDetailsExpander));
public string RomeName {
get => (string)GetValue(RomeNameProperty);
set => SetValue(RomeNameProperty, value);
}
public static readonly BindableProperty UsersProperty = BindableProperty.Create(
nameof(Users), typeof(IEnumerable), typeof(UserDetailsExpander));
public IEnumerable Users {
get => (IEnumerable)GetValue(UsersProperty);
set => SetValue(UsersProperty, value);
}
public static readonly BindableProperty HandleCheckBoxCommandProperty = BindableProperty.Create(
nameof(HandleCheckBoxCommand), typeof(RelayCommand), typeof(UserDetailsExpander));
public RelayCommand HandleCheckBoxCommand {
get => (RelayCommand)GetValue(HandleCheckBoxCommandProperty);
set => SetValue(HandleCheckBoxCommandProperty, value);
}
}
Now why we don't go even further and inherit from the NewLecturePage
I did that
NewTestviewModel
public partial class NewTestPageViewMode(IAppService appService, IDataService<Models.User> dataService, IMeetingService meetingService, IAuthenticationService authenticationService) : NewLecturePageViewModel(appService, dataService, meetingService, authenticationService) {
}
and the page
xmlns:vm="clr-namespace:DemyAI.ViewModels"
Title="NewTest"
x:DataType="vm:NewTestPageViewMode">
<ContentPage.Behaviors>
<mct:EventToCommandBehavior
Command="{Binding AppearingCommand}"
EventName="Appearing" />
</ContentPage.Behaviors>
<VerticalStackLayout Margin="10">
<controls:UserDetailsExpander
HandleCheckBoxCommand="{Binding HandleCheckBoxCommand}"
Hint="Name of the test"
RomeName="{Binding RoomName}"
Users="{Binding Users}" />
<Button Command="{Binding StartMeetingCommand}"/>
</VerticalStackLayout>
everything seems to work
I get the students and everything
but when I press the button
everything Is empty.
I do not understand, if I am inherited from NewLecture, and have everything, why is not working