Sort DMs by last message and avoid grouping them in GUI

Closes #271
pull/882/head
Oleksii Holub 2 years ago
parent 2c7986c4e6
commit 2463cb5087

@ -20,7 +20,7 @@ public class GetDirectMessageChannelsCommand : TokenCommandBase
var textChannels = channels var textChannels = channels
.Where(c => c.Kind.IsText()) .Where(c => c.Kind.IsText())
.OrderBy(c => c.Category.Position) .OrderByDescending(c => c.LastMessageId)
.ThenBy(c => c.Name) .ThenBy(c => c.Name)
.ToArray(); .ToArray();

@ -35,10 +35,10 @@ public partial record Attachment
{ {
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse); var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
var url = json.GetProperty("url").GetNonWhiteSpaceString(); var url = json.GetProperty("url").GetNonWhiteSpaceString();
var width = json.GetPropertyOrNull("width")?.GetInt32OrNull();
var height = json.GetPropertyOrNull("height")?.GetInt32OrNull();
var fileName = json.GetProperty("filename").GetNonNullString(); var fileName = json.GetProperty("filename").GetNonNullString();
var description = json.GetPropertyOrNull("description")?.GetNonWhiteSpaceStringOrNull(); var description = json.GetPropertyOrNull("description")?.GetNonWhiteSpaceStringOrNull();
var width = json.GetPropertyOrNull("width")?.GetInt32OrNull();
var height = json.GetPropertyOrNull("height")?.GetInt32OrNull();
var fileSize = json.GetProperty("size").GetInt64().Pipe(FileSize.FromBytes); var fileSize = json.GetProperty("size").GetInt64().Pipe(FileSize.FromBytes);
return new Attachment(id, url, fileName, description, width, height, fileSize); return new Attachment(id, url, fileName, description, width, height, fileSize);

@ -14,7 +14,8 @@ public partial record Channel(
ChannelCategory Category, ChannelCategory Category,
string Name, string Name,
int? Position, int? Position,
string? Topic string? Topic,
Snowflake? LastMessageId
) : IHasId; ) : IHasId;
public partial record Channel public partial record Channel
@ -36,9 +37,8 @@ public partial record Channel
public static Channel Parse(JsonElement json, ChannelCategory? category = null, int? positionHint = null) public static Channel Parse(JsonElement json, ChannelCategory? category = null, int? positionHint = null)
{ {
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse); var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
var guildId = json.GetPropertyOrNull("guild_id")?.GetNonWhiteSpaceStringOrNull()?.Pipe(Snowflake.Parse);
var topic = json.GetPropertyOrNull("topic")?.GetStringOrNull();
var kind = (ChannelKind)json.GetProperty("type").GetInt32(); var kind = (ChannelKind)json.GetProperty("type").GetInt32();
var guildId = json.GetPropertyOrNull("guild_id")?.GetNonWhiteSpaceStringOrNull()?.Pipe(Snowflake.Parse);
var name = var name =
// Guild channel // Guild channel
@ -54,7 +54,16 @@ public partial record Channel
// Fallback // Fallback
id.ToString(); id.ToString();
var position = positionHint ?? json.GetPropertyOrNull("position")?.GetInt32OrNull(); var position =
positionHint ??
json.GetPropertyOrNull("position")?.GetInt32OrNull();
var topic = json.GetPropertyOrNull("topic")?.GetStringOrNull();
var lastMessageId = json
.GetPropertyOrNull("last_message_id")?
.GetNonWhiteSpaceStringOrNull()?
.Pipe(Snowflake.Parse);
return new Channel( return new Channel(
id, id,
@ -63,7 +72,8 @@ public partial record Channel
category ?? GetFallbackCategory(kind), category ?? GetFallbackCategory(kind),
name, name,
position, position,
topic topic,
lastMessageId
); );
} }
} }

@ -12,12 +12,15 @@ public record ChannelCategory(Snowflake Id, string Name, int? Position) : IHasId
public static ChannelCategory Parse(JsonElement json, int? positionHint = null) public static ChannelCategory Parse(JsonElement json, int? positionHint = null)
{ {
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse); var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
var position = positionHint ?? json.GetPropertyOrNull("position")?.GetInt32OrNull();
var name = var name =
json.GetPropertyOrNull("name")?.GetNonWhiteSpaceStringOrNull() ?? json.GetPropertyOrNull("name")?.GetNonWhiteSpaceStringOrNull() ??
id.ToString(); id.ToString();
var position =
positionHint ??
json.GetPropertyOrNull("position")?.GetInt32OrNull();
return new ChannelCategory(id, name, position); return new ChannelCategory(id, name, position);
} }
} }

