Browse Source

add vpn ui toggle

bdbai 3 years ago
parent
commit
cf3a709cf5
6 changed files with 344 additions and 186 deletions
  1. 1 2
      Maple.App/App.xaml
  2. 106 6
      Maple.App/MainPage.cpp
  3. 11 0
      Maple.App/MainPage.h
  4. 213 167
      Maple.App/MainPage.xaml
  5. 13 11
      README.md
  6. BIN
      image/screenshot-setting1.png

+ 1 - 2
Maple.App/App.xaml

@@ -2,8 +2,7 @@
     x:Class="Maple_App.App"
     x:Class="Maple_App.App"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    xmlns:local="using:Maple_App"
-    RequestedTheme="Light">
+    xmlns:local="using:Maple_App">
     <Application.Resources>
     <Application.Resources>
         <ResourceDictionary>
         <ResourceDictionary>
             <Color x:Key="SystemAccentColor">#2ebe5a</Color>
             <Color x:Key="SystemAccentColor">#2ebe5a</Color>

+ 106 - 6
Maple.App/MainPage.cpp

@@ -2,13 +2,18 @@
 #include "MainPage.h"
 #include "MainPage.h"
 #include "MainPage.g.cpp"
 #include "MainPage.g.cpp"
 #include <filesystem>
 #include <filesystem>
-#include <winrt/Windows.Networking.Vpn.h>
+#include <winrt/Windows.ApplicationModel.Core.h>
 #include <winrt/Windows.UI.ViewManagement.h>
 #include <winrt/Windows.UI.ViewManagement.h>
+#include <winrt/Windows.UI.Xaml.Media.h>
 #include "Model\Netif.h"
 #include "Model\Netif.h"
 
 
 namespace winrt::Maple_App::implementation
 namespace winrt::Maple_App::implementation
 {
 {
+    using namespace std::chrono_literals;
+
     using namespace winrt::Windows::UI::ViewManagement;
     using namespace winrt::Windows::UI::ViewManagement;
+    using namespace winrt::Windows::UI::Xaml;
+    using namespace winrt::Windows::UI::Xaml::Media;
 
 
     std::string getNormalizedExtentionFromPath(const winrt::hstring& path) {
     std::string getNormalizedExtentionFromPath(const winrt::hstring& path) {
         auto ext = std::filesystem::path(std::wstring_view(path)).extension().string();
         auto ext = std::filesystem::path(std::wstring_view(path)).extension().string();
@@ -21,6 +26,34 @@ namespace winrt::Maple_App::implementation
     MainPage::MainPage()
     MainPage::MainPage()
     {
     {
         InitializeComponent();
         InitializeComponent();
+
+        const auto coreTitleBar{ CoreApplication::GetCurrentView().TitleBar() };
+        const auto window{ Window::Current() };
+        coreTitleBar.ExtendViewIntoTitleBar(true);
+        window.SetTitleBar(CustomTitleBar());
+
+        coreTitleBar.LayoutMetricsChanged({ this,&MainPage::CoreTitleBar_LayoutMetricsChanged });
+        window.Activated({ this, &MainPage::CoreWindow_Activated });
+    }
+
+    void MainPage::CoreTitleBar_LayoutMetricsChanged(CoreApplicationViewTitleBar const& coreTitleBar, IInspectable const&)
+    {
+        LeftPaddingColumn().Width(GridLength{ .Value = coreTitleBar.SystemOverlayLeftInset(), .GridUnitType = GridUnitType::Pixel });
+        RightPaddingColumn().Width(GridLength{ .Value = coreTitleBar.SystemOverlayRightInset(), .GridUnitType = GridUnitType::Pixel });
+    }
+    void MainPage::CoreWindow_Activated(IInspectable const&, WindowActivatedEventArgs const& args)
+    {
+        UISettings settings{};
+        if (args.WindowActivationState() == CoreWindowActivationState::Deactivated)
+        {
+            AppTitleTextBlock().Foreground(
+                SolidColorBrush{ settings.UIElementColor(UIElementType::GrayText) });
+        }
+        else
+        {
+            AppTitleTextBlock().Foreground(
+                SolidColorBrush{ settings.GetColorValue(UIColorType::Foreground) });
+        }
     }
     }
 
 
     DependencyProperty MainPage::ConfigItemsProperty()
     DependencyProperty MainPage::ConfigItemsProperty()
@@ -50,6 +83,8 @@ namespace winrt::Maple_App::implementation
                 }
                 }
             }
             }
             });
             });
