Files

245 lines
9.6 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.ObjectModel;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using Windows.System;
namespace WpywMail.Client;
public sealed partial class MainWindow : Window
{
private readonly ObservableCollection<MailSummary> messages = [];
private ApiClient api = new();
private string folder = "inbox";
private CancellationTokenSource? searchCancellation;
public MainWindow()
{
InitializeComponent();
MessageList.ItemsSource = messages;
}
private async void LoginButton_Click(object sender, RoutedEventArgs e) => await LoginAsync();
private async void PasswordBox_KeyDown(object sender, KeyRoutedEventArgs e)
{
if (e.Key == VirtualKey.Enter) await LoginAsync();
}
private async Task LoginAsync()
{
var email = EmailBox.Text.Trim();
var password = PasswordBox.Password;
if (string.IsNullOrWhiteSpace(email) || string.IsNullOrWhiteSpace(password))
{
LoginStatus.Text = "请输入邮箱和密码。";
return;
}
LoginButton.IsEnabled = false;
LoginStatus.Text = "正在连接邮箱服务…";
try
{
api.SetBaseUrl(ApiUrlBox.Text);
var result = await api.LoginAsync(email, password);
api.SetToken(result.Token);
AccountText.Text = result.User.Email;
LoginView.Visibility = Visibility.Collapsed;
ShellView.Visibility = Visibility.Visible;
LoginStatus.Text = "";
await RefreshAsync();
}
catch (Exception ex)
{
LoginStatus.Text = ex.Message.Contains("无法连接", StringComparison.OrdinalIgnoreCase)
? "无法连接服务端,请检查地址、端口和服务状态。"
: ex.Message;
}
finally { LoginButton.IsEnabled = true; }
}
private async Task RefreshAsync()
{
try
{
var me = await api.GetMeAsync();
InboxCount.Text = me.Stats.Unread > 0 ? me.Stats.Unread.ToString() : "";
await LoadFolderAsync(folder, SearchBox.Text);
ConnectionText.Text = "已连接";
ConnectionText.Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["AccentBrush"];
}
catch (Exception ex)
{
ConnectionText.Text = ex.Message;
ConnectionText.Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["DangerBrush"];
}
}
private async Task LoadFolderAsync(string selectedFolder, string query = "")
{
try
{
var result = await api.GetMessagesAsync(selectedFolder, query);
messages.Clear();
foreach (var message in result) messages.Add(message);
FolderTitle.Text = selectedFolder switch
{
"sent" => "已发送",
"drafts" => "草稿",
"archive" => "归档",
"trash" => "垃圾箱",
_ => "收件箱"
};
FolderSubtitle.Text = messages.Count == 0 ? "暂无邮件" : $"{messages.Count} 封邮件";
}
catch (Exception ex)
{
ConnectionText.Text = ex.Message;
ConnectionText.Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["DangerBrush"];
}
}
private async void FolderButton_Click(object sender, RoutedEventArgs e)
{
if (sender is not Button button || button.Tag is not string selectedFolder) return;
folder = selectedFolder;
EmptyReadingPane.Visibility = Visibility.Visible;
ReadingPane.Visibility = Visibility.Collapsed;
await LoadFolderAsync(folder, SearchBox.Text);
}
private async void MessageList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (MessageList.SelectedItem is not MailSummary summary) return;
try
{
var message = await api.GetMessageAsync(summary.Id);
ReadingSubject.Text = message.Subject;
ReadingInitials.Text = message.Initials;
ReadingFrom.Text = message.From;
ReadingDate.Text = message.Date.ToLocalTime().ToString("yyyy-MM-dd HH:mm");
ReadingBody.Text = message.Text;
EmptyReadingPane.Visibility = Visibility.Collapsed;
ReadingPane.Visibility = Visibility.Visible;
summary.Unread = false;
MessageList.SelectedItem = null;
}
catch (Exception ex) { ConnectionText.Text = ex.Message; }
}
private async void RefreshButton_Click(object sender, RoutedEventArgs e) => await RefreshAsync();
private async void SearchBox_TextChanged(object sender, TextChangedEventArgs e)
{
searchCancellation?.Cancel();
searchCancellation = new CancellationTokenSource();
var token = searchCancellation.Token;
try
{
await Task.Delay(260, token);
await LoadFolderAsync(folder, SearchBox.Text);
}
catch (OperationCanceledException) { }
}
private async void LogoutButton_Click(object sender, RoutedEventArgs e)
{
await api.LogoutAsync();
messages.Clear();
ShellView.Visibility = Visibility.Collapsed;
LoginView.Visibility = Visibility.Visible;
PasswordBox.Password = "";
LoginStatus.Text = "已退出登录。";
}
private async void SettingsButton_Click(object sender, RoutedEventArgs e)
{
ConfigResponse config;
try { config = await api.GetConfigAsync(); }
catch (Exception ex)
{
ConnectionText.Text = ex.Message;
ConnectionText.Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["DangerBrush"];
return;
}
var newPassword = new PasswordBox { PlaceholderText = "新密码(至少 12 位)", MinWidth = 360 };
var content = new StackPanel { Spacing = 12, Width = 430 };
content.Children.Add(new TextBlock { Text = $"账户\n{config.Account}", Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["TextSecondaryBrush"] });
content.Children.Add(new TextBlock { Text = $"域名:{config.Domain}\n主机名:{config.Hostname}\nSMTP:{config.Protocols.Smtp} 提交端口:{config.Protocols.Submission}", Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["TextTertiaryBrush"] });
content.Children.Add(new Border { Height = 1, Background = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["StrokeBrush"] });
content.Children.Add(new TextBlock { Text = "修改密码", FontWeight = Microsoft.UI.Text.FontWeights.SemiBold });
content.Children.Add(newPassword);
var dialog = new ContentDialog
{
Title = "账户与服务",
Content = content,
PrimaryButtonText = "保存密码",
CloseButtonText = "关闭",
DefaultButton = ContentDialogButton.Close,
XamlRoot = Content.XamlRoot
};
if (await dialog.ShowAsync() != ContentDialogResult.Primary || string.IsNullOrWhiteSpace(newPassword.Password)) return;
if (newPassword.Password.Length < 12)
{
ConnectionText.Text = "密码至少需要 12 位。";
ConnectionText.Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["DangerBrush"];
return;
}
try
{
await api.ChangePasswordAsync(newPassword.Password);
ConnectionText.Text = "密码已更新。下次登录请使用新密码。";
ConnectionText.Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["AccentBrush"];
}
catch (Exception ex)
{
ConnectionText.Text = ex.Message;
ConnectionText.Foreground = (Microsoft.UI.Xaml.Media.Brush)Application.Current.Resources["DangerBrush"];
}
}
private async void ComposeButton_Click(object sender, RoutedEventArgs e)
{
var dialog = new ContentDialog
{
Title = "写信",
PrimaryButtonText = "发送",
CloseButtonText = "取消",
DefaultButton = ContentDialogButton.Primary,
XamlRoot = Content.XamlRoot,
Content = new ComposeView()
};
if (await dialog.ShowAsync() != ContentDialogResult.Primary || dialog.Content is not ComposeView compose) return;
if (string.IsNullOrWhiteSpace(compose.ToBox.Text) || string.IsNullOrWhiteSpace(compose.SubjectBox.Text) || string.IsNullOrWhiteSpace(compose.BodyBox.Text))
{
ConnectionText.Text = "收件人、主题和正文不能为空。";
return;
}
try
{
await api.SendMessageAsync(compose.ToBox.Text, compose.SubjectBox.Text, compose.BodyBox.Text);
ConnectionText.Text = "邮件已加入发送队列。";
if (folder == "sent") await LoadFolderAsync(folder, SearchBox.Text);
}
catch (Exception ex) { ConnectionText.Text = ex.Message; }
}
}
public sealed class ComposeView : StackPanel
{
public TextBox ToBox { get; } = new() { PlaceholderText = "收件人,例如 [email protected]" };
public TextBox SubjectBox { get; } = new() { PlaceholderText = "主题" };
public TextBox BodyBox { get; } = new() { PlaceholderText = "正文", AcceptsReturn = true, TextWrapping = TextWrapping.Wrap, MinHeight = 180 };
public ComposeView()
{
Spacing = 10;
Width = 520;
Children.Add(ToBox);
Children.Add(SubjectBox);
Children.Add(BodyBox);
}
}