@ -39,17 +39,17 @@ public partial record Embed
var url = json.GetPropertyOrNull("url")?.GetNonWhiteSpaceStringOrNull(); var url = json.GetPropertyOrNull("url")?.GetNonWhiteSpaceStringOrNull();
var timestamp = json.GetPropertyOrNull("timestamp")?.GetDateTimeOffset(); var timestamp = json.GetPropertyOrNull("timestamp")?.GetDateTimeOffset();
var color = json.GetPropertyOrNull("color")?.GetInt32OrNull()?.Pipe(System.Drawing.Color.FromArgb).ResetAlpha(); var color = json.GetPropertyOrNull("color")?.GetInt32OrNull()?.Pipe(System.Drawing.Color.FromArgb).ResetAlpha();
var description = json.GetPropertyOrNull("description")?.GetStringOrNull();
var author = json.GetPropertyOrNull("author")?.Pipe(EmbedAuthor.Parse); var author = json.GetPropertyOrNull("author")?.Pipe(EmbedAuthor.Parse);
var thumbnail = json.GetPropertyOrNull("thumbnail")?.Pipe(EmbedImage.Parse); var description = json.GetPropertyOrNull("description")?.GetStringOrNull();
var image = json.GetPropertyOrNull("image")?.Pipe(EmbedImage.Parse);
var footer = json.GetPropertyOrNull("footer")?.Pipe(EmbedFooter.Parse);
var fields = var fields =
json.GetPropertyOrNull("fields")?.EnumerateArrayOrNull()?.Select(EmbedField.Parse).ToArray() ?? json.GetPropertyOrNull("fields")?.EnumerateArrayOrNull()?.Select(EmbedField.Parse).ToArray() ??
Array.Empty<EmbedField>(); Array.Empty<EmbedField>();
var thumbnail = json.GetPropertyOrNull("thumbnail")?.Pipe(EmbedImage.Parse);
var image = json.GetPropertyOrNull("image")?.Pipe(EmbedImage.Parse);
var footer = json.GetPropertyOrNull("footer")?.Pipe(EmbedFooter.Parse);
return new Embed( return new Embed(
title, title,
url, url,

@ -64,7 +64,6 @@ public partial record Emoji
var id = json.GetPropertyOrNull("id")?.GetNonWhiteSpaceStringOrNull()?.Pipe(Snowflake.Parse); var id = json.GetPropertyOrNull("id")?.GetNonWhiteSpaceStringOrNull()?.Pipe(Snowflake.Parse);
var name = json.GetPropertyOrNull("name")?.GetNonWhiteSpaceStringOrNull(); var name = json.GetPropertyOrNull("name")?.GetNonWhiteSpaceStringOrNull();
var isAnimated = json.GetPropertyOrNull("animated")?.GetBooleanOrNull() ?? false; var isAnimated = json.GetPropertyOrNull("animated")?.GetBooleanOrNull() ?? false;
var imageUrl = GetImageUrl(id, name, isAnimated); var imageUrl = GetImageUrl(id, name, isAnimated);
return new Emoji( return new Emoji(

@ -24,8 +24,8 @@ public record Guild(Snowflake Id, string Name, string IconUrl) : IHasId
{ {
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse); var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
var name = json.GetProperty("name").GetNonNullString(); var name = json.GetProperty("name").GetNonNullString();
var iconHash = json.GetPropertyOrNull("icon")?.GetNonWhiteSpaceStringOrNull();
var iconHash = json.GetPropertyOrNull("icon")?.GetNonWhiteSpaceStringOrNull();
var iconUrl = !string.IsNullOrWhiteSpace(iconHash) var iconUrl = !string.IsNullOrWhiteSpace(iconHash)
? GetIconUrl(id, iconHash) ? GetIconUrl(id, iconHash)
: GetDefaultIconUrl(); : GetDefaultIconUrl();

@ -30,15 +30,17 @@ public record Message(
public static Message Parse(JsonElement json) public static Message Parse(JsonElement json)
{ {
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse); var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
var kind = (MessageKind)json.GetProperty("type").GetInt32();
var author = json.GetProperty("author").Pipe(User.Parse); var author = json.GetProperty("author").Pipe(User.Parse);
var timestamp = json.GetProperty("timestamp").GetDateTimeOffset(); var timestamp = json.GetProperty("timestamp").GetDateTimeOffset();
var editedTimestamp = json.GetPropertyOrNull("edited_timestamp")?.GetDateTimeOffset(); var editedTimestamp = json.GetPropertyOrNull("edited_timestamp")?.GetDateTimeOffset();
var callEndedTimestamp = json.GetPropertyOrNull("call")?.GetPropertyOrNull("ended_timestamp") var callEndedTimestamp = json
?.GetDateTimeOffset(); .GetPropertyOrNull("call")?
var kind = (MessageKind)json.GetProperty("type").GetInt32(); .GetPropertyOrNull("ended_timestamp")?
.GetDateTimeOffset();
var isPinned = json.GetPropertyOrNull("pinned")?.GetBooleanOrNull() ?? false; var isPinned = json.GetPropertyOrNull("pinned")?.GetBooleanOrNull() ?? false;
var messageReference = json.GetPropertyOrNull("message_reference")?.Pipe(MessageReference.Parse);
var referencedMessage = json.GetPropertyOrNull("referenced_message")?.Pipe(Parse);
var content = kind switch var content = kind switch
{ {
@ -73,6 +75,9 @@ public record Message(
json.GetPropertyOrNull("mentions")?.EnumerateArrayOrNull()?.Select(User.Parse).ToArray() ?? json.GetPropertyOrNull("mentions")?.EnumerateArrayOrNull()?.Select(User.Parse).ToArray() ??
Array.Empty<User>(); Array.Empty<User>();
var messageReference = json.GetPropertyOrNull("message_reference")?.Pipe(MessageReference.Parse);
var referencedMessage = json.GetPropertyOrNull("referenced_message")?.Pipe(Parse);
return new Message( return new Message(
id, id,
kind, kind,

@ -9,9 +9,20 @@ public record MessageReference(Snowflake? MessageId, Snowflake? ChannelId, Snowf
{ {
public static MessageReference Parse(JsonElement json) public static MessageReference Parse(JsonElement json)
{ {
var messageId = json.GetPropertyOrNull("message_id")?.GetNonWhiteSpaceStringOrNull()?.Pipe(Snowflake.Parse); var messageId = json
var channelId = json.GetPropertyOrNull("channel_id")?.GetNonWhiteSpaceStringOrNull()?.Pipe(Snowflake.Parse); .GetPropertyOrNull("message_id")?
var guildId = json.GetPropertyOrNull("guild_id")?.GetNonWhiteSpaceStringOrNull()?.Pipe(Snowflake.Parse); .GetNonWhiteSpaceStringOrNull()?
.Pipe(Snowflake.Parse);
var channelId = json
.GetPropertyOrNull("channel_id")?
.GetNonWhiteSpaceStringOrNull()?
.Pipe(Snowflake.Parse);
var guildId = json
.GetPropertyOrNull("guild_id")?
.GetNonWhiteSpaceStringOrNull()?
.Pipe(Snowflake.Parse);
return new MessageReference(messageId, channelId, guildId); return new MessageReference(messageId, channelId, guildId);
} }

@ -18,7 +18,6 @@ public record Sticker(Snowflake Id, string Name, StickerFormat Format, string So
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse); var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
var name = json.GetProperty("name").GetNonNullString(); var name = json.GetProperty("name").GetNonNullString();
var format = (StickerFormat)json.GetProperty("format_type").GetInt32(); var format = (StickerFormat)json.GetProperty("format_type").GetInt32();
var sourceUrl = GetSourceUrl(id, format); var sourceUrl = GetSourceUrl(id, format);
return new Sticker(id, name, format, sourceUrl); return new Sticker(id, name, format, sourceUrl);

@ -39,12 +39,12 @@ public partial record User
var isBot = json.GetPropertyOrNull("bot")?.GetBooleanOrNull() ?? false; var isBot = json.GetPropertyOrNull("bot")?.GetBooleanOrNull() ?? false;
var discriminator = json.GetProperty("discriminator").GetNonWhiteSpaceString().Pipe(int.Parse); var discriminator = json.GetProperty("discriminator").GetNonWhiteSpaceString().Pipe(int.Parse);
var name = json.GetProperty("username").GetNonNullString(); var name = json.GetProperty("username").GetNonNullString();
var avatarHash = json.GetPropertyOrNull("avatar")?.GetNonWhiteSpaceStringOrNull();
var avatarHash = json.GetPropertyOrNull("avatar")?.GetNonWhiteSpaceStringOrNull();
var avatarUrl = !string.IsNullOrWhiteSpace(avatarHash) var avatarUrl = !string.IsNullOrWhiteSpace(avatarHash)
? GetAvatarUrl(id, avatarHash) ? GetAvatarUrl(id, avatarHash)
: GetDefaultAvatarUrl(discriminator); : GetDefaultAvatarUrl(discriminator);
return new User(id, isBot, discriminator, name, avatarUrl); return new User(id, isBot, discriminator, name, avatarUrl);
} }
} }

@ -181,6 +181,8 @@ public class DiscordClient
.Select((j, index) => ChannelCategory.Parse(j, index + 1)) .Select((j, index) => ChannelCategory.Parse(j, index + 1))
.ToDictionary(j => j.Id.ToString(), StringComparer.Ordinal); .ToDictionary(j => j.Id.ToString(), StringComparer.Ordinal);
// Discord positions are not deterministic, so we need to normalize them
// because the user may refer to the channel position via file name template.
var position = 0; var position = 0;
foreach (var channelJson in responseOrdered) foreach (var channelJson in responseOrdered)

@ -49,7 +49,15 @@ public partial record struct Snowflake
public static Snowflake Parse(string str) => Parse(str, null); public static Snowflake Parse(string str) => Parse(str, null);
} }
public partial record struct Snowflake : IComparable<Snowflake> public partial record struct Snowflake : IComparable<Snowflake>, IComparable
{ {
public int CompareTo(Snowflake other) => Value.CompareTo(other.Value); public int CompareTo(Snowflake other) => Value.CompareTo(other.Value);
public int CompareTo(object? obj)
{
if (obj is not Snowflake other)
throw new ArgumentException($"Object must be of type {nameof(Snowflake)}.");
return Value.CompareTo(other.Value);
}
} }

@ -0,0 +1,20 @@
using System;
using System.Globalization;
using System.Windows.Data;
using DiscordChatExporter.Core.Discord;
namespace DiscordChatExporter.Gui.Converters;
[ValueConversion(typeof(Snowflake?), typeof(DateTimeOffset?))]
public class SnowflakeToDateTimeOffsetConverter : IValueConverter
{
public static SnowflakeToDateTimeOffsetConverter Instance { get; } = new();
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
value is Snowflake snowflake
? snowflake.ToDate()
: null;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
throw new NotSupportedException();
}

@ -44,6 +44,8 @@ public class DashboardViewModel : PropertyChangedBase
public Guild? SelectedGuild { get; set; } public Guild? SelectedGuild { get; set; }
public bool IsDirectMessageGuildSelected => SelectedGuild?.Id == Guild.DirectMessages.Id;
public IReadOnlyList<Channel>? AvailableChannels => SelectedGuild is not null public IReadOnlyList<Channel>? AvailableChannels => SelectedGuild is not null
? GuildChannelMap?[SelectedGuild] ? GuildChannelMap?[SelectedGuild]
: null; : null;

@ -8,6 +8,8 @@
xmlns:components="clr-namespace:DiscordChatExporter.Gui.ViewModels.Components" xmlns:components="clr-namespace:DiscordChatExporter.Gui.ViewModels.Components"
xmlns:converters="clr-namespace:DiscordChatExporter.Gui.Converters" xmlns:converters="clr-namespace:DiscordChatExporter.Gui.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:data="clr-namespace:DiscordChatExporter.Core.Discord.Data;assembly=DiscordChatExporter.Core"
xmlns:globalization="clr-namespace:System.Globalization;assembly=System.Runtime"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
@ -16,6 +18,14 @@
Loaded="{s:Action OnViewLoaded}" Loaded="{s:Action OnViewLoaded}"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.Resources> <UserControl.Resources>
<!-- Sort DMs by last message -->
<CollectionViewSource x:Key="AvailableDirectMessageChannelsViewSource" Source="{Binding AvailableChannels, Mode=OneWay}">
<CollectionViewSource.SortDescriptions>
<componentModel:SortDescription Direction="Descending" PropertyName="LastMessageId" />
<componentModel:SortDescription Direction="Ascending" PropertyName="Name" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<!-- Sort guild channels by position -->
<CollectionViewSource x:Key="AvailableChannelsViewSource" Source="{Binding AvailableChannels, Mode=OneWay}"> <CollectionViewSource x:Key="AvailableChannelsViewSource" Source="{Binding AvailableChannels, Mode=OneWay}">
<CollectionViewSource.GroupDescriptions> <CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Category.Name" /> <PropertyGroupDescription PropertyName="Category.Name" />
@ -274,13 +284,24 @@
<Border Grid.Column="1"> <Border Grid.Column="1">
<ListBox <ListBox
HorizontalContentAlignment="Stretch" HorizontalContentAlignment="Stretch"
ItemsSource="{Binding Source={StaticResource AvailableChannelsViewSource}}"
SelectionMode="Extended" SelectionMode="Extended"
TextSearch.TextPath="Model.Name" TextSearch.TextPath="Model.Name"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"> VirtualizingPanel.IsVirtualizingWhenGrouping="True">
<b:Interaction.Behaviors> <b:Interaction.Behaviors>
<behaviors:ChannelMultiSelectionListBoxBehavior SelectedItems="{Binding SelectedChannels}" /> <behaviors:ChannelMultiSelectionListBoxBehavior SelectedItems="{Binding SelectedChannels}" />
</b:Interaction.Behaviors> </b:Interaction.Behaviors>
<ListBox.Style>
<Style BasedOn="{StaticResource {x:Type ListBox}}" TargetType="{x:Type ListBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsDirectMessageGuildSelected}" Value="True">
<Setter Property="ItemsSource" Value="{Binding Source={StaticResource AvailableDirectMessageChannelsViewSource}}" />
</DataTrigger>
<DataTrigger Binding="{Binding IsDirectMessageGuildSelected}" Value="False">
<Setter Property="ItemsSource" Value="{Binding Source={StaticResource AvailableChannelsViewSource}}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
<ListBox.GroupStyle> <ListBox.GroupStyle>
<GroupStyle> <GroupStyle>
<GroupStyle.ContainerStyle> <GroupStyle.ContainerStyle>
@ -306,7 +327,7 @@
</GroupStyle> </GroupStyle>
</ListBox.GroupStyle> </ListBox.GroupStyle>
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate> <DataTemplate DataType="{x:Type data:Channel}">
<Grid Margin="-8" Background="Transparent"> <Grid Margin="-8" Background="Transparent">
<Grid.InputBindings> <Grid.InputBindings>
<MouseBinding Command="{s:Action ExportChannels}" MouseAction="LeftDoubleClick" /> <MouseBinding Command="{s:Action ExportChannels}" MouseAction="LeftDoubleClick" />
@ -316,6 +337,12 @@
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.ToolTip>
<TextBlock>
<Run Text="Last message sent on:" />
<Run FontWeight="SemiBold" Text="{Binding LastMessageId, Converter={x:Static converters:SnowflakeToDateTimeOffsetConverter.Instance}, ConverterCulture={x:Static globalization:CultureInfo.CurrentCulture}, TargetNullValue=never}" />
</TextBlock>
</Grid.ToolTip>
<!-- Channel icon --> <!-- Channel icon -->
<materialDesign:PackIcon <materialDesign:PackIcon

@ -5,6 +5,7 @@
xmlns:converters="clr-namespace:DiscordChatExporter.Gui.Converters" xmlns:converters="clr-namespace:DiscordChatExporter.Gui.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dialogs="clr-namespace:DiscordChatExporter.Gui.ViewModels.Dialogs" xmlns:dialogs="clr-namespace:DiscordChatExporter.Gui.ViewModels.Dialogs"
xmlns:globalization="clr-namespace:System.Globalization;assembly=System.Runtime"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:s="https://github.com/canton7/Stylet" xmlns:s="https://github.com/canton7/Stylet"
@ -86,7 +87,7 @@
Style="{DynamicResource MaterialDesignOutlinedComboBox}"> Style="{DynamicResource MaterialDesignOutlinedComboBox}">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<TextBlock Text="{Binding Converter={x:Static converters:ExportFormatToStringConverter.Instance}}" /> <TextBlock Text="{Binding Converter={x:Static converters:ExportFormatToStringConverter.Instance}, ConverterCulture={x:Static globalization:CultureInfo.CurrentCulture}}" />
</DataTemplate> </DataTemplate>
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
@ -110,8 +111,8 @@
Margin="16,8,16,4" Margin="16,8,16,4"
materialDesign:HintAssist.Hint="After (date)" materialDesign:HintAssist.Hint="After (date)"
materialDesign:HintAssist.IsFloating="True" materialDesign:HintAssist.IsFloating="True"
DisplayDateEnd="{Binding BeforeDate, Converter={x:Static converters:DateTimeOffsetToDateTimeConverter.Instance}}" DisplayDateEnd="{Binding BeforeDate, Converter={x:Static converters:DateTimeOffsetToDateTimeConverter.Instance}, ConverterCulture={x:Static globalization:CultureInfo.CurrentCulture}}"
SelectedDate="{Binding AfterDate, Converter={x:Static converters:DateTimeOffsetToDateTimeConverter.Instance}}" SelectedDate="{Binding AfterDate, Converter={x:Static converters:DateTimeOffsetToDateTimeConverter.Instance}, ConverterCulture={x:Static globalization:CultureInfo.CurrentCulture}}"
Style="{DynamicResource MaterialDesignOutlinedDatePicker}" Style="{DynamicResource MaterialDesignOutlinedDatePicker}"
ToolTip="Only include messages sent after this date" /> ToolTip="Only include messages sent after this date" />
<DatePicker <DatePicker
@ -120,8 +121,8 @@
Margin="16,8,16,4" Margin="16,8,16,4"
materialDesign:HintAssist.Hint="Before (date)" materialDesign:HintAssist.Hint="Before (date)"
materialDesign:HintAssist.IsFloating="True" materialDesign:HintAssist.IsFloating="True"
DisplayDateStart="{Binding AfterDate, Converter={x:Static converters:DateTimeOffsetToDateTimeConverter.Instance}}" DisplayDateStart="{Binding AfterDate, Converter={x:Static converters:DateTimeOffsetToDateTimeConverter.Instance}, ConverterCulture={x:Static globalization:CultureInfo.CurrentCulture}}"
SelectedDate="{Binding BeforeDate, Converter={x:Static converters:DateTimeOffsetToDateTimeConverter.Instance}}" SelectedDate="{Binding BeforeDate, Converter={x:Static converters:DateTimeOffsetToDateTimeConverter.Instance}, ConverterCulture={x:Static globalization:CultureInfo.CurrentCulture}}"
Style="{DynamicResource MaterialDesignOutlinedDatePicker}" Style="{DynamicResource MaterialDesignOutlinedDatePicker}"
ToolTip="Only include messages sent before this date" /> ToolTip="Only include messages sent before this date" />
<materialDesign:TimePicker <materialDesign:TimePicker
@ -132,7 +133,7 @@
materialDesign:HintAssist.IsFloating="True" materialDesign:HintAssist.IsFloating="True"
Is24Hours="{x:Static utils:Internationalization.Is24Hours}" Is24Hours="{x:Static utils:Internationalization.Is24Hours}"
IsEnabled="{Binding IsAfterDateSet}" IsEnabled="{Binding IsAfterDateSet}"
SelectedTime="{Binding AfterTime, Converter={x:Static converters:TimeSpanToDateTimeConverter.Instance}}" SelectedTime="{Binding AfterTime, Converter={x:Static converters:TimeSpanToDateTimeConverter.Instance}, ConverterCulture={x:Static globalization:CultureInfo.CurrentCulture}}"
Style="{DynamicResource MaterialDesignOutlinedTimePicker}" Style="{DynamicResource MaterialDesignOutlinedTimePicker}"
ToolTip="Only include messages sent after this time" /> ToolTip="Only include messages sent after this time" />
<materialDesign:TimePicker <materialDesign:TimePicker
@ -143,7 +144,7 @@
materialDesign:HintAssist.IsFloating="True" materialDesign:HintAssist.IsFloating="True"
Is24Hours="{x:Static utils:Internationalization.Is24Hours}" Is24Hours="{x:Static utils:Internationalization.Is24Hours}"
IsEnabled="{Binding IsBeforeDateSet}" IsEnabled="{Binding IsBeforeDateSet}"
SelectedTime="{Binding BeforeTime, Converter={x:Static converters:TimeSpanToDateTimeConverter.Instance}}" SelectedTime="{Binding BeforeTime, Converter={x:Static converters:TimeSpanToDateTimeConverter.Instance}, ConverterCulture={x:Static globalization:CultureInfo.CurrentCulture}}"
Style="{DynamicResource MaterialDesignOutlinedTimePicker}" Style="{DynamicResource MaterialDesignOutlinedTimePicker}"
ToolTip="Only include messages sent before this time" /> ToolTip="Only include messages sent before this time" />
</Grid> </Grid>

Loading…
Cancel
Save