diff --git a/src/App/App.csproj b/src/App/App.csproj index 3e3169265..31733a632 100644 --- a/src/App/App.csproj +++ b/src/App/App.csproj @@ -38,6 +38,9 @@ Properties\SharedAssemblyInfo.cs + + + TipPopup.xaml @@ -48,6 +51,24 @@ + + DynamicArticleItem.xaml + + + DynamicImageItem.xaml + + + DynamicForwardItem.xaml + + + DynamicPresenter.xaml + + + DynamicNotSupportItem.xaml + + + ImageViewer.xaml + LiveAreaItem.xaml @@ -67,6 +88,7 @@ ScreenshotSettingSection.xaml + CoverDownloaderView.xaml @@ -152,10 +174,6 @@ ReaderView.xaml - - - - ReplyMessageItem.xaml @@ -434,6 +452,7 @@ + @@ -583,6 +602,30 @@ MSBuild:Compile Designer + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + Designer MSBuild:Compile diff --git a/src/App/App.xaml b/src/App/App.xaml index f5a16a2b4..32ddebedc 100644 --- a/src/App/App.xaml +++ b/src/App/App.xaml @@ -40,6 +40,7 @@ + diff --git a/src/App/Controls/App/ImageViewer.xaml b/src/App/Controls/App/ImageViewer.xaml new file mode 100644 index 000000000..3f0d5101d --- /dev/null +++ b/src/App/Controls/App/ImageViewer.xaml @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App/Controls/App/ImageViewer.xaml.cs b/src/App/Controls/App/ImageViewer.xaml.cs new file mode 100644 index 000000000..368c6f0d6 --- /dev/null +++ b/src/App/Controls/App/ImageViewer.xaml.cs @@ -0,0 +1,311 @@ +// Copyright (c) Richasy. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; +using Richasy.Bili.Locator.Uwp; +using Richasy.Bili.Toolkit.Interfaces; +using Richasy.Bili.ViewModels.Uwp; +using Windows.ApplicationModel.DataTransfer; +using Windows.Storage; +using Windows.Storage.Pickers; +using Windows.Storage.Streams; +using Windows.System.UserProfile; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media.Imaging; + +namespace Richasy.Bili.App.Controls +{ + /// + /// 图片查看器. + /// + public sealed partial class ImageViewer : UserControl + { + private readonly Dictionary _images; + private int _currentIndex; + private int _currentImageHeight; + + /// + /// Initializes a new instance of the class. + /// + public ImageViewer() + { + this.InitializeComponent(); + _images = new Dictionary(); + Instance = this; + ImageUrls = new ObservableCollection(); + } + + /// + /// 实例. + /// + public static ImageViewer Instance { get; private set; } + + /// + /// 图片地址. + /// + public ObservableCollection ImageUrls { get; } + + /// + /// 加载图片. + /// + /// 图片列表. + /// 初始加载的图片索引. + /// . + public async Task LoadImagesAsync(List urls, int firstLoadImage = 0) + { + Container.Visibility = Visibility.Visible; + _images.Clear(); + ImageUrls.Clear(); + urls.ForEach(url => ImageUrls.Add(url)); + FactoryBlock.Text = 1.ToString("p00"); + await ShowImageAsync(firstLoadImage); + ImageListContainer.Visibility = urls.Count > 1 ? Visibility.Visible : Visibility.Collapsed; + } + + /// + /// 显示图片. + /// + /// 图片索引. + /// . + public async Task ShowImageAsync(int index) + { + if (index >= 0 && ImageUrls.Count > index) + { + _currentIndex = index; + if (ImageRepeater == null || !ImageRepeater.IsLoaded) + { + await Task.Delay(200); + } + + SetSelectedItem(index); + await LoadImageAsync(ImageUrls[index]); + } + } + + private async Task LoadImageAsync(string url) + { + _currentImageHeight = 0; + RotateTransform.Angle = 0; + ImageScrollViewer.ChangeView(null, null, 1f); + + if (Image.Source != null) + { + Image.Source = null; + } + + var hasCache = _images.TryGetValue(url, out var imageBytes); + + if (!hasCache) + { + using (var client = new HttpClient()) + { + imageBytes = await client.GetByteArrayAsync(url); + _images.Add(url, imageBytes); + } + } + + var bitmapImage = new BitmapImage(); + Image.Source = bitmapImage; + var stream = new MemoryStream(imageBytes); + await bitmapImage.SetSourceAsync(stream.AsRandomAccessStream()); + _currentImageHeight = bitmapImage.PixelHeight; + + UpdateLayout(); + var factor = ImageScrollViewer.ViewportHeight / _currentImageHeight; + if (factor > 1) + { + factor = 1; + } + + ImageScrollViewer.ChangeView(null, null, (float)factor); + } + + private void CheckButtonStatus() + { + ZoomOutButton.IsEnabled = ImageScrollViewer.ZoomFactor > 0.2; + ZoomInButton.IsEnabled = ImageScrollViewer.ZoomFactor < 1.5; + } + + private void SetSelectedItem(int index) + { + if (ImageUrls.Count <= 1) + { + return; + } + + for (var i = 0; i < ImageUrls.Count; i++) + { + var element = ImageRepeater.GetOrCreateElement(i); + if (element is CardPanel panel) + { + var url = panel.DataContext as string; + panel.IsEnableCheck = true; + panel.IsChecked = url == ImageUrls[index]; + panel.IsEnableCheck = false; + } + } + } + + private void OnScrollViewerTapped(object sender, TappedRoutedEventArgs e) + { + // 关闭控件. + _images.Clear(); + ImageUrls.Clear(); + _currentIndex = 0; + Image.Source = null; + Container.Visibility = Visibility.Collapsed; + AppViewModel.Instance.ShowImages(null, -1); + } + + private void OnScrollViewerViewChanged(object sender, ScrollViewerViewChangedEventArgs e) + { + if (e.IsIntermediate) + { + FactoryBlock.Text = ImageScrollViewer.ZoomFactor.ToString("p00"); + CheckButtonStatus(); + } + } + + private void OnRotateButtonClick(object sender, Windows.UI.Xaml.RoutedEventArgs e) + => RotateTransform.Angle += 90; + + private void OnZoomOutButtonClick(object sender, Windows.UI.Xaml.RoutedEventArgs e) + { + if (_currentImageHeight == 0) + { + return; + } + + ImageScrollViewer.ChangeView(null, null, ImageScrollViewer.ZoomFactor - 0.1f); + } + + private void OnZoomInButtonClick(object sender, Windows.UI.Xaml.RoutedEventArgs e) + { + if (_currentImageHeight == 0) + { + return; + } + + ImageScrollViewer.ChangeView(null, null, ImageScrollViewer.ZoomFactor + 0.1f); + } + + private async void OnImageItemClickAsync(object sender, RoutedEventArgs e) + { + var imageUrl = (sender as FrameworkElement).DataContext as string; + var index = ImageUrls.IndexOf(imageUrl); + await ShowImageAsync(index); + } + + private async void OnNextButtonClickAsync(object sender, RoutedEventArgs e) + { + if (ImageUrls.Count - 1 <= _currentIndex) + { + return; + } + + await ShowImageAsync(_currentIndex + 1); + } + + private async void OnPrevButtonClickAsync(object sender, RoutedEventArgs e) + { + if (_currentIndex <= 0) + { + return; + } + + await ShowImageAsync(_currentIndex - 1); + } + + private void OnCopyButtonClickAysnc(object sender, RoutedEventArgs e) + { + if (_currentIndex < 0 || _currentIndex > ImageUrls.Count - 1) + { + return; + } + + var url = ImageUrls[_currentIndex]; + var dp = new DataPackage(); + dp.SetBitmap(RandomAccessStreamReference.CreateFromUri(new Uri(url))); + Clipboard.SetContent(dp); + var resourceToolkit = ServiceLocator.Instance.GetService(); + AppViewModel.Instance.ShowTip(resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.Copied), Models.Enums.App.InfoType.Success); + } + + private async void OnSaveButtonClickAsync(object sender, RoutedEventArgs e) + { + if (_currentIndex < 0 || _currentIndex > ImageUrls.Count - 1) + { + return; + } + + var url = ImageUrls[_currentIndex]; + var hasCache = _images.TryGetValue(url, out var cache); + if (!hasCache) + { + return; + } + + var savePicker = new FileSavePicker(); + savePicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary; + var fileName = Path.GetFileName(url); + var extension = Path.GetExtension(url); + savePicker.FileTypeChoices.Add($"{extension.TrimStart('.').ToUpper()} 图片", new string[] { extension }); + savePicker.SuggestedFileName = fileName; + var file = await savePicker.PickSaveFileAsync(); + if (file != null) + { + await FileIO.WriteBytesAsync(file, cache); + var resourceToolkit = ServiceLocator.Instance.GetService(); + AppViewModel.Instance.ShowTip(resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.Saved), Models.Enums.App.InfoType.Success); + } + } + + private async void OnSettingToBackgroundClickAsync(object sender, RoutedEventArgs e) + => await SetWallpaperOrLockScreenAsync(true); + + private async void OnSettingToLockScreenClickAsync(object sender, RoutedEventArgs e) + => await SetWallpaperOrLockScreenAsync(false); + + private async Task SetWallpaperOrLockScreenAsync(bool isWallpaper) + { + if (_currentIndex < 0 || _currentIndex > ImageUrls.Count - 1) + { + return; + } + + var url = ImageUrls[_currentIndex]; + var hasCache = _images.TryGetValue(url, out var cache); + if (!hasCache) + { + return; + } + + var profileSettings = UserProfilePersonalizationSettings.Current; + var fileName = Path.GetFileName(url); + var file = await ApplicationData.Current.LocalFolder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting); + await FileIO.WriteBytesAsync(file, cache); + var result = isWallpaper + ? await profileSettings.TrySetWallpaperImageAsync(file).AsTask() + : await profileSettings.TrySetLockScreenImageAsync(file).AsTask(); + + var resourceToolkit = ServiceLocator.Instance.GetService(); + if (result) + { + AppViewModel.Instance.ShowTip(resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.SetSuccess), Models.Enums.App.InfoType.Success); + } + else + { + AppViewModel.Instance.ShowTip(resourceToolkit.GetLocaleString(Models.Enums.LanguageNames.SetFailed), Models.Enums.App.InfoType.Error); + } + + await Task.Delay(1000); + await file.DeleteAsync(); + } + } +} diff --git a/src/App/Controls/App/RootNavigationView.xaml b/src/App/Controls/App/RootNavigationView.xaml index dc2717046..d3b160c0a 100644 --- a/src/App/Controls/App/RootNavigationView.xaml +++ b/src/App/Controls/App/RootNavigationView.xaml @@ -137,13 +137,29 @@ ColumnSpacing="12" Visibility="{x:Bind ViewModel.IsShowOverlay, Mode=OneWay, Converter={StaticResource BoolToVisibilityReverseConverter}}"> + + + + + + + + +