+
+        StartConnectionCheck();
     }
     }
 
 
     IAsyncAction MainPage::NotifyUser(const hstring& message) {
     IAsyncAction MainPage::NotifyUser(const hstring& message) {
@@ -276,7 +311,7 @@ namespace winrt::Maple_App::implementation
             boxed_netifs.reserve(netifs.size());
             boxed_netifs.reserve(netifs.size());
             std::transform(netifs.begin(), netifs.end(), std::back_inserter(boxed_netifs), [](const auto& netif) -> auto {
             std::transform(netifs.begin(), netifs.end(), std::back_inserter(boxed_netifs), [](const auto& netif) -> auto {
                 return netif;
                 return netif;
-            });
+                });
             NetifCombobox().ItemsSource(single_threaded_vector(std::move(boxed_netifs)));
             NetifCombobox().ItemsSource(single_threaded_vector(std::move(boxed_netifs)));
 
 
             const auto& currentNetif = ApplicationData::Current().LocalSettings().Values().TryLookup(NETIF_SETTING_KEY).try_as<hstring>();
             const auto& currentNetif = ApplicationData::Current().LocalSettings().Values().TryLookup(NETIF_SETTING_KEY).try_as<hstring>();
@@ -380,8 +415,6 @@ namespace winrt::Maple_App::implementation
     fire_and_forget MainPage::GenerateProfileButton_Click(IInspectable const& sender, RoutedEventArgs const& e)
     fire_and_forget MainPage::GenerateProfileButton_Click(IInspectable const& sender, RoutedEventArgs const& e)
     {
     {
         const auto lifetime = get_strong();
         const auto lifetime = get_strong();
-        using namespace winrt::Windows::Networking::Vpn;
-        const auto& agent = VpnManagementAgent{};
         const auto& profile = VpnPlugInProfile{};
         const auto& profile = VpnPlugInProfile{};
         profile.AlwaysOn(false);
         profile.AlwaysOn(false);
         profile.ProfileName(L"Maple");
         profile.ProfileName(L"Maple");
@@ -389,7 +422,7 @@ namespace winrt::Maple_App::implementation
         profile.VpnPluginPackageFamilyName(Windows::ApplicationModel::Package::Current().Id().FamilyName());
         profile.VpnPluginPackageFamilyName(Windows::ApplicationModel::Package::Current().Id().FamilyName());
         profile.RememberCredentials(false);
         profile.RememberCredentials(false);
         profile.ServerUris().Append(Uri{ L"https://github.com/YtFlow/Maple" });
         profile.ServerUris().Append(Uri{ L"https://github.com/YtFlow/Maple" });
-        const auto& result = co_await agent.AddProfileFromObjectAsync(profile);
+        const auto& result = co_await VpnMgmtAgent.AddProfileFromObjectAsync(profile);
         if (result == VpnManagementErrorStatus::Ok) {
         if (result == VpnManagementErrorStatus::Ok) {
             co_await NotifyUser(L"Profile generated.");
             co_await NotifyUser(L"Profile generated.");
         }
         }
@@ -397,5 +430,72 @@ namespace winrt::Maple_App::implementation
             co_await NotifyUser(L"Failed to generate a profile (" + to_hstring(static_cast<int32_t>(result)) + L").");
             co_await NotifyUser(L"Failed to generate a profile (" + to_hstring(static_cast<int32_t>(result)) + L").");
         }
         }
     }
     }
-}
 
 
+    fire_and_forget MainPage::ConnectionToggleSwitch_Toggled(IInspectable const&, RoutedEventArgs const&)
+    {
+        const auto lifetime{ get_strong() };
+
+        if (!ApplicationData::Current().LocalSettings().Values().HasKey(NETIF_SETTING_KEY)) {
+            MainPivot().SelectedIndex(1);
+            co_await 400ms;
+            co_await resume_foreground(Dispatcher());
+            NetifCombobox().IsDropDownOpen(true);
+            co_return;
+        }
+
+        const auto connect = ConnectionToggleSwitch().IsOn();
+        ConnectionToggleSwitch().IsEnabled(false);
+        VpnManagementErrorStatus status = VpnManagementErrorStatus::Ok;
+        if (connect) {
+            status = co_await VpnMgmtAgent.ConnectProfileAsync(m_vpnProfile);
+        }
+        else {
+            status = co_await VpnMgmtAgent.DisconnectProfileAsync(m_vpnProfile);
+        }
+        if (status == VpnManagementErrorStatus::Ok)
+        {
+            ConnectionToggleSwitch().IsEnabled(true);
+        }
+        else {
+            NotifyUser(L"Could not perform the requested operation. Please try again from system VPN settings for detailed error messages.");
+        }
+    }
+    fire_and_forget MainPage::StartConnectionCheck()
+    {
+        const auto lifetime{ get_strong() };
+        IVectorView<IVpnProfile> profiles{ nullptr };
+
+        auto event_token{ ConnectionToggleSwitch().Toggled({ this, &MainPage::ConnectionToggleSwitch_Toggled }) };
+        while (true) {
+            if (m_vpnProfile == nullptr) {
+                profiles = co_await VpnMgmtAgent.GetProfilesAsync();
+                for (auto const p : profiles) {
+                    if (p.ProfileName() == L"Maple" || p.ProfileName() == L"maple") {
+                        m_vpnProfile = p.try_as<VpnPlugInProfile>();
+                        break;
+                    }
+                }
+            }
+            if (m_vpnProfile == nullptr) {
+                ConnectionToggleSwitch().IsEnabled(false);
+            }
+            else {
+                ToolTipService::SetToolTip(ConnectionToggleSwitchContainer(), nullptr);
+                auto status = VpnManagementConnectionStatus::Disconnected;
+                try {
+                    status = m_vpnProfile.ConnectionStatus();
+                }
+                catch (...) {}
+
+                ConnectionToggleSwitch().IsEnabled(status == VpnManagementConnectionStatus::Connected
+                    || status == VpnManagementConnectionStatus::Disconnected);
+                ConnectionToggleSwitch().Toggled(event_token);
+                ConnectionToggleSwitch().IsOn(status == VpnManagementConnectionStatus::Connected
+                    || status == VpnManagementConnectionStatus::Connecting);
+                event_token = ConnectionToggleSwitch().Toggled({ this, &MainPage::ConnectionToggleSwitch_Toggled });
+            }
+            co_await 1s;
+            co_await resume_foreground(Dispatcher());
+        }
+    }
+}

+ 11 - 0
Maple.App/MainPage.h

@@ -3,15 +3,20 @@
 #include "MainPage.g.h"
 #include "MainPage.g.h"
 #include "Model/ConfigViewModel.h"
 #include "Model/ConfigViewModel.h"
 
 
+#include <winrt/Windows.Networking.Vpn.h>
+
 using namespace winrt;
 using namespace winrt;
+using namespace Windows::ApplicationModel::Core;
 using namespace Windows::ApplicationModel::DataTransfer;
 using namespace Windows::ApplicationModel::DataTransfer;
 using namespace Windows::Foundation;
 using namespace Windows::Foundation;
 using namespace Windows::Foundation::Collections;
 using namespace Windows::Foundation::Collections;
+using namespace Windows::Networking::Vpn;
 using namespace Windows::Storage;
 using namespace Windows::Storage;
 using namespace Windows::UI::Core;
 using namespace Windows::UI::Core;
 using namespace Windows::UI::Xaml;
 using namespace Windows::UI::Xaml;
 using namespace Windows::UI::Xaml::Controls;
 using namespace Windows::UI::Xaml::Controls;
 using namespace Windows::UI::Xaml::Input;
 using namespace Windows::UI::Xaml::Input;
+using namespace Windows::UI::Xaml::Navigation;
 
 
 namespace winrt::Maple_App::implementation
 namespace winrt::Maple_App::implementation
 {
 {
@@ -29,6 +34,8 @@ namespace winrt::Maple_App::implementation
         IObservableVector<Maple_App::ConfigViewModel> ConfigItems();
         IObservableVector<Maple_App::ConfigViewModel> ConfigItems();
 
 
         void Page_Loaded(IInspectable const& sender, RoutedEventArgs const& e);
         void Page_Loaded(IInspectable const& sender, RoutedEventArgs const& e);
+        void CoreTitleBar_LayoutMetricsChanged(CoreApplicationViewTitleBar const& sender, IInspectable const& args);
+        void CoreWindow_Activated(IInspectable const& sender, WindowActivatedEventArgs const& args);
         void ConfigSetAsDefaultMenuItem_Click(IInspectable const& sender, RoutedEventArgs const& e);
         void ConfigSetAsDefaultMenuItem_Click(IInspectable const& sender, RoutedEventArgs const& e);
         void ConfigRenameMenuItem_Click(IInspectable const& sender, RoutedEventArgs const& e);
         void ConfigRenameMenuItem_Click(IInspectable const& sender, RoutedEventArgs const& e);
         fire_and_forget ConfigDeleteMenuItem_Click(IInspectable const& sender, RoutedEventArgs const& e);
         fire_and_forget ConfigDeleteMenuItem_Click(IInspectable const& sender, RoutedEventArgs const& e);
@@ -47,6 +54,7 @@ namespace winrt::Maple_App::implementation
         void WindowWidth_CurrentStateChanged(IInspectable const& sender, VisualStateChangedEventArgs const& e);
         void WindowWidth_CurrentStateChanged(IInspectable const& sender, VisualStateChangedEventArgs const& e);
         void MainSplitView_PaneClosing(SplitView const& sender, SplitViewPaneClosingEventArgs const& args);
         void MainSplitView_PaneClosing(SplitView const& sender, SplitViewPaneClosingEventArgs const& args);
         fire_and_forget GenerateProfileButton_Click(IInspectable const& sender, RoutedEventArgs const& e);
         fire_and_forget GenerateProfileButton_Click(IInspectable const& sender, RoutedEventArgs const& e);
+        fire_and_forget ConnectionToggleSwitch_Toggled(IInspectable const& sender, RoutedEventArgs const& e);
 
 
     private:
     private:
         inline static DependencyProperty m_configItemsProperty =
         inline static DependencyProperty m_configItemsProperty =
@@ -64,9 +72,11 @@ namespace winrt::Maple_App::implementation
                 nullptr
                 nullptr
             );
             );
         inline static SystemNavigationManager NavigationManager{ nullptr };
         inline static SystemNavigationManager NavigationManager{ nullptr };
+        inline static VpnManagementAgent VpnMgmtAgent{};
 
 
         IStorageFolder m_configFolder{ nullptr };
         IStorageFolder m_configFolder{ nullptr };
         Maple_App::ConfigViewModel m_defaultConfig{ nullptr };
         Maple_App::ConfigViewModel m_defaultConfig{ nullptr };
+        VpnPlugInProfile m_vpnProfile{ nullptr };
 
 
         static IAsyncAction NotifyUser(const hstring& message);
         static IAsyncAction NotifyUser(const hstring& message);
         static IAsyncOperation<IStorageFolder> InitializeConfigFolder();
         static IAsyncOperation<IStorageFolder> InitializeConfigFolder();
@@ -77,6 +87,7 @@ namespace winrt::Maple_App::implementation
         void SetAsDefault(const Maple_App::ConfigViewModel& item);
         void SetAsDefault(const Maple_App::ConfigViewModel& item);
         fire_and_forget ConfirmRename();
         fire_and_forget ConfirmRename();
         IAsyncAction LoadConfigs();
         IAsyncAction LoadConfigs();
+        fire_and_forget StartConnectionCheck();
 
 
         template<ConvertableToIStorageItem T>
         template<ConvertableToIStorageItem T>
         IAsyncAction ImportFiles(const IVectorView<T>& items) {
         IAsyncAction ImportFiles(const IVectorView<T>& items) {

+ 213 - 167
Maple.App/MainPage.xaml

@@ -17,176 +17,222 @@
         <pickers:FileOpenPicker x:Key="ImportFilePicker" x:Name="ImportFilePicker"/>
         <pickers:FileOpenPicker x:Key="ImportFilePicker" x:Name="ImportFilePicker"/>
     </Page.Resources>
     </Page.Resources>
 
 
-    <SplitView x:Name="MainSplitView" DisplayMode="Overlay" PaneClosing="MainSplitView_PaneClosing">
-        <SplitView.Pane>
-            <Pivot PivotItemLoaded="MainPivot_PivotItemLoaded">
-                <PivotItem Header="Config" Margin="0">
-                    <Grid>
-                        <Grid.RowDefinitions>
-                            <RowDefinition Height="*"/>
-                            <RowDefinition Height="Auto"/>
-                        </Grid.RowDefinitions>
-                        <ListView
-                            x:Name="ConfigListView"
-                            Grid.Row="0"
-                            SelectionMode="Single"
-                            CanDragItems="True"
-                            AllowDrop="True"
-                            ItemsSource="{x:Bind ConfigItems, Mode=OneWay}"
-                            SelectionChanged="ConfigListView_SelectionChanged"
-                            DragItemsStarting="ConfigListView_DragItemsStarting"
-                            DragOver="ConfigListView_DragOver"
-                            Drop="ConfigListView_Drop">
-                            <ListView.ItemContainerStyle>
-                                <Style TargetType="ListViewItem">
-                                    <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
-                                </Style>
-                            </ListView.ItemContainerStyle>
-                            <ListView.Resources>
-                                <MenuFlyout x:Key="ItemContextFlyout">
-                                    <MenuFlyoutItem Icon="Favorite" Text="Set as Default" Click="ConfigSetAsDefaultMenuItem_Click"/>
-                                    <MenuFlyoutItem Icon="Copy" Text="Duplicate" Click="ConfigDuplicateMenuItem_Click"/>
-                                    <MenuFlyoutItem Icon="Rename" Text="Rename" Click="ConfigRenameMenuItem_Click">
-                                        <Windows10FallCreatorsUpdate:MenuFlyoutItem.KeyboardAccelerators>
-                                            <Windows10FallCreatorsUpdate:KeyboardAccelerator Key="F2" ScopeOwner="{x:Bind ConfigListView}"/>
-                                        </Windows10FallCreatorsUpdate:MenuFlyoutItem.KeyboardAccelerators>
-                                    </MenuFlyoutItem>
-                                    <MenuFlyoutItem Icon="Delete" Text="Delete" Click="ConfigDeleteMenuItem_Click">
-                                        <MenuFlyoutItem.Command>
-                                            <Windows10version1809:StandardUICommand Kind="Delete"/>
-                                        </MenuFlyoutItem.Command>
-                                    </MenuFlyoutItem>
-                                </MenuFlyout>
-                            </ListView.Resources>
-                            <ListView.ItemTemplate>
-                                <DataTemplate x:DataType="maple_app:ConfigViewModel">
-                                    <Grid
-                                        HorizontalAlignment="Stretch"
-                                        VerticalAlignment="Stretch"
-                                        ContextFlyout="{StaticResource ItemContextFlyout}"
-                                        DoubleTapped="ConfigItem_DoubleTapped">
-                                        <Grid.RowDefinitions>
-                                            <RowDefinition Height="Auto"/>
-                                            <RowDefinition Height="Auto"/>
-                                        </Grid.RowDefinitions>
-                                        <Grid.ColumnDefinitions>
-                                            <ColumnDefinition Width="0"/>
-                                            <ColumnDefinition Width="*"/>
-                                        </Grid.ColumnDefinitions>
-                                        <Rectangle
-                                            Grid.Row="0"
-                                            Grid.RowSpan="2"
-                                            Grid.Column="0"
-                                            Margin="-12, 6, 4, 6"
-                                            Fill="{ThemeResource SystemAccentColor}"
-                                            Visibility="{x:Bind IsDefault, Mode=OneWay}"/>
-                                        <TextBlock
-                                            Grid.Row="0"
-                                            Grid.Column="1"
-                                            Text="{x:Bind Name, Mode=OneWay}"/>
-                                        <TextBlock
-                                            Grid.Row="1"
-                                            Grid.Column="1"
-                                            Text="{x:Bind DateUpdated, Mode=OneWay, Converter={StaticResource DateTimeConverter}}"
-                                            Style="{ThemeResource CaptionTextBlockStyle}"
-                                            Foreground="{ThemeResource SystemControlPageTextBaseMediumBrush}"/>
-                                    </Grid>
-                                </DataTemplate>
-                            </ListView.ItemTemplate>
-                        </ListView>
-                        <CommandBar Grid.Row="1">
-                            <AppBarButton Icon="Add" Label="Add">
-                                <AppBarButton.Flyout>
-                                    <MenuFlyout Placement="Top">
-                                        <MenuFlyoutItem Text="Conf" Click="ConfigCreateMenuItem_Click"/>
-                                        <MenuFlyoutItem Text="JSON" Click="ConfigCreateMenuItem_Click"/>
-                                        <MenuFlyoutSeparator/>
-                                        <MenuFlyoutItem Text="Import…" Click="ConfigImportMenuItem_Click"/>
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto" />
+            <RowDefinition />
+        </Grid.RowDefinitions>
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition x:Name="LeftPaddingColumn" Width="0"/>
+            <ColumnDefinition />
+            <ColumnDefinition x:Name="RightPaddingColumn" Width="0"/>
+        </Grid.ColumnDefinitions>
+
+        <Grid Grid.Column="1" x:Name="CustomTitleBar" Height="32" Background="Transparent">
+            <TextBlock
+                x:Name="AppTitleTextBlock"
+                Text="Maple"
+                VerticalAlignment="Center"
+                FontSize="13"
+                Margin="12, 0, 0, 0"/>
+        </Grid>
+        <Grid
+            x:Name="ConnectionToggleSwitchContainer"
+            Grid.Column="1"
+            HorizontalAlignment="Right"
+            VerticalAlignment="Center"
+            Background="Transparent">
+            <ToolTipService.ToolTip>
+                <ToolTip x:Name="NoProfileToggleTooltip">
+                    Maple VPN profile is not ready. Please check Setting page for more information.
+                </ToolTip>
+            </ToolTipService.ToolTip>
+            <ToggleSwitch x:Name="ConnectionToggleSwitch" IsEnabled="False">
+                <ToggleSwitch.RenderTransform>
+                    <CompositeTransform ScaleX=".8" ScaleY=".8" TranslateX="90" TranslateY="3" />
+                </ToggleSwitch.RenderTransform>
+            </ToggleSwitch>
+        </Grid>
+
+        <SplitView
+            x:Name="MainSplitView"
+            RequestedTheme="Light"
+            Grid.Row="1"
+            Grid.ColumnSpan="3"
+            DisplayMode="Overlay"
+            PaneClosing="MainSplitView_PaneClosing"
+            Background="White" >
+            <SplitView.Pane>
+                <Pivot x:Name="MainPivot" PivotItemLoaded="MainPivot_PivotItemLoaded">
+                    <PivotItem Header="Config" Margin="0">
+                        <Grid>
+                            <Grid.RowDefinitions>
+                                <RowDefinition Height="*"/>
+                                <RowDefinition Height="Auto"/>
+                            </Grid.RowDefinitions>
+                            <ListView
+                                x:Name="ConfigListView"
+                                Grid.Row="0"
+                                SelectionMode="Single"
+                                CanDragItems="True"
+                                AllowDrop="True"
+                                ItemsSource="{x:Bind ConfigItems, Mode=OneWay}"
+                                SelectionChanged="ConfigListView_SelectionChanged"
+                                DragItemsStarting="ConfigListView_DragItemsStarting"
+                                DragOver="ConfigListView_DragOver"
+                                Drop="ConfigListView_Drop">
+                                <ListView.ItemContainerStyle>
+                                    <Style TargetType="ListViewItem">
+                                        <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
+                                    </Style>
+                                </ListView.ItemContainerStyle>
+                                <ListView.Resources>
+                                    <MenuFlyout x:Key="ItemContextFlyout">
+                                        <MenuFlyoutItem Icon="Favorite" Text="Set as Default" Click="ConfigSetAsDefaultMenuItem_Click"/>
+                                        <MenuFlyoutItem Icon="Copy" Text="Duplicate" Click="ConfigDuplicateMenuItem_Click"/>
+                                        <MenuFlyoutItem Icon="Rename" Text="Rename" Click="ConfigRenameMenuItem_Click">
+                                            <Windows10FallCreatorsUpdate:MenuFlyoutItem.KeyboardAccelerators>
+                                                <Windows10FallCreatorsUpdate:KeyboardAccelerator Key="F2" ScopeOwner="{x:Bind ConfigListView}"/>
+                                            </Windows10FallCreatorsUpdate:MenuFlyoutItem.KeyboardAccelerators>
+                                        </MenuFlyoutItem>
+                                        <MenuFlyoutItem Icon="Delete" Text="Delete" Click="ConfigDeleteMenuItem_Click">
+                                            <MenuFlyoutItem.Command>
+                                                <Windows10version1809:StandardUICommand Kind="Delete"/>
+                                            </MenuFlyoutItem.Command>
+                                        </MenuFlyoutItem>
                                     </MenuFlyout>
                                     </MenuFlyout>
-                                </AppBarButton.Flyout>
-                            </AppBarButton>
-                        </CommandBar>
-                        <ContentDialog
-                            x:Name="RenameDialog"
-                            Height="10"
-                            Title="Specify a new file name"
-                            PrimaryButtonText="Rename"
-                            SecondaryButtonText="Close"
-                            PrimaryButtonClick="RenameDialogPrimaryButton_Click">
-                            <ContentDialog.SecondaryButtonCommand>
-                                <Windows10version1809:StandardUICommand Kind="Close"/>
-                            </ContentDialog.SecondaryButtonCommand>
-                            <TextBox
-                                x:Name="RenameDialogText"
-                                MaxLength="100"
-                                Height="32"
-                                AcceptsReturn="False"
-                                TextWrapping="NoWrap"
-                                KeyDown="RenameDialogText_KeyDown"/>
-                        </ContentDialog>
-                    </Grid>
-                </PivotItem>
-                <PivotItem Header="Setting">
-                    <ScrollViewer>
-                        <StackPanel>
-                            <TextBlock Margin="0, 18" Text="Network Interface" Style="{ThemeResource TitleTextBlockStyle}"/>
-                            <TextBlock
-                                Text="Choose a default network interface for DNS and outbound connections"
-                                TextWrapping="WrapWholeWords"/>
-                            <ComboBox
-                                x:Name="NetifCombobox"
-                                HorizontalAlignment="Stretch"
-                                Margin="0, 8"
-                                DisplayMemberPath="Desc"
-                                SelectedValuePath="Addr"
-                                SelectionChanged="NetifCombobox_SelectionChanged"/>
+                                </ListView.Resources>
+                                <ListView.ItemTemplate>
+                                    <DataTemplate x:DataType="maple_app:ConfigViewModel">
+                                        <Grid
+                                            HorizontalAlignment="Stretch"
+                                            VerticalAlignment="Stretch"
+                                            ContextFlyout="{StaticResource ItemContextFlyout}"
+                                            DoubleTapped="ConfigItem_DoubleTapped">
+                                            <Grid.RowDefinitions>
+                                                <RowDefinition Height="Auto"/>
+                                                <RowDefinition Height="Auto"/>
+                                            </Grid.RowDefinitions>
+                                            <Grid.ColumnDefinitions>
+                                                <ColumnDefinition Width="0"/>
+                                                <ColumnDefinition Width="*"/>
+                                            </Grid.ColumnDefinitions>
+                                            <Rectangle
+                                                Grid.Row="0"
+                                                Grid.RowSpan="2"
+                                                Grid.Column="0"
+                                                Margin="-12, 6, 4, 6"
+                                                Fill="{ThemeResource SystemAccentColor}"
+                                                Visibility="{x:Bind IsDefault, Mode=OneWay}"/>
+                                            <TextBlock
+                                                Grid.Row="0"
+                                                Grid.Column="1"
+                                                Text="{x:Bind Name, Mode=OneWay}"/>
+                                            <TextBlock
+                                                Grid.Row="1"
+                                                Grid.Column="1"
+                                                Text="{x:Bind DateUpdated, Mode=OneWay, Converter={StaticResource DateTimeConverter}}"
+                                                Style="{ThemeResource CaptionTextBlockStyle}"
+                                                Foreground="{ThemeResource SystemControlPageTextBaseMediumBrush}"/>
+                                        </Grid>
+                                    </DataTemplate>
+                                </ListView.ItemTemplate>
+                            </ListView>
+                            <CommandBar Grid.Row="1">
+                                <AppBarButton Icon="Add" Label="Add">
+                                    <AppBarButton.Flyout>
+                                        <MenuFlyout Placement="Top">
+                                            <MenuFlyoutItem Text="Conf" Click="ConfigCreateMenuItem_Click"/>
+                                            <MenuFlyoutItem Text="JSON" Click="ConfigCreateMenuItem_Click"/>
+                                            <MenuFlyoutSeparator/>
+                                            <MenuFlyoutItem Text="Import…" Click="ConfigImportMenuItem_Click"/>
+                                        </MenuFlyout>
+                                    </AppBarButton.Flyout>
+                                </AppBarButton>
+                            </CommandBar>
+                            <ContentDialog
+                                x:Name="RenameDialog"
+                                Height="10"
+                                Title="Specify a new file name"
+                                PrimaryButtonText="Rename"
+                                SecondaryButtonText="Close"
+                                PrimaryButtonClick="RenameDialogPrimaryButton_Click">
+                                <ContentDialog.SecondaryButtonCommand>
+                                    <Windows10version1809:StandardUICommand Kind="Close"/>
+                                </ContentDialog.SecondaryButtonCommand>
+                                <TextBox
+                                    x:Name="RenameDialogText"
+                                    MaxLength="100"
+                                    Height="32"
+                                    AcceptsReturn="False"
+                                    TextWrapping="NoWrap"
+                                    KeyDown="RenameDialogText_KeyDown"/>
+                            </ContentDialog>
+                        </Grid>
+                    </PivotItem>
+                    <PivotItem Header="Setting">
+                        <ScrollViewer>
+                            <StackPanel>
+                                <TextBlock Margin="0, 18" Text="Network Interface" Style="{ThemeResource TitleTextBlockStyle}"/>
+                                <TextBlock
+                                    Text="Choose a default network interface for DNS and outbound connections"
+                                    TextWrapping="WrapWholeWords"/>
+                                <ComboBox
+                                    x:Name="NetifCombobox"
+                                    HorizontalAlignment="Stretch"
+                                    Margin="0, 8"
+                                    DisplayMemberPath="Desc"
+                                    SelectedValuePath="Addr"
+                                    SelectionChanged="NetifCombobox_SelectionChanged"/>
+
+                                <TextBlock Margin="0, 18" Text="VPN Connection" Style="{ThemeResource TitleTextBlockStyle}"/>
+                                <TextBlock Margin="0, 0, 0, 18" TextWrapping="WrapWholeWords">
+                                    <Run Text="Connect to Maple in the"/>
+                                    <Hyperlink TextDecorations="None" NavigateUri="ms-settings:network-vpn">
+                                        <Run Text="Windows Settings"/>
+                                    </Hyperlink>
+                                    <Run Text="app."/>
+                                </TextBlock>
+                                <TextBlock
+                                    Margin="0, 0, 0, 8"
+                                    Foreground="{ThemeResource SystemControlPageTextBaseMediumBrush}"
+                                    TextWrapping="WrapWholeWords">
+                                    <Run Text="For first time connection, create a VPN profile with provider"/>
+                                    <Run FontFamily="Consolas" Text="Maple"/>
+                                    <Run Text="and server name"/>
+                                    <Run FontFamily="Consolas" Text="maple"/>
+                                    <Run Text=". Alternatively, select &quot;Generate Profile&quot; to create one automatically (not recommended)."/>
+                                </TextBlock>
+                                <Button Content="Generate Profile" Click="GenerateProfileButton_Click"/>
 
 
-                            <TextBlock Margin="0, 18" Text="VPN Connection" Style="{ThemeResource TitleTextBlockStyle}"/>
-                            <TextBlock Margin="0, 0, 0, 18" TextWrapping="WrapWholeWords">
-                                <Run Text="Connect to Maple in the"/>
-                                <Hyperlink TextDecorations="None" NavigateUri="ms-settings:network-vpn">
-                                    <Run Text="Windows Settings"/>
-                                </Hyperlink>
-                                <Run Text="app."/>
-                            </TextBlock>
-                            <TextBlock
-                                Margin="0, 0, 0, 8"
-                                Foreground="{ThemeResource SystemControlPageTextBaseMediumBrush}"
-                                TextWrapping="WrapWholeWords">
-                                <Run Text="For first time connection, create a VPN profile with provider"/>
-                                <Run FontFamily="Consolas" Text="Maple"/>
-                                <Run Text="and server name"/>
-                                <Run FontFamily="Consolas" Text="maple"/>
-                                <Run Text=". Alternatively, select &quot;Generate Profile&quot; to create one automatically."/>
-                            </TextBlock>
-                            <Button Content="Generate Profile" Click="GenerateProfileButton_Click"/>
+                                <TextBlock Margin="0, 18" Text="About" Style="{ThemeResource TitleTextBlockStyle}"/>
+                                <HyperlinkButton Padding="0" Content="Homepage" NavigateUri="https://github.com/YtFlow/Maple"/>
+                                <HyperlinkButton Padding="0" Content="Report Issues" NavigateUri="https://github.com/YtFlow/Maple/issues"/>
+                                <HyperlinkButton Padding="0" Content="License" NavigateUri="https://github.com/YtFlow/Maple/blob/main/LICENSE"/>
 
 
-                            <TextBlock Margin="0, 18" Text="About" Style="{ThemeResource TitleTextBlockStyle}"/>
-                            <HyperlinkButton Padding="0" Content="Homepage" NavigateUri="https://github.com/YtFlow/Maple"/>
-                            <HyperlinkButton Padding="0" Content="Report Issues" NavigateUri="https://github.com/YtFlow/Maple/issues"/>
-                            <HyperlinkButton Padding="0" Content="License" NavigateUri="https://github.com/YtFlow/Maple/blob/main/LICENSE"/>
+                                <TextBlock Margin="0, 20, 0, 0" TextWrapping="WrapWholeWords">
+                                    <Run>This product contains a</Run>
+                                    <Hyperlink NavigateUri="https://github.com/YtFlow/leaf" TextDecorations="None">
+                                        <Run>modified version</Run>
+                                    </Hyperlink>
+                                    <Run>of</Run>
+                                    <Hyperlink NavigateUri="https://github.com/eycorsican/leaf" TextDecorations="None">
+                                        <Run>eycorsican/leaf</Run>
+                                    </Hyperlink>
+                                    <Run>under</Run>
+                                    <Hyperlink NavigateUri="https://github.com/eycorsican/leaf/blob/master/LICENSE" TextDecorations="None">
+                                        <Run>Apache License 2.0</Run>
+                                    </Hyperlink>
+                                    <Run>.</Run>
+                                </TextBlock>
+                            </StackPanel>
+                        </ScrollViewer>
+                    </PivotItem>
+                </Pivot>
+            </SplitView.Pane>
+            <Frame x:Name="MainContentFrame" CacheSize="0" />
+        </SplitView>
 
 
-                            <TextBlock Margin="0, 20, 0, 0" TextWrapping="WrapWholeWords">
-                                <Run>This product contains a</Run>
-                                <Hyperlink NavigateUri="https://github.com/YtFlow/leaf" TextDecorations="None">
-                                    <Run>modified version</Run>
-                                </Hyperlink>
-                                <Run>of</Run>
-                                <Hyperlink NavigateUri="https://github.com/eycorsican/leaf" TextDecorations="None">
-                                    <Run>eycorsican/leaf</Run>
-                                </Hyperlink>
-                                <Run>under</Run>
-                                <Hyperlink NavigateUri="https://github.com/eycorsican/leaf/blob/master/LICENSE" TextDecorations="None">
-                                    <Run>Apache License 2.0</Run>
-                                </Hyperlink>
-                                <Run>.</Run>
-                            </TextBlock>
-                        </StackPanel>
-                    </ScrollViewer>
-                </PivotItem>
-            </Pivot>
-        </SplitView.Pane>
-        <Frame x:Name="MainContentFrame" CacheSize="0"/>
         <VisualStateManager.VisualStateGroups>
         <VisualStateManager.VisualStateGroups>
             <VisualStateGroup CurrentStateChanged="WindowWidth_CurrentStateChanged">
             <VisualStateGroup CurrentStateChanged="WindowWidth_CurrentStateChanged">
                 <VisualState>
                 <VisualState>
@@ -200,5 +246,5 @@
                 </VisualState>
                 </VisualState>
             </VisualStateGroup>
             </VisualStateGroup>
         </VisualStateManager.VisualStateGroups>
         </VisualStateManager.VisualStateGroups>
-    </SplitView>
+    </Grid>
 </Page>
 </Page>

+ 13 - 11
README.md

@@ -5,9 +5,9 @@ A lightweight Universal Windows proxy app based on https://github.com/eycorsican
 
 
 - Comes with Leaf core:
 - Comes with Leaf core:
    - Domain name resolution with built-in DNS processor
    - Domain name resolution with built-in DNS processor
-   - `tun`/`http`/`socks`/`trojan`/`ws` chainable inbounds
+   - `tun`/`shadowsocks`/`socks`/`trojan`/`ws` chainable inbounds
    - `direct`/`drop`/`tls`/`ws`/`h2`/`shadowsocks`/`vmess`/`trojan`/`socks` chainable outbounds
    - `direct`/`drop`/`tls`/`ws`/`h2`/`shadowsocks`/`vmess`/`trojan`/`socks` chainable outbounds
-   - `failover`/`tryall`/`random`/`retry` composed outbounds
+   - `failover`/`tryall`/`static` composed outbounds
    - `amux` multiplexing
    - `amux` multiplexing
    - Rule system based on IP, GeoIP and domain name
    - Rule system based on IP, GeoIP and domain name
    - External rules from GeoIP database and V2Ray [Domain List Community](https://github.com/v2fly/domain-list-community)
    - External rules from GeoIP database and V2Ray [Domain List Community](https://github.com/v2fly/domain-list-community)
@@ -37,7 +37,7 @@ Maple as a UWP app is distributed for sideloading only. When installed, it acts
 1. Launch Maple from the Start menu.
 1. Launch Maple from the Start menu.
 2. Edit configuration. Refer to https://github.com/eycorsican/leaf/blob/master/README.zh.md for further explanation.
 2. Edit configuration. Refer to https://github.com/eycorsican/leaf/blob/master/README.zh.md for further explanation.
 3. Save the configuration file.
 3. Save the configuration file.
-4. If any `EXTERNAL` or `GEOIP` directive is used, drag external database files into `Config` area. V2Ray Domain List Community database can be fetched at https://github.com/v2ray/domain-list-community/releases/latest/download/dlc.dat . For GeoIP database, see [this script](https://github.com/eycorsican/ileaf/blob/main/misc/download_data.sh#L10) for inspiration.
+4. If any `EXTERNAL` or `GEOIP` directive is used, drag external database files into `Config` area. V2Ray Domain List Community database can be fetched at https://github.com/v2ray/domain-list-community/releases/latest/download/dlc.dat . For GeoIP database, please go to [MaxMind Developer Portal](https://dev.maxmind.com/geoip/geolite2-free-geolocation-data) and sign up for free download.
 5. Rename these databases accordingly (if applicable). By default, GeoIP database is `geo.mmdb` and V2Ray Domain List Community database is `site.dat`.
 5. Rename these databases accordingly (if applicable). By default, GeoIP database is `geo.mmdb` and V2Ray Domain List Community database is `site.dat`.
 6. Go to Setting page in Maple. Choose your network adapter such as `Ethernet` or `WLAN`.
 6. Go to Setting page in Maple. Choose your network adapter such as `Ethernet` or `WLAN`.
 7. Launch [Windows Settings](	ms-settings:network-vpn) app.
 7. Launch [Windows Settings](	ms-settings:network-vpn) app.
@@ -50,29 +50,31 @@ Maple as a UWP app is distributed for sideloading only. When installed, it acts
 
 
 ### Connect
 ### Connect
 
 
-1. Launch [Windows Settings](	ms-settings:network-vpn) app.
-2. In the VPN Settings, select **Maple**, and then Connect.  
+- Simply click the toggle button on the title bar, or
+- In Windows 11, select the battery, network, or volume icon to open the Quick Settings panel. Find **Maple** in VPN panel and connect, or
+- In Windows 10, select the Network  icon on the taskbar, and click Maple. In [Windows Settings](	ms-settings:network-vpn) app, select **Maple**, and then Connect.  
+
 *Note: Modifying the current configuration file while VPN is connected will take effect immediately.*
 *Note: Modifying the current configuration file while VPN is connected will take effect immediately.*
-3. Select Disconnect to disconnect.
 
 
 ## TODO
 ## TODO
 
 
-- VPN lifecycle management on Maple UI
+- <del>VPN lifecycle management on Maple UI</del>
 - Better editing experience
 - Better editing experience
 - Log collection (currently logs are sent to Visual Studio Output window for debugging only)
 - Log collection (currently logs are sent to Visual Studio Output window for debugging only)
 - <del>`external` entries</del>
 - <del>`external` entries</del>
 - VPN On Demand
 - VPN On Demand
 - Configurable routing entries
 - Configurable routing entries
+- IPv6 support
 
 
 ## Build
 ## Build
 
 
-To build Leaf and Maple, a Rust `nightly-x86_64-pc-windows-msvc` toolchain, Windows 10 SDK 10.0.19041 and Visual Studio 2019 with C++ Development Workflow are required. [C++/WinRT Visual Studio extension](https://marketplace.visualstudio.com/items?itemName=CppWinRTTeam.cppwinrt101804264) must be installed to generate Windows Metadata.
+To build Leaf and Maple, a Rust `nightly-x86_64-pc-windows-msvc` toolchain, Windows 10 SDK 10.0.22000 and Visual Studio 2022 with C++ Development Workflow are required. [C++/WinRT Visual Studio extension](https://marketplace.visualstudio.com/items?itemName=CppWinRTTeam.cppwinrt101804264) must be installed to generate Windows Metadata.
 
 
 1. **Recursively** clone this repository.
 1. **Recursively** clone this repository.
 2. Open a PowerShell Prompt.
 2. Open a PowerShell Prompt.
-3. Change working directory to `leaf/leaf-ffi`.
-4. `cargo build -Z build-std=std,panic_abort --target x86_64-uwp-windows-msvc`.  
-   For Release builds, use `cargo build -Z build-std=std,panic_abort --target x86_64-uwp-windows-msvc --release`.  
+3. Change working directory to `leaf`.
+4. `cargo build -p leaf-ffi -Z build-std=std,panic_abort --target x86_64-uwp-windows-msvc`.  
+   For Release builds, use `cargo build -p leaf-ffi -Z build-std=std,panic_abort --target x86_64-uwp-windows-msvc --release`.  
    See also https://github.com/eycorsican/leaf#build .
    See also https://github.com/eycorsican/leaf#build .
 5. Open `Maple.sln` in Visual Studio.
 5. Open `Maple.sln` in Visual Studio.
 6. Build Solution.
 6. Build Solution.

BIN
image/screenshot-setting1.png