2449 lines
91 KiB
C#
2449 lines
91 KiB
C#
using CommunityToolkit.Maui.Views;
|
|
using MarketAlly.Replicate.Maui;
|
|
using MarketAlly.Replicate.Maui.Localization;
|
|
|
|
namespace MarketAlly.Replicate.Maui.Controls;
|
|
|
|
/// <summary>
|
|
/// Layout mode for the transformer view.
|
|
/// </summary>
|
|
public enum TransformerLayoutMode
|
|
{
|
|
/// <summary>
|
|
/// Image only, no buttons shown. Use for custom button implementations.
|
|
/// </summary>
|
|
ImageOnly,
|
|
|
|
/// <summary>
|
|
/// Buttons displayed in rows below the image (default).
|
|
/// </summary>
|
|
ButtonsBelow,
|
|
|
|
/// <summary>
|
|
/// Two flush buttons overlaid at the bottom of the image (50% width each).
|
|
/// </summary>
|
|
ButtonsOverlay,
|
|
|
|
/// <summary>
|
|
/// Source image on left, result image on right, with buttons below.
|
|
/// </summary>
|
|
SideBySide
|
|
}
|
|
|
|
public partial class ReplicateTransformerView : ContentView, IDisposable
|
|
{
|
|
private byte[]? _currentImageBytes;
|
|
private IReplicateTransformer? _transformer;
|
|
private IReplicateTransformerFactory? _transformerFactory;
|
|
private CancellationTokenSource? _currentTransformationCts;
|
|
private string? _currentPredictionId;
|
|
private IPredictionTracker? _tracker;
|
|
private bool _disposed;
|
|
private bool _trackPredictions = true;
|
|
private ModelPreset? _imagePreset;
|
|
private ModelPreset? _videoPreset;
|
|
private Dictionary<string, object>? _customImageParameters;
|
|
private Dictionary<string, object>? _customVideoParameters;
|
|
private TransformerLayoutMode _layoutMode = TransformerLayoutMode.ButtonsBelow;
|
|
private bool _isShowingVideo;
|
|
private string? _videoUrl;
|
|
private TransformationType _lastTransformationType = TransformationType.Image;
|
|
private string? _lastSavedImagePath;
|
|
private string? _lastSavedVideoPath;
|
|
private TransformerState _currentState = TransformerState.Empty;
|
|
private Services.IFileSaveService? _fileSaveService;
|
|
|
|
#region Bindable Properties
|
|
|
|
public static readonly BindableProperty LayoutModeProperty =
|
|
BindableProperty.Create(nameof(LayoutMode), typeof(TransformerLayoutMode), typeof(ReplicateTransformerView),
|
|
TransformerLayoutMode.ButtonsBelow, propertyChanged: OnLayoutModeChanged);
|
|
|
|
public static readonly BindableProperty ShowCaptureButtonsProperty =
|
|
BindableProperty.Create(nameof(ShowCaptureButtons), typeof(bool), typeof(ReplicateTransformerView), true,
|
|
propertyChanged: OnShowCaptureButtonsChanged);
|
|
|
|
public static readonly BindableProperty ShowVideoOptionsProperty =
|
|
BindableProperty.Create(nameof(ShowVideoOptions), typeof(bool), typeof(ReplicateTransformerView), true,
|
|
propertyChanged: OnShowVideoOptionsChanged);
|
|
|
|
public static readonly BindableProperty AnimeButtonTextProperty =
|
|
BindableProperty.Create(nameof(AnimeButtonText), typeof(string), typeof(ReplicateTransformerView), "Transform to Anime",
|
|
propertyChanged: OnAnimeButtonTextChanged);
|
|
|
|
public static readonly BindableProperty VideoButtonTextProperty =
|
|
BindableProperty.Create(nameof(VideoButtonText), typeof(string), typeof(ReplicateTransformerView), "Generate Video",
|
|
propertyChanged: OnVideoButtonTextChanged);
|
|
|
|
public static readonly BindableProperty PlaceholderTextProperty =
|
|
BindableProperty.Create(nameof(PlaceholderText), typeof(string), typeof(ReplicateTransformerView), "Select or capture an image",
|
|
propertyChanged: OnPlaceholderTextChanged);
|
|
|
|
public static readonly BindableProperty OverlaySelectButtonTextProperty =
|
|
BindableProperty.Create(nameof(OverlaySelectButtonText), typeof(string), typeof(ReplicateTransformerView), "🖼 Select",
|
|
propertyChanged: OnOverlaySelectButtonTextChanged);
|
|
|
|
public static readonly BindableProperty OverlayTransformButtonTextProperty =
|
|
BindableProperty.Create(nameof(OverlayTransformButtonText), typeof(string), typeof(ReplicateTransformerView), "✨ Transform",
|
|
propertyChanged: OnOverlayTransformButtonTextChanged);
|
|
|
|
public static readonly BindableProperty CustomImagePromptProperty =
|
|
BindableProperty.Create(nameof(CustomImagePrompt), typeof(string), typeof(ReplicateTransformerView), null);
|
|
|
|
public static readonly BindableProperty CustomVideoPromptProperty =
|
|
BindableProperty.Create(nameof(CustomVideoPrompt), typeof(string), typeof(ReplicateTransformerView), null);
|
|
|
|
public static readonly BindableProperty IsProcessingProperty =
|
|
BindableProperty.Create(nameof(IsProcessing), typeof(bool), typeof(ReplicateTransformerView), false);
|
|
|
|
public static readonly BindableProperty TrackPredictionsProperty =
|
|
BindableProperty.Create(nameof(TrackPredictions), typeof(bool), typeof(ReplicateTransformerView), true,
|
|
propertyChanged: OnTrackPredictionsChanged);
|
|
|
|
public static readonly BindableProperty UseLocalizationProperty =
|
|
BindableProperty.Create(nameof(UseLocalization), typeof(bool), typeof(ReplicateTransformerView), false,
|
|
propertyChanged: OnUseLocalizationChanged);
|
|
|
|
public static readonly BindableProperty ResultUrlProperty =
|
|
BindableProperty.Create(nameof(ResultUrl), typeof(string), typeof(ReplicateTransformerView), null);
|
|
|
|
public static readonly BindableProperty SourceImageProperty =
|
|
BindableProperty.Create(nameof(SourceImageSource), typeof(ImageSource), typeof(ReplicateTransformerView), null);
|
|
|
|
public static readonly BindableProperty OverlayTransformTypeProperty =
|
|
BindableProperty.Create(nameof(OverlayTransformType), typeof(TransformationType), typeof(ReplicateTransformerView), TransformationType.Image);
|
|
|
|
public static readonly BindableProperty ShowSideBySideSourceButtonsProperty =
|
|
BindableProperty.Create(nameof(ShowSideBySideSourceButtons), typeof(bool), typeof(ReplicateTransformerView), true,
|
|
propertyChanged: OnShowSideBySideSourceButtonsChanged);
|
|
|
|
public static readonly BindableProperty ShowSideBySideResultButtonsProperty =
|
|
BindableProperty.Create(nameof(ShowSideBySideResultButtons), typeof(bool), typeof(ReplicateTransformerView), true,
|
|
propertyChanged: OnShowSideBySideResultButtonsChanged);
|
|
|
|
public static readonly BindableProperty SideBySideCaptureButtonTextProperty =
|
|
BindableProperty.Create(nameof(SideBySideCaptureButtonText), typeof(string), typeof(ReplicateTransformerView), "📸 Take",
|
|
propertyChanged: OnSideBySideCaptureButtonTextChanged);
|
|
|
|
public static readonly BindableProperty SideBySidePickButtonTextProperty =
|
|
BindableProperty.Create(nameof(SideBySidePickButtonText), typeof(string), typeof(ReplicateTransformerView), "🖼 Pick",
|
|
propertyChanged: OnSideBySidePickButtonTextChanged);
|
|
|
|
public static readonly BindableProperty SideBySideClearButtonTextProperty =
|
|
BindableProperty.Create(nameof(SideBySideClearButtonText), typeof(string), typeof(ReplicateTransformerView), "🗑 Clear",
|
|
propertyChanged: OnSideBySideClearButtonTextChanged);
|
|
|
|
public static readonly BindableProperty SideBySideRedoButtonTextProperty =
|
|
BindableProperty.Create(nameof(SideBySideRedoButtonText), typeof(string), typeof(ReplicateTransformerView), "↺ Redo",
|
|
propertyChanged: OnSideBySideRedoButtonTextChanged);
|
|
|
|
// ============================================
|
|
// CONSOLIDATED BUTTON CONFIGURATION
|
|
// ============================================
|
|
|
|
/// <summary>
|
|
/// Consolidated button configurations for all transformer buttons.
|
|
/// Provides a cleaner API for customizing button appearance.
|
|
/// </summary>
|
|
public static readonly BindableProperty ButtonConfigsProperty =
|
|
BindableProperty.Create(nameof(ButtonConfigs), typeof(TransformerButtonConfigs), typeof(ReplicateTransformerView),
|
|
null, propertyChanged: OnButtonConfigsChanged);
|
|
|
|
// ============================================
|
|
// BUTTON DISPLAY MODE AND ICON PROPERTIES (Legacy - use ButtonConfigs instead)
|
|
// ============================================
|
|
|
|
// Global display mode (affects all buttons unless overridden)
|
|
public static readonly BindableProperty DefaultButtonDisplayModeProperty =
|
|
BindableProperty.Create(nameof(DefaultButtonDisplayMode), typeof(ButtonDisplayMode), typeof(ReplicateTransformerView),
|
|
ButtonDisplayMode.Both, propertyChanged: OnButtonDisplayModeChanged);
|
|
|
|
// Overlay buttons - Select
|
|
public static readonly BindableProperty OverlaySelectIconTextProperty =
|
|
BindableProperty.Create(nameof(OverlaySelectIconText), typeof(string), typeof(ReplicateTransformerView), "🖼",
|
|
propertyChanged: OnOverlaySelectAppearanceChanged);
|
|
|
|
public static readonly BindableProperty OverlaySelectIconSourceProperty =
|
|
BindableProperty.Create(nameof(OverlaySelectIconSource), typeof(ImageSource), typeof(ReplicateTransformerView), null,
|
|
propertyChanged: OnOverlaySelectAppearanceChanged);
|
|
|
|
public static readonly BindableProperty OverlaySelectLabelProperty =
|
|
BindableProperty.Create(nameof(OverlaySelectLabel), typeof(string), typeof(ReplicateTransformerView), "Select",
|
|
propertyChanged: OnOverlaySelectAppearanceChanged);
|
|
|
|
public static readonly BindableProperty OverlaySelectDisplayModeProperty =
|
|
BindableProperty.Create(nameof(OverlaySelectDisplayMode), typeof(ButtonDisplayMode?), typeof(ReplicateTransformerView), null,
|
|
propertyChanged: OnOverlaySelectAppearanceChanged);
|
|
|
|
// Overlay buttons - Transform
|
|
public static readonly BindableProperty OverlayTransformIconTextProperty =
|
|
BindableProperty.Create(nameof(OverlayTransformIconText), typeof(string), typeof(ReplicateTransformerView), "✨",
|
|
propertyChanged: OnOverlayTransformAppearanceChanged);
|
|
|
|
public static readonly BindableProperty OverlayTransformIconSourceProperty =
|
|
BindableProperty.Create(nameof(OverlayTransformIconSource), typeof(ImageSource), typeof(ReplicateTransformerView), null,
|
|
propertyChanged: OnOverlayTransformAppearanceChanged);
|
|
|
|
public static readonly BindableProperty OverlayTransformLabelProperty =
|
|
BindableProperty.Create(nameof(OverlayTransformLabel), typeof(string), typeof(ReplicateTransformerView), "Transform",
|
|
propertyChanged: OnOverlayTransformAppearanceChanged);
|
|
|
|
public static readonly BindableProperty OverlayTransformDisplayModeProperty =
|
|
BindableProperty.Create(nameof(OverlayTransformDisplayMode), typeof(ButtonDisplayMode?), typeof(ReplicateTransformerView), null,
|
|
propertyChanged: OnOverlayTransformAppearanceChanged);
|
|
|
|
// Overlay button position
|
|
public static readonly BindableProperty OverlayButtonPositionProperty =
|
|
BindableProperty.Create(nameof(OverlayButtonPosition), typeof(OverlayButtonPosition), typeof(ReplicateTransformerView),
|
|
OverlayButtonPosition.Bottom, propertyChanged: OnOverlayButtonPositionChanged);
|
|
|
|
// SideBySide - Capture button
|
|
public static readonly BindableProperty SideBySideCaptureIconTextProperty =
|
|
BindableProperty.Create(nameof(SideBySideCaptureIconText), typeof(string), typeof(ReplicateTransformerView), "📸",
|
|
propertyChanged: OnSideBySideCaptureAppearanceChanged);
|
|
|
|
public static readonly BindableProperty SideBySideCaptureIconSourceProperty =
|
|
BindableProperty.Create(nameof(SideBySideCaptureIconSource), typeof(ImageSource), typeof(ReplicateTransformerView), null,
|
|
propertyChanged: OnSideBySideCaptureAppearanceChanged);
|
|
|
|
public static readonly BindableProperty SideBySideCaptureLabelProperty =
|
|
BindableProperty.Create(nameof(SideBySideCaptureLabel), typeof(string), typeof(ReplicateTransformerView), "Take",
|
|
propertyChanged: OnSideBySideCaptureAppearanceChanged);
|
|
|
|
public static readonly BindableProperty SideBySideCaptureDisplayModeProperty =
|
|
BindableProperty.Create(nameof(SideBySideCaptureDisplayMode), typeof(ButtonDisplayMode?), typeof(ReplicateTransformerView), null,
|
|
propertyChanged: OnSideBySideCaptureAppearanceChanged);
|
|
|
|
// SideBySide - Pick button
|
|
public static readonly BindableProperty SideBySidePickIconTextProperty =
|
|
BindableProperty.Create(nameof(SideBySidePickIconText), typeof(string), typeof(ReplicateTransformerView), "🖼",
|
|
propertyChanged: OnSideBySidePickAppearanceChanged);
|
|
|
|
public static readonly BindableProperty SideBySidePickIconSourceProperty =
|
|
BindableProperty.Create(nameof(SideBySidePickIconSource), typeof(ImageSource), typeof(ReplicateTransformerView), null,
|
|
propertyChanged: OnSideBySidePickAppearanceChanged);
|
|
|
|
public static readonly BindableProperty SideBySidePickLabelProperty =
|
|
BindableProperty.Create(nameof(SideBySidePickLabel), typeof(string), typeof(ReplicateTransformerView), "Pick",
|
|
propertyChanged: OnSideBySidePickAppearanceChanged);
|
|
|
|
public static readonly BindableProperty SideBySidePickDisplayModeProperty =
|
|
BindableProperty.Create(nameof(SideBySidePickDisplayMode), typeof(ButtonDisplayMode?), typeof(ReplicateTransformerView), null,
|
|
propertyChanged: OnSideBySidePickAppearanceChanged);
|
|
|
|
// SideBySide - Clear button
|
|
public static readonly BindableProperty SideBySideClearIconTextProperty =
|
|
BindableProperty.Create(nameof(SideBySideClearIconText), typeof(string), typeof(ReplicateTransformerView), "🗑",
|
|
propertyChanged: OnSideBySideClearAppearanceChanged);
|
|
|
|
public static readonly BindableProperty SideBySideClearIconSourceProperty =
|
|
BindableProperty.Create(nameof(SideBySideClearIconSource), typeof(ImageSource), typeof(ReplicateTransformerView), null,
|
|
propertyChanged: OnSideBySideClearAppearanceChanged);
|
|
|
|
public static readonly BindableProperty SideBySideClearLabelProperty =
|
|
BindableProperty.Create(nameof(SideBySideClearLabel), typeof(string), typeof(ReplicateTransformerView), "Clear",
|
|
propertyChanged: OnSideBySideClearAppearanceChanged);
|
|
|
|
public static readonly BindableProperty SideBySideClearDisplayModeProperty =
|
|
BindableProperty.Create(nameof(SideBySideClearDisplayMode), typeof(ButtonDisplayMode?), typeof(ReplicateTransformerView), null,
|
|
propertyChanged: OnSideBySideClearAppearanceChanged);
|
|
|
|
// SideBySide - Redo button
|
|
public static readonly BindableProperty SideBySideRedoIconTextProperty =
|
|
BindableProperty.Create(nameof(SideBySideRedoIconText), typeof(string), typeof(ReplicateTransformerView), "↺",
|
|
propertyChanged: OnSideBySideRedoAppearanceChanged);
|
|
|
|
public static readonly BindableProperty SideBySideRedoIconSourceProperty =
|
|
BindableProperty.Create(nameof(SideBySideRedoIconSource), typeof(ImageSource), typeof(ReplicateTransformerView), null,
|
|
propertyChanged: OnSideBySideRedoAppearanceChanged);
|
|
|
|
public static readonly BindableProperty SideBySideRedoLabelProperty =
|
|
BindableProperty.Create(nameof(SideBySideRedoLabel), typeof(string), typeof(ReplicateTransformerView), "Redo",
|
|
propertyChanged: OnSideBySideRedoAppearanceChanged);
|
|
|
|
public static readonly BindableProperty SideBySideRedoDisplayModeProperty =
|
|
BindableProperty.Create(nameof(SideBySideRedoDisplayMode), typeof(ButtonDisplayMode?), typeof(ReplicateTransformerView), null,
|
|
propertyChanged: OnSideBySideRedoAppearanceChanged);
|
|
|
|
// SideBySide - Transform button
|
|
public static readonly BindableProperty SideBySideTransformIconTextProperty =
|
|
BindableProperty.Create(nameof(SideBySideTransformIconText), typeof(string), typeof(ReplicateTransformerView), "✨",
|
|
propertyChanged: OnSideBySideTransformAppearanceChanged);
|
|
|
|
public static readonly BindableProperty SideBySideTransformIconSourceProperty =
|
|
BindableProperty.Create(nameof(SideBySideTransformIconSource), typeof(ImageSource), typeof(ReplicateTransformerView), null,
|
|
propertyChanged: OnSideBySideTransformAppearanceChanged);
|
|
|
|
public static readonly BindableProperty SideBySideTransformLabelProperty =
|
|
BindableProperty.Create(nameof(SideBySideTransformLabel), typeof(string), typeof(ReplicateTransformerView), "Transform",
|
|
propertyChanged: OnSideBySideTransformAppearanceChanged);
|
|
|
|
public static readonly BindableProperty SideBySideTransformDisplayModeProperty =
|
|
BindableProperty.Create(nameof(SideBySideTransformDisplayMode), typeof(ButtonDisplayMode?), typeof(ReplicateTransformerView), null,
|
|
propertyChanged: OnSideBySideTransformAppearanceChanged);
|
|
|
|
public static readonly BindableProperty ShowSideBySideTransformButtonProperty =
|
|
BindableProperty.Create(nameof(ShowSideBySideTransformButton), typeof(bool), typeof(ReplicateTransformerView), true,
|
|
propertyChanged: OnShowSideBySideTransformButtonChanged);
|
|
|
|
public static readonly BindableProperty SideBySideTransformTypeProperty =
|
|
BindableProperty.Create(nameof(SideBySideTransformType), typeof(TransformationType), typeof(ReplicateTransformerView), TransformationType.Image);
|
|
|
|
public static readonly BindableProperty AutoTransformOnSelectProperty =
|
|
BindableProperty.Create(nameof(AutoTransformOnSelect), typeof(bool), typeof(ReplicateTransformerView), false);
|
|
|
|
public static readonly BindableProperty AutoTransformTypeProperty =
|
|
BindableProperty.Create(nameof(AutoTransformType), typeof(TransformationType), typeof(ReplicateTransformerView), TransformationType.Image);
|
|
|
|
// ============================================
|
|
// AUTO-SAVE PROPERTIES
|
|
// ============================================
|
|
|
|
public static readonly BindableProperty AutoSaveImageProperty =
|
|
BindableProperty.Create(nameof(AutoSaveImage), typeof(bool), typeof(ReplicateTransformerView), false);
|
|
|
|
public static readonly BindableProperty AutoSaveVideoProperty =
|
|
BindableProperty.Create(nameof(AutoSaveVideo), typeof(bool), typeof(ReplicateTransformerView), false);
|
|
|
|
public static readonly BindableProperty ImageSavePathProperty =
|
|
BindableProperty.Create(nameof(ImageSavePath), typeof(string), typeof(ReplicateTransformerView), null);
|
|
|
|
public static readonly BindableProperty VideoSavePathProperty =
|
|
BindableProperty.Create(nameof(VideoSavePath), typeof(string), typeof(ReplicateTransformerView), null);
|
|
|
|
public static readonly BindableProperty ImageFilenamePatternProperty =
|
|
BindableProperty.Create(nameof(ImageFilenamePattern), typeof(string), typeof(ReplicateTransformerView), "image_{timestamp}");
|
|
|
|
public static readonly BindableProperty VideoFilenamePatternProperty =
|
|
BindableProperty.Create(nameof(VideoFilenamePattern), typeof(string), typeof(ReplicateTransformerView), "video_{timestamp}");
|
|
|
|
/// <summary>
|
|
/// The layout mode for the control.
|
|
/// </summary>
|
|
public TransformerLayoutMode LayoutMode
|
|
{
|
|
get => (TransformerLayoutMode)GetValue(LayoutModeProperty);
|
|
set => SetValue(LayoutModeProperty, value);
|
|
}
|
|
|
|
public bool ShowCaptureButtons
|
|
{
|
|
get => (bool)GetValue(ShowCaptureButtonsProperty);
|
|
set => SetValue(ShowCaptureButtonsProperty, value);
|
|
}
|
|
|
|
public bool ShowVideoOptions
|
|
{
|
|
get => (bool)GetValue(ShowVideoOptionsProperty);
|
|
set => SetValue(ShowVideoOptionsProperty, value);
|
|
}
|
|
|
|
public string AnimeButtonText
|
|
{
|
|
get => (string)GetValue(AnimeButtonTextProperty);
|
|
set => SetValue(AnimeButtonTextProperty, value);
|
|
}
|
|
|
|
public string VideoButtonText
|
|
{
|
|
get => (string)GetValue(VideoButtonTextProperty);
|
|
set => SetValue(VideoButtonTextProperty, value);
|
|
}
|
|
|
|
public string PlaceholderText
|
|
{
|
|
get => (string)GetValue(PlaceholderTextProperty);
|
|
set => SetValue(PlaceholderTextProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Text for the select button in overlay mode.
|
|
/// </summary>
|
|
public string OverlaySelectButtonText
|
|
{
|
|
get => (string)GetValue(OverlaySelectButtonTextProperty);
|
|
set => SetValue(OverlaySelectButtonTextProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Text for the transform button in overlay mode.
|
|
/// </summary>
|
|
public string OverlayTransformButtonText
|
|
{
|
|
get => (string)GetValue(OverlayTransformButtonTextProperty);
|
|
set => SetValue(OverlayTransformButtonTextProperty, value);
|
|
}
|
|
|
|
public string? CustomImagePrompt
|
|
{
|
|
get => (string?)GetValue(CustomImagePromptProperty);
|
|
set => SetValue(CustomImagePromptProperty, value);
|
|
}
|
|
|
|
public string? CustomVideoPrompt
|
|
{
|
|
get => (string?)GetValue(CustomVideoPromptProperty);
|
|
set => SetValue(CustomVideoPromptProperty, value);
|
|
}
|
|
|
|
public bool IsProcessing
|
|
{
|
|
get => (bool)GetValue(IsProcessingProperty);
|
|
private set => SetValue(IsProcessingProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enable or disable prediction tracking with history.
|
|
/// </summary>
|
|
public bool TrackPredictions
|
|
{
|
|
get => (bool)GetValue(TrackPredictionsProperty);
|
|
set => SetValue(TrackPredictionsProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enable localization for button labels and status messages.
|
|
/// When enabled, uses ReplicateStrings for all UI text based on CurrentCulture.
|
|
/// </summary>
|
|
public bool UseLocalization
|
|
{
|
|
get => (bool)GetValue(UseLocalizationProperty);
|
|
set => SetValue(UseLocalizationProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the prediction tracker for accessing history and events.
|
|
/// Returns null if transformer hasn't been initialized or tracking is disabled.
|
|
/// </summary>
|
|
public IPredictionTracker? Tracker => _tracker;
|
|
|
|
public string? ResultUrl
|
|
{
|
|
get => (string?)GetValue(ResultUrlProperty);
|
|
private set => SetValue(ResultUrlProperty, value);
|
|
}
|
|
|
|
public ImageSource? SourceImageSource
|
|
{
|
|
get => (ImageSource?)GetValue(SourceImageProperty);
|
|
private set => SetValue(SourceImageProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The type of transformation to perform when the overlay transform button is clicked.
|
|
/// Default is Image. Set to Video for video generation pages.
|
|
/// </summary>
|
|
public TransformationType OverlayTransformType
|
|
{
|
|
get => (TransformationType)GetValue(OverlayTransformTypeProperty);
|
|
set => SetValue(OverlayTransformTypeProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Show or hide the source side buttons (Take/Pick) in SideBySide layout.
|
|
/// </summary>
|
|
public bool ShowSideBySideSourceButtons
|
|
{
|
|
get => (bool)GetValue(ShowSideBySideSourceButtonsProperty);
|
|
set => SetValue(ShowSideBySideSourceButtonsProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Show or hide the result side buttons (Clear/Redo) in SideBySide layout.
|
|
/// </summary>
|
|
public bool ShowSideBySideResultButtons
|
|
{
|
|
get => (bool)GetValue(ShowSideBySideResultButtonsProperty);
|
|
set => SetValue(ShowSideBySideResultButtonsProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Text for the capture button in SideBySide layout.
|
|
/// </summary>
|
|
public string SideBySideCaptureButtonText
|
|
{
|
|
get => (string)GetValue(SideBySideCaptureButtonTextProperty);
|
|
set => SetValue(SideBySideCaptureButtonTextProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Text for the pick button in SideBySide layout.
|
|
/// </summary>
|
|
public string SideBySidePickButtonText
|
|
{
|
|
get => (string)GetValue(SideBySidePickButtonTextProperty);
|
|
set => SetValue(SideBySidePickButtonTextProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Text for the clear button in SideBySide layout.
|
|
/// </summary>
|
|
public string SideBySideClearButtonText
|
|
{
|
|
get => (string)GetValue(SideBySideClearButtonTextProperty);
|
|
set => SetValue(SideBySideClearButtonTextProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Text for the redo button in SideBySide layout.
|
|
/// </summary>
|
|
public string SideBySideRedoButtonText
|
|
{
|
|
get => (string)GetValue(SideBySideRedoButtonTextProperty);
|
|
set => SetValue(SideBySideRedoButtonTextProperty, value);
|
|
}
|
|
|
|
// ============================================
|
|
// CONSOLIDATED BUTTON CONFIGURATION ACCESSOR
|
|
// ============================================
|
|
|
|
/// <summary>
|
|
/// Consolidated button configurations for all transformer buttons.
|
|
/// Provides a cleaner API for customizing button appearance.
|
|
/// Use this instead of individual button properties for a cleaner API.
|
|
/// </summary>
|
|
/// <example>
|
|
/// <code>
|
|
/// // XAML usage:
|
|
/// <replicate:ReplicateTransformerView>
|
|
/// <replicate:ReplicateTransformerView.ButtonConfigs>
|
|
/// <replicate:TransformerButtonConfigs DefaultDisplayMode="Icon">
|
|
/// <replicate:TransformerButtonConfigs.OverlaySelect>
|
|
/// <replicate:ButtonConfig Text="Select" IconText="🖼"/>
|
|
/// </replicate:TransformerButtonConfigs.OverlaySelect>
|
|
/// </replicate:TransformerButtonConfigs>
|
|
/// </replicate:ReplicateTransformerView.ButtonConfigs>
|
|
/// </replicate:ReplicateTransformerView>
|
|
///
|
|
/// // Code-behind usage:
|
|
/// transformerView.ButtonConfigs = TransformerButtonConfigs.CreateDefault();
|
|
/// transformerView.ButtonConfigs.DefaultDisplayMode = ButtonDisplayMode.Icon;
|
|
/// </code>
|
|
/// </example>
|
|
public TransformerButtonConfigs? ButtonConfigs
|
|
{
|
|
get => (TransformerButtonConfigs?)GetValue(ButtonConfigsProperty);
|
|
set => SetValue(ButtonConfigsProperty, value);
|
|
}
|
|
|
|
// ============================================
|
|
// BUTTON DISPLAY MODE AND ICON PROPERTY ACCESSORS (Legacy - use ButtonConfigs instead)
|
|
// ============================================
|
|
|
|
/// <summary>
|
|
/// Global display mode for buttons (Label, Icon, or Both). Can be overridden per button.
|
|
/// Consider using ButtonConfigs property instead for a cleaner API.
|
|
/// </summary>
|
|
public ButtonDisplayMode DefaultButtonDisplayMode
|
|
{
|
|
get => (ButtonDisplayMode)GetValue(DefaultButtonDisplayModeProperty);
|
|
set => SetValue(DefaultButtonDisplayModeProperty, value);
|
|
}
|
|
|
|
// Overlay Select Button
|
|
public string OverlaySelectIconText
|
|
{
|
|
get => (string)GetValue(OverlaySelectIconTextProperty);
|
|
set => SetValue(OverlaySelectIconTextProperty, value);
|
|
}
|
|
|
|
public ImageSource? OverlaySelectIconSource
|
|
{
|
|
get => (ImageSource?)GetValue(OverlaySelectIconSourceProperty);
|
|
set => SetValue(OverlaySelectIconSourceProperty, value);
|
|
}
|
|
|
|
public string OverlaySelectLabel
|
|
{
|
|
get => (string)GetValue(OverlaySelectLabelProperty);
|
|
set => SetValue(OverlaySelectLabelProperty, value);
|
|
}
|
|
|
|
public ButtonDisplayMode? OverlaySelectDisplayMode
|
|
{
|
|
get => (ButtonDisplayMode?)GetValue(OverlaySelectDisplayModeProperty);
|
|
set => SetValue(OverlaySelectDisplayModeProperty, value);
|
|
}
|
|
|
|
// Overlay Transform Button
|
|
public string OverlayTransformIconText
|
|
{
|
|
get => (string)GetValue(OverlayTransformIconTextProperty);
|
|
set => SetValue(OverlayTransformIconTextProperty, value);
|
|
}
|
|
|
|
public ImageSource? OverlayTransformIconSource
|
|
{
|
|
get => (ImageSource?)GetValue(OverlayTransformIconSourceProperty);
|
|
set => SetValue(OverlayTransformIconSourceProperty, value);
|
|
}
|
|
|
|
public string OverlayTransformLabel
|
|
{
|
|
get => (string)GetValue(OverlayTransformLabelProperty);
|
|
set => SetValue(OverlayTransformLabelProperty, value);
|
|
}
|
|
|
|
public ButtonDisplayMode? OverlayTransformDisplayMode
|
|
{
|
|
get => (ButtonDisplayMode?)GetValue(OverlayTransformDisplayModeProperty);
|
|
set => SetValue(OverlayTransformDisplayModeProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Position of the overlay buttons (Top or Bottom). Default is Bottom.
|
|
/// </summary>
|
|
public OverlayButtonPosition OverlayButtonPosition
|
|
{
|
|
get => (OverlayButtonPosition)GetValue(OverlayButtonPositionProperty);
|
|
set => SetValue(OverlayButtonPositionProperty, value);
|
|
}
|
|
|
|
// SideBySide Capture Button
|
|
public string SideBySideCaptureIconText
|
|
{
|
|
get => (string)GetValue(SideBySideCaptureIconTextProperty);
|
|
set => SetValue(SideBySideCaptureIconTextProperty, value);
|
|
}
|
|
|
|
public ImageSource? SideBySideCaptureIconSource
|
|
{
|
|
get => (ImageSource?)GetValue(SideBySideCaptureIconSourceProperty);
|
|
set => SetValue(SideBySideCaptureIconSourceProperty, value);
|
|
}
|
|
|
|
public string SideBySideCaptureLabel
|
|
{
|
|
get => (string)GetValue(SideBySideCaptureLabelProperty);
|
|
set => SetValue(SideBySideCaptureLabelProperty, value);
|
|
}
|
|
|
|
public ButtonDisplayMode? SideBySideCaptureDisplayMode
|
|
{
|
|
get => (ButtonDisplayMode?)GetValue(SideBySideCaptureDisplayModeProperty);
|
|
set => SetValue(SideBySideCaptureDisplayModeProperty, value);
|
|
}
|
|
|
|
// SideBySide Pick Button
|
|
public string SideBySidePickIconText
|
|
{
|
|
get => (string)GetValue(SideBySidePickIconTextProperty);
|
|
set => SetValue(SideBySidePickIconTextProperty, value);
|
|
}
|
|
|
|
public ImageSource? SideBySidePickIconSource
|
|
{
|
|
get => (ImageSource?)GetValue(SideBySidePickIconSourceProperty);
|
|
set => SetValue(SideBySidePickIconSourceProperty, value);
|
|
}
|
|
|
|
public string SideBySidePickLabel
|
|
{
|
|
get => (string)GetValue(SideBySidePickLabelProperty);
|
|
set => SetValue(SideBySidePickLabelProperty, value);
|
|
}
|
|
|
|
public ButtonDisplayMode? SideBySidePickDisplayMode
|
|
{
|
|
get => (ButtonDisplayMode?)GetValue(SideBySidePickDisplayModeProperty);
|
|
set => SetValue(SideBySidePickDisplayModeProperty, value);
|
|
}
|
|
|
|
// SideBySide Clear Button
|
|
public string SideBySideClearIconText
|
|
{
|
|
get => (string)GetValue(SideBySideClearIconTextProperty);
|
|
set => SetValue(SideBySideClearIconTextProperty, value);
|
|
}
|
|
|
|
public ImageSource? SideBySideClearIconSource
|
|
{
|
|
get => (ImageSource?)GetValue(SideBySideClearIconSourceProperty);
|
|
set => SetValue(SideBySideClearIconSourceProperty, value);
|
|
}
|
|
|
|
public string SideBySideClearLabel
|
|
{
|
|
get => (string)GetValue(SideBySideClearLabelProperty);
|
|
set => SetValue(SideBySideClearLabelProperty, value);
|
|
}
|
|
|
|
public ButtonDisplayMode? SideBySideClearDisplayMode
|
|
{
|
|
get => (ButtonDisplayMode?)GetValue(SideBySideClearDisplayModeProperty);
|
|
set => SetValue(SideBySideClearDisplayModeProperty, value);
|
|
}
|
|
|
|
// SideBySide Redo Button
|
|
public string SideBySideRedoIconText
|
|
{
|
|
get => (string)GetValue(SideBySideRedoIconTextProperty);
|
|
set => SetValue(SideBySideRedoIconTextProperty, value);
|
|
}
|
|
|
|
public ImageSource? SideBySideRedoIconSource
|
|
{
|
|
get => (ImageSource?)GetValue(SideBySideRedoIconSourceProperty);
|
|
set => SetValue(SideBySideRedoIconSourceProperty, value);
|
|
}
|
|
|
|
public string SideBySideRedoLabel
|
|
{
|
|
get => (string)GetValue(SideBySideRedoLabelProperty);
|
|
set => SetValue(SideBySideRedoLabelProperty, value);
|
|
}
|
|
|
|
public ButtonDisplayMode? SideBySideRedoDisplayMode
|
|
{
|
|
get => (ButtonDisplayMode?)GetValue(SideBySideRedoDisplayModeProperty);
|
|
set => SetValue(SideBySideRedoDisplayModeProperty, value);
|
|
}
|
|
|
|
// SideBySide Transform Button
|
|
public string SideBySideTransformIconText
|
|
{
|
|
get => (string)GetValue(SideBySideTransformIconTextProperty);
|
|
set => SetValue(SideBySideTransformIconTextProperty, value);
|
|
}
|
|
|
|
public ImageSource? SideBySideTransformIconSource
|
|
{
|
|
get => (ImageSource?)GetValue(SideBySideTransformIconSourceProperty);
|
|
set => SetValue(SideBySideTransformIconSourceProperty, value);
|
|
}
|
|
|
|
public string SideBySideTransformLabel
|
|
{
|
|
get => (string)GetValue(SideBySideTransformLabelProperty);
|
|
set => SetValue(SideBySideTransformLabelProperty, value);
|
|
}
|
|
|
|
public ButtonDisplayMode? SideBySideTransformDisplayMode
|
|
{
|
|
get => (ButtonDisplayMode?)GetValue(SideBySideTransformDisplayModeProperty);
|
|
set => SetValue(SideBySideTransformDisplayModeProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Show or hide the transform button in SideBySide layout.
|
|
/// </summary>
|
|
public bool ShowSideBySideTransformButton
|
|
{
|
|
get => (bool)GetValue(ShowSideBySideTransformButtonProperty);
|
|
set => SetValue(ShowSideBySideTransformButtonProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The type of transformation to perform when the SideBySide transform button is clicked.
|
|
/// Default is Image. Set to Video for video generation.
|
|
/// </summary>
|
|
public TransformationType SideBySideTransformType
|
|
{
|
|
get => (TransformationType)GetValue(SideBySideTransformTypeProperty);
|
|
set => SetValue(SideBySideTransformTypeProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Automatically start transformation when an image is selected.
|
|
/// </summary>
|
|
public bool AutoTransformOnSelect
|
|
{
|
|
get => (bool)GetValue(AutoTransformOnSelectProperty);
|
|
set => SetValue(AutoTransformOnSelectProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The type of transformation to auto-start (Image or Video).
|
|
/// Only used when AutoTransformOnSelect is true.
|
|
/// </summary>
|
|
public TransformationType AutoTransformType
|
|
{
|
|
get => (TransformationType)GetValue(AutoTransformTypeProperty);
|
|
set => SetValue(AutoTransformTypeProperty, value);
|
|
}
|
|
|
|
// ============================================
|
|
// AUTO-SAVE PROPERTY ACCESSORS
|
|
// ============================================
|
|
|
|
/// <summary>
|
|
/// Automatically save image results to local storage when transformation completes.
|
|
/// </summary>
|
|
public bool AutoSaveImage
|
|
{
|
|
get => (bool)GetValue(AutoSaveImageProperty);
|
|
set => SetValue(AutoSaveImageProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Automatically save video results to local storage when transformation completes.
|
|
/// </summary>
|
|
public bool AutoSaveVideo
|
|
{
|
|
get => (bool)GetValue(AutoSaveVideoProperty);
|
|
set => SetValue(AutoSaveVideoProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Local folder path where images will be saved. If null, uses app's cache directory.
|
|
/// </summary>
|
|
public string? ImageSavePath
|
|
{
|
|
get => (string?)GetValue(ImageSavePathProperty);
|
|
set => SetValue(ImageSavePathProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Local folder path where videos will be saved. If null, uses app's cache directory.
|
|
/// </summary>
|
|
public string? VideoSavePath
|
|
{
|
|
get => (string?)GetValue(VideoSavePathProperty);
|
|
set => SetValue(VideoSavePathProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Filename pattern for saved images. Supports placeholders:
|
|
/// {timestamp} - Unix timestamp, {datetime} - formatted date/time,
|
|
/// {id} - prediction ID, {guid} - new GUID.
|
|
/// Extension is added automatically based on content type.
|
|
/// </summary>
|
|
public string ImageFilenamePattern
|
|
{
|
|
get => (string)GetValue(ImageFilenamePatternProperty);
|
|
set => SetValue(ImageFilenamePatternProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Filename pattern for saved videos. Supports placeholders:
|
|
/// {timestamp} - Unix timestamp, {datetime} - formatted date/time,
|
|
/// {id} - prediction ID, {guid} - new GUID.
|
|
/// Extension is added automatically based on content type.
|
|
/// </summary>
|
|
public string VideoFilenamePattern
|
|
{
|
|
get => (string)GetValue(VideoFilenamePatternProperty);
|
|
set => SetValue(VideoFilenamePatternProperty, value);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Events
|
|
|
|
public event EventHandler<TransformationStartedEventArgs>? TransformationStarted;
|
|
public event EventHandler<TransformationCompletedEventArgs>? TransformationCompleted;
|
|
public event EventHandler<TransformationErrorEventArgs>? TransformationError;
|
|
public event EventHandler<ImageSelectedEventArgs>? ImageSelected;
|
|
|
|
/// <summary>
|
|
/// Raised when the source image is tapped.
|
|
/// </summary>
|
|
public event EventHandler<ImageTappedEventArgs>? SourceImageTapped;
|
|
|
|
/// <summary>
|
|
/// Raised when the result image is tapped.
|
|
/// </summary>
|
|
public event EventHandler<ImageTappedEventArgs>? ResultImageTapped;
|
|
|
|
/// <summary>
|
|
/// Raised when the source image is double-tapped.
|
|
/// </summary>
|
|
public event EventHandler<ImageTappedEventArgs>? SourceImageDoubleTapped;
|
|
|
|
/// <summary>
|
|
/// Raised when the result image is double-tapped.
|
|
/// </summary>
|
|
public event EventHandler<ImageTappedEventArgs>? ResultImageDoubleTapped;
|
|
|
|
/// <summary>
|
|
/// Raised when a result is auto-saved to local storage.
|
|
/// </summary>
|
|
public event EventHandler<FileSavedEventArgs>? FileSaved;
|
|
|
|
/// <summary>
|
|
/// Raised when auto-save fails.
|
|
/// </summary>
|
|
public event EventHandler<FileSaveErrorEventArgs>? FileSaveError;
|
|
|
|
/// <summary>
|
|
/// Raised when a prediction is tracked (started or updated).
|
|
/// This is forwarded from the internal PredictionTracker.
|
|
/// </summary>
|
|
public event EventHandler<PredictionStatusChangedEventArgs>? PredictionTracked;
|
|
|
|
/// <summary>
|
|
/// Raised when a tracked prediction completes.
|
|
/// This is forwarded from the internal PredictionTracker.
|
|
/// </summary>
|
|
public event EventHandler<PredictionCompletedEventArgs>? PredictionCompleted;
|
|
|
|
/// <summary>
|
|
/// Raised when the control's state changes.
|
|
/// </summary>
|
|
public event EventHandler<StateChangedEventArgs>? StateChanged;
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Gets the current state of the control.
|
|
/// </summary>
|
|
public TransformerState CurrentState => _currentState;
|
|
|
|
private void SetState(TransformerState newState)
|
|
{
|
|
if (_currentState == newState)
|
|
return;
|
|
|
|
var previousState = _currentState;
|
|
_currentState = newState;
|
|
StateChanged?.Invoke(this, new StateChangedEventArgs(previousState, newState));
|
|
OnPropertyChanged(nameof(CurrentState));
|
|
}
|
|
|
|
public ReplicateTransformerView()
|
|
{
|
|
InitializeComponent();
|
|
UpdateVideoButtonsVisibility();
|
|
ApplyLayoutMode();
|
|
UpdateAllButtonAppearances();
|
|
SetupGestureRecognizers();
|
|
}
|
|
|
|
private void SetupGestureRecognizers()
|
|
{
|
|
// Single image layout - source image
|
|
var sourceImageTap = new TapGestureRecognizer();
|
|
sourceImageTap.Tapped += OnSourceImageTapped;
|
|
SourceImage.GestureRecognizers.Add(sourceImageTap);
|
|
|
|
var sourceImageDoubleTap = new TapGestureRecognizer { NumberOfTapsRequired = 2 };
|
|
sourceImageDoubleTap.Tapped += OnSourceImageDoubleTapped;
|
|
SourceImage.GestureRecognizers.Add(sourceImageDoubleTap);
|
|
|
|
// Single image layout - result image
|
|
var resultImageTap = new TapGestureRecognizer();
|
|
resultImageTap.Tapped += OnResultImageTapped;
|
|
ResultImage.GestureRecognizers.Add(resultImageTap);
|
|
|
|
var resultImageDoubleTap = new TapGestureRecognizer { NumberOfTapsRequired = 2 };
|
|
resultImageDoubleTap.Tapped += OnResultImageDoubleTapped;
|
|
ResultImage.GestureRecognizers.Add(resultImageDoubleTap);
|
|
|
|
// Side by side layout - source image
|
|
var sbsSourceTap = new TapGestureRecognizer();
|
|
sbsSourceTap.Tapped += OnSourceImageTapped;
|
|
SideBySideSourceImage.GestureRecognizers.Add(sbsSourceTap);
|
|
|
|
var sbsSourceDoubleTap = new TapGestureRecognizer { NumberOfTapsRequired = 2 };
|
|
sbsSourceDoubleTap.Tapped += OnSourceImageDoubleTapped;
|
|
SideBySideSourceImage.GestureRecognizers.Add(sbsSourceDoubleTap);
|
|
|
|
// Side by side layout - result image
|
|
var sbsResultTap = new TapGestureRecognizer();
|
|
sbsResultTap.Tapped += OnResultImageTapped;
|
|
SideBySideResultImage.GestureRecognizers.Add(sbsResultTap);
|
|
|
|
var sbsResultDoubleTap = new TapGestureRecognizer { NumberOfTapsRequired = 2 };
|
|
sbsResultDoubleTap.Tapped += OnResultImageDoubleTapped;
|
|
SideBySideResultImage.GestureRecognizers.Add(sbsResultDoubleTap);
|
|
}
|
|
|
|
private void OnSourceImageTapped(object? sender, TappedEventArgs e)
|
|
{
|
|
SourceImageTapped?.Invoke(this, new ImageTappedEventArgs(true, null, _currentImageBytes));
|
|
}
|
|
|
|
private void OnSourceImageDoubleTapped(object? sender, TappedEventArgs e)
|
|
{
|
|
SourceImageDoubleTapped?.Invoke(this, new ImageTappedEventArgs(true, null, _currentImageBytes));
|
|
}
|
|
|
|
private void OnResultImageTapped(object? sender, TappedEventArgs e)
|
|
{
|
|
ResultImageTapped?.Invoke(this, new ImageTappedEventArgs(false, ResultUrl, null));
|
|
}
|
|
|
|
private void OnResultImageDoubleTapped(object? sender, TappedEventArgs e)
|
|
{
|
|
ResultImageDoubleTapped?.Invoke(this, new ImageTappedEventArgs(false, ResultUrl, null));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize with a pre-configured transformer instance.
|
|
/// </summary>
|
|
public void Initialize(IReplicateTransformer transformer)
|
|
{
|
|
_transformer = transformer;
|
|
InitializeTracker();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialize with a factory for BYOK (Bring Your Own Key) scenarios.
|
|
/// Call SetApiToken() to configure the API token before transformations.
|
|
/// </summary>
|
|
public void Initialize(IReplicateTransformerFactory factory)
|
|
{
|
|
_transformerFactory = factory;
|
|
_transformer = factory.Create();
|
|
InitializeTracker();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set or update the API token at runtime (BYOK scenario).
|
|
/// Requires Initialize(IReplicateTransformerFactory) to be called first.
|
|
/// </summary>
|
|
/// <param name="apiToken">The Replicate API token.</param>
|
|
public void SetApiToken(string apiToken)
|
|
{
|
|
if (_transformerFactory == null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"SetApiToken requires initialization with IReplicateTransformerFactory. " +
|
|
"Call Initialize(IReplicateTransformerFactory) first.");
|
|
}
|
|
|
|
_transformer = _transformerFactory.CreateWithToken(apiToken);
|
|
InitializeTracker();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Configure custom settings at runtime (BYOK scenario with full control).
|
|
/// Requires Initialize(IReplicateTransformerFactory) to be called first.
|
|
/// </summary>
|
|
/// <param name="configure">Action to configure settings (token, prompts, timeouts, etc.).</param>
|
|
public void ConfigureSettings(Action<ReplicateSettings> configure)
|
|
{
|
|
if (_transformerFactory == null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"ConfigureSettings requires initialization with IReplicateTransformerFactory. " +
|
|
"Call Initialize(IReplicateTransformerFactory) first.");
|
|
}
|
|
|
|
_transformer = _transformerFactory.CreateWithSettings(configure);
|
|
InitializeTracker();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set a model preset for image transformations.
|
|
/// When set, image transformations will use the preset instead of ReplicateSettings.
|
|
/// </summary>
|
|
/// <param name="preset">The model preset to use for images.</param>
|
|
/// <param name="customParameters">Optional custom parameters to override preset defaults.</param>
|
|
public void SetImagePreset(ModelPreset preset, Dictionary<string, object>? customParameters = null)
|
|
{
|
|
if (preset.Type != ModelType.Image)
|
|
{
|
|
throw new ArgumentException($"Preset '{preset.Name}' is not an image model.", nameof(preset));
|
|
}
|
|
_imagePreset = preset;
|
|
_customImageParameters = customParameters;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set a model preset for video generation.
|
|
/// When set, video transformations will use the preset instead of ReplicateSettings.
|
|
/// </summary>
|
|
/// <param name="preset">The model preset to use for video.</param>
|
|
/// <param name="customParameters">Optional custom parameters to override preset defaults.</param>
|
|
public void SetVideoPreset(ModelPreset preset, Dictionary<string, object>? customParameters = null)
|
|
{
|
|
if (preset.Type != ModelType.Video)
|
|
{
|
|
throw new ArgumentException($"Preset '{preset.Name}' is not a video model.", nameof(preset));
|
|
}
|
|
_videoPreset = preset;
|
|
_customVideoParameters = customParameters;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update custom parameters for the current image preset.
|
|
/// </summary>
|
|
public void SetImageParameters(Dictionary<string, object>? customParameters)
|
|
{
|
|
_customImageParameters = customParameters;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update custom parameters for the current video preset.
|
|
/// </summary>
|
|
public void SetVideoParameters(Dictionary<string, object>? customParameters)
|
|
{
|
|
_customVideoParameters = customParameters;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear presets and use ReplicateSettings instead.
|
|
/// </summary>
|
|
public void ClearPresets()
|
|
{
|
|
_imagePreset = null;
|
|
_videoPreset = null;
|
|
_customImageParameters = null;
|
|
_customVideoParameters = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the currently configured image preset, if any.
|
|
/// </summary>
|
|
public ModelPreset? ImagePreset => _imagePreset;
|
|
|
|
/// <summary>
|
|
/// Gets the currently configured video preset, if any.
|
|
/// </summary>
|
|
public ModelPreset? VideoPreset => _videoPreset;
|
|
|
|
private void InitializeTracker()
|
|
{
|
|
if (_trackPredictions && _transformer != null)
|
|
{
|
|
// Unsubscribe from old tracker
|
|
if (_tracker != null)
|
|
{
|
|
_tracker.PredictionStatusChanged -= OnTrackerPredictionStatusChanged;
|
|
_tracker.PredictionCompleted -= OnTrackerPredictionCompleted;
|
|
_tracker.Dispose();
|
|
}
|
|
|
|
_tracker = new PredictionTracker(_transformer);
|
|
|
|
// Subscribe to forward events
|
|
_tracker.PredictionStatusChanged += OnTrackerPredictionStatusChanged;
|
|
_tracker.PredictionCompleted += OnTrackerPredictionCompleted;
|
|
}
|
|
}
|
|
|
|
private void OnTrackerPredictionStatusChanged(object? sender, PredictionStatusChangedEventArgs e)
|
|
{
|
|
PredictionTracked?.Invoke(this, e);
|
|
}
|
|
|
|
private void OnTrackerPredictionCompleted(object? sender, PredictionCompletedEventArgs e)
|
|
{
|
|
PredictionCompleted?.Invoke(this, e);
|
|
}
|
|
|
|
#region Property Changed Handlers
|
|
|
|
private static void OnLayoutModeChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
{
|
|
view._layoutMode = (TransformerLayoutMode)newValue;
|
|
view.ApplyLayoutMode();
|
|
}
|
|
}
|
|
|
|
private static void OnShowCaptureButtonsChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
{
|
|
view.CapturePhotoButton.IsVisible = (bool)newValue && view._layoutMode == TransformerLayoutMode.ButtonsBelow;
|
|
view.CaptureVideoButton.IsVisible = (bool)newValue && view.ShowVideoOptions && view._layoutMode == TransformerLayoutMode.ButtonsBelow;
|
|
}
|
|
}
|
|
|
|
private static void OnShowVideoOptionsChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
{
|
|
view.UpdateVideoButtonsVisibility();
|
|
}
|
|
}
|
|
|
|
private static void OnAnimeButtonTextChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
view.TransformAnimeButton.Text = (string)newValue;
|
|
}
|
|
|
|
private static void OnVideoButtonTextChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
view.TransformVideoButton.Text = (string)newValue;
|
|
}
|
|
|
|
private static void OnPlaceholderTextChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
{
|
|
view.PlaceholderLabel.Text = (string)newValue;
|
|
view.SideBySidePlaceholderLabel.Text = (string)newValue;
|
|
}
|
|
}
|
|
|
|
private static void OnOverlaySelectButtonTextChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
view.OverlayPickButton.Text = (string)newValue;
|
|
}
|
|
|
|
private static void OnOverlayTransformButtonTextChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
view.OverlayTransformButton.Text = (string)newValue;
|
|
}
|
|
|
|
private static void OnTrackPredictionsChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
{
|
|
view._trackPredictions = (bool)newValue;
|
|
if ((bool)newValue && view._transformer != null && view._tracker == null)
|
|
{
|
|
view._tracker = new PredictionTracker(view._transformer);
|
|
}
|
|
else if (!(bool)newValue && view._tracker != null)
|
|
{
|
|
view._tracker.Dispose();
|
|
view._tracker = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void OnUseLocalizationChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
{
|
|
view.ApplyLocalization();
|
|
}
|
|
}
|
|
|
|
private static void OnShowSideBySideSourceButtonsChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
view.SideBySideSourceButtons.IsVisible = (bool)newValue && view._layoutMode == TransformerLayoutMode.SideBySide;
|
|
}
|
|
|
|
private static void OnShowSideBySideResultButtonsChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
// Result buttons visibility is controlled by whether there's a result, combined with this setting
|
|
// The actual visibility logic is in UpdateSideBySideResultButtonsVisibility
|
|
if (bindable is ReplicateTransformerView view)
|
|
view.UpdateSideBySideResultButtonsVisibility();
|
|
}
|
|
|
|
private static void OnSideBySideCaptureButtonTextChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
view.SideBySideCaptureButton.Text = (string)newValue;
|
|
}
|
|
|
|
private static void OnSideBySidePickButtonTextChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
view.SideBySidePickButton.Text = (string)newValue;
|
|
}
|
|
|
|
private static void OnSideBySideClearButtonTextChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
view.SideBySideClearButton.Text = (string)newValue;
|
|
}
|
|
|
|
private static void OnSideBySideRedoButtonTextChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
view.SideBySideRedoButton.Text = (string)newValue;
|
|
}
|
|
|
|
// Consolidated button configs property changed handler
|
|
private static void OnButtonConfigsChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
{
|
|
// Unsubscribe from old config
|
|
if (oldValue is TransformerButtonConfigs oldConfigs)
|
|
{
|
|
oldConfigs.ConfigurationChanged -= view.OnButtonConfigsConfigurationChanged;
|
|
}
|
|
|
|
// Subscribe to new config
|
|
if (newValue is TransformerButtonConfigs newConfigs)
|
|
{
|
|
newConfigs.ConfigurationChanged += view.OnButtonConfigsConfigurationChanged;
|
|
}
|
|
|
|
view.UpdateAllButtonAppearances();
|
|
}
|
|
}
|
|
|
|
private void OnButtonConfigsConfigurationChanged(object? sender, EventArgs e)
|
|
{
|
|
UpdateAllButtonAppearances();
|
|
}
|
|
|
|
// Button display mode property changed handlers
|
|
private static void OnButtonDisplayModeChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
view.UpdateAllButtonAppearances();
|
|
}
|
|
|
|
private static void OnOverlaySelectAppearanceChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
view.UpdateOverlaySelectButtonAppearance();
|
|
}
|
|
|
|
private static void OnOverlayTransformAppearanceChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
view.UpdateOverlayTransformButtonAppearance();
|
|
}
|
|
|
|
private static void OnOverlayButtonPositionChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
view.UpdateOverlayButtonPosition();
|
|
}
|
|
|
|
private static void OnSideBySideCaptureAppearanceChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
view.UpdateSideBySideCaptureButtonAppearance();
|
|
}
|
|
|
|
private static void OnSideBySidePickAppearanceChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
view.UpdateSideBySidePickButtonAppearance();
|
|
}
|
|
|
|
private static void OnSideBySideClearAppearanceChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
view.UpdateSideBySideClearButtonAppearance();
|
|
}
|
|
|
|
private static void OnSideBySideRedoAppearanceChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
view.UpdateSideBySideRedoButtonAppearance();
|
|
}
|
|
|
|
private static void OnSideBySideTransformAppearanceChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
view.UpdateSideBySideTransformButtonAppearance();
|
|
}
|
|
|
|
private static void OnShowSideBySideTransformButtonChanged(BindableObject bindable, object oldValue, object newValue)
|
|
{
|
|
if (bindable is ReplicateTransformerView view)
|
|
view.UpdateSideBySideTransformButtonVisibility();
|
|
}
|
|
|
|
// Helper method to format button text based on display mode
|
|
private string FormatButtonText(string iconText, string label, ImageSource? iconSource, ButtonDisplayMode? specificMode)
|
|
{
|
|
var mode = specificMode ?? DefaultButtonDisplayMode;
|
|
|
|
return mode switch
|
|
{
|
|
ButtonDisplayMode.Label => label,
|
|
ButtonDisplayMode.Icon => iconSource == null ? iconText : string.Empty,
|
|
ButtonDisplayMode.Both => iconSource == null ? $"{iconText} {label}".Trim() : label,
|
|
_ => label
|
|
};
|
|
}
|
|
|
|
// Helper to update button appearance (text and image)
|
|
private void UpdateButtonAppearance(Button button, string iconText, string label, ImageSource? iconSource, ButtonDisplayMode? specificMode)
|
|
{
|
|
var mode = specificMode ?? DefaultButtonDisplayMode;
|
|
|
|
button.Text = FormatButtonText(iconText, label, iconSource, specificMode);
|
|
|
|
// Set image source if using Icon or Both mode with an ImageSource
|
|
if (iconSource != null && mode != ButtonDisplayMode.Label)
|
|
{
|
|
button.ImageSource = iconSource;
|
|
}
|
|
else
|
|
{
|
|
button.ImageSource = null;
|
|
}
|
|
}
|
|
|
|
private void UpdateAllButtonAppearances()
|
|
{
|
|
UpdateOverlaySelectButtonAppearance();
|
|
UpdateOverlayTransformButtonAppearance();
|
|
UpdateSideBySideCaptureButtonAppearance();
|
|
UpdateSideBySidePickButtonAppearance();
|
|
UpdateSideBySideTransformButtonAppearance();
|
|
UpdateSideBySideClearButtonAppearance();
|
|
UpdateSideBySideRedoButtonAppearance();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply localized strings to all UI elements when UseLocalization is enabled.
|
|
/// </summary>
|
|
private void ApplyLocalization()
|
|
{
|
|
if (!UseLocalization)
|
|
return;
|
|
|
|
// Update button labels with localized strings
|
|
OverlaySelectLabel = ReplicateStrings.Get(ReplicateStrings.Keys.Select);
|
|
OverlayTransformLabel = ReplicateStrings.Get(ReplicateStrings.Keys.Transform);
|
|
SideBySideCaptureLabel = ReplicateStrings.Get(ReplicateStrings.Keys.Take);
|
|
SideBySidePickLabel = ReplicateStrings.Get(ReplicateStrings.Keys.Pick);
|
|
SideBySideTransformLabel = ReplicateStrings.Get(ReplicateStrings.Keys.Transform);
|
|
SideBySideClearLabel = ReplicateStrings.Get(ReplicateStrings.Keys.Clear);
|
|
SideBySideRedoLabel = ReplicateStrings.Get(ReplicateStrings.Keys.Redo);
|
|
|
|
// Update placeholder labels
|
|
PlaceholderLabel.Text = ReplicateStrings.Get(ReplicateStrings.Keys.TapToSelectImage);
|
|
SideBySidePlaceholderLabel.Text = ReplicateStrings.Get(ReplicateStrings.Keys.TapToSelectImage);
|
|
|
|
// Update all button appearances to apply the new labels
|
|
UpdateAllButtonAppearances();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a localized string, or the fallback if localization is disabled.
|
|
/// </summary>
|
|
private string L(string key, string fallback)
|
|
{
|
|
return UseLocalization ? ReplicateStrings.Get(key) : fallback;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the effective display mode, preferring ButtonConfigs if set.
|
|
/// </summary>
|
|
private ButtonDisplayMode GetEffectiveDisplayMode()
|
|
{
|
|
return ButtonConfigs?.DefaultDisplayMode ?? DefaultButtonDisplayMode;
|
|
}
|
|
|
|
private void UpdateOverlaySelectButtonAppearance()
|
|
{
|
|
// Prefer ButtonConfigs if set
|
|
if (ButtonConfigs != null)
|
|
{
|
|
ButtonConfigs.OverlaySelect.ApplyTo(OverlayPickButton, GetEffectiveDisplayMode());
|
|
}
|
|
else
|
|
{
|
|
UpdateButtonAppearance(OverlayPickButton, OverlaySelectIconText, OverlaySelectLabel, OverlaySelectIconSource, OverlaySelectDisplayMode);
|
|
}
|
|
}
|
|
|
|
private void UpdateOverlayTransformButtonAppearance()
|
|
{
|
|
if (ButtonConfigs != null)
|
|
{
|
|
ButtonConfigs.OverlayTransform.ApplyTo(OverlayTransformButton, GetEffectiveDisplayMode());
|
|
}
|
|
else
|
|
{
|
|
UpdateButtonAppearance(OverlayTransformButton, OverlayTransformIconText, OverlayTransformLabel, OverlayTransformIconSource, OverlayTransformDisplayMode);
|
|
}
|
|
}
|
|
|
|
private void UpdateOverlayButtonPosition()
|
|
{
|
|
OverlayButtonsContainer.VerticalOptions = OverlayButtonPosition == OverlayButtonPosition.Top
|
|
? LayoutOptions.Start
|
|
: LayoutOptions.End;
|
|
}
|
|
|
|
private void UpdateSideBySideCaptureButtonAppearance()
|
|
{
|
|
if (ButtonConfigs != null)
|
|
{
|
|
ButtonConfigs.SideBySideCapture.ApplyTo(SideBySideCaptureButton, GetEffectiveDisplayMode());
|
|
}
|
|
else
|
|
{
|
|
UpdateButtonAppearance(SideBySideCaptureButton, SideBySideCaptureIconText, SideBySideCaptureLabel, SideBySideCaptureIconSource, SideBySideCaptureDisplayMode);
|
|
}
|
|
}
|
|
|
|
private void UpdateSideBySidePickButtonAppearance()
|
|
{
|
|
if (ButtonConfigs != null)
|
|
{
|
|
ButtonConfigs.SideBySidePick.ApplyTo(SideBySidePickButton, GetEffectiveDisplayMode());
|
|
}
|
|
else
|
|
{
|
|
UpdateButtonAppearance(SideBySidePickButton, SideBySidePickIconText, SideBySidePickLabel, SideBySidePickIconSource, SideBySidePickDisplayMode);
|
|
}
|
|
}
|
|
|
|
private void UpdateSideBySideClearButtonAppearance()
|
|
{
|
|
if (ButtonConfigs != null)
|
|
{
|
|
ButtonConfigs.SideBySideClear.ApplyTo(SideBySideClearButton, GetEffectiveDisplayMode());
|
|
}
|
|
else
|
|
{
|
|
UpdateButtonAppearance(SideBySideClearButton, SideBySideClearIconText, SideBySideClearLabel, SideBySideClearIconSource, SideBySideClearDisplayMode);
|
|
}
|
|
}
|
|
|
|
private void UpdateSideBySideRedoButtonAppearance()
|
|
{
|
|
if (ButtonConfigs != null)
|
|
{
|
|
ButtonConfigs.SideBySideRedo.ApplyTo(SideBySideRedoButton, GetEffectiveDisplayMode());
|
|
}
|
|
else
|
|
{
|
|
UpdateButtonAppearance(SideBySideRedoButton, SideBySideRedoIconText, SideBySideRedoLabel, SideBySideRedoIconSource, SideBySideRedoDisplayMode);
|
|
}
|
|
}
|
|
|
|
private void UpdateSideBySideTransformButtonAppearance()
|
|
{
|
|
if (ButtonConfigs != null)
|
|
{
|
|
ButtonConfigs.SideBySideTransform.ApplyTo(SideBySideTransformButton, GetEffectiveDisplayMode());
|
|
}
|
|
else
|
|
{
|
|
UpdateButtonAppearance(SideBySideTransformButton, SideBySideTransformIconText, SideBySideTransformLabel, SideBySideTransformIconSource, SideBySideTransformDisplayMode);
|
|
}
|
|
}
|
|
|
|
private void UpdateSideBySideTransformButtonVisibility()
|
|
{
|
|
// Show transform button only when there's an image AND the setting allows it AND we're in SideBySide mode
|
|
SideBySideTransformButton.IsVisible = ShowSideBySideTransformButton &&
|
|
_layoutMode == TransformerLayoutMode.SideBySide &&
|
|
_currentImageBytes != null;
|
|
}
|
|
|
|
private void UpdateSideBySideResultButtonsVisibility()
|
|
{
|
|
// Show result buttons only when there's a result AND the setting allows it
|
|
SideBySideResultButtons.IsVisible = ShowSideBySideResultButtons &&
|
|
_layoutMode == TransformerLayoutMode.SideBySide &&
|
|
SideBySideResultImage.IsVisible;
|
|
}
|
|
|
|
private void UpdateVideoButtonsVisibility()
|
|
{
|
|
if (_layoutMode == TransformerLayoutMode.ButtonsBelow)
|
|
{
|
|
CaptureVideoButton.IsVisible = ShowVideoOptions && ShowCaptureButtons;
|
|
PickVideoButton.IsVisible = ShowVideoOptions;
|
|
TransformVideoButton.IsVisible = ShowVideoOptions;
|
|
}
|
|
}
|
|
|
|
private void ApplyLayoutMode()
|
|
{
|
|
// Reset all containers
|
|
SingleImageContainer.IsVisible = false;
|
|
SideBySideContainer.IsVisible = false;
|
|
SourceButtonsGrid.IsVisible = false;
|
|
TransformButtonsGrid.IsVisible = false;
|
|
OverlayButtonsContainer.IsVisible = false;
|
|
|
|
switch (_layoutMode)
|
|
{
|
|
case TransformerLayoutMode.ImageOnly:
|
|
SingleImageContainer.IsVisible = true;
|
|
// No buttons shown
|
|
break;
|
|
|
|
case TransformerLayoutMode.ButtonsBelow:
|
|
SingleImageContainer.IsVisible = true;
|
|
SourceButtonsGrid.IsVisible = true;
|
|
// TransformButtonsGrid shown when image is selected
|
|
UpdateVideoButtonsVisibility();
|
|
break;
|
|
|
|
case TransformerLayoutMode.ButtonsOverlay:
|
|
SingleImageContainer.IsVisible = true;
|
|
OverlayButtonsContainer.IsVisible = true;
|
|
break;
|
|
|
|
case TransformerLayoutMode.SideBySide:
|
|
SideBySideContainer.IsVisible = true;
|
|
SideBySideSourceButtons.IsVisible = ShowSideBySideSourceButtons;
|
|
// Result buttons shown only when there's a result
|
|
UpdateSideBySideResultButtonsVisibility();
|
|
break;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Button Handlers
|
|
|
|
private async void OnCapturePhotoClicked(object? sender, EventArgs e)
|
|
{
|
|
try
|
|
{
|
|
if (!MediaPicker.Default.IsCaptureSupported)
|
|
{
|
|
await ShowError("Camera capture is not supported on this device.");
|
|
return;
|
|
}
|
|
|
|
var photo = await MediaPicker.Default.CapturePhotoAsync();
|
|
if (photo != null)
|
|
{
|
|
await LoadImageFromFileResult(photo);
|
|
}
|
|
}
|
|
catch (PermissionException)
|
|
{
|
|
await ShowError("Camera permission is required to capture photos.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await ShowError($"Failed to capture photo: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async void OnPickImageClicked(object? sender, EventArgs e)
|
|
{
|
|
try
|
|
{
|
|
var photo = await MediaPicker.Default.PickPhotoAsync();
|
|
if (photo != null)
|
|
{
|
|
await LoadImageFromFileResult(photo);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await ShowError($"Failed to pick image: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async void OnCaptureVideoClicked(object? sender, EventArgs e)
|
|
{
|
|
try
|
|
{
|
|
if (!MediaPicker.Default.IsCaptureSupported)
|
|
{
|
|
await ShowError("Video capture is not supported on this device.");
|
|
return;
|
|
}
|
|
|
|
var video = await MediaPicker.Default.CaptureVideoAsync();
|
|
if (video != null)
|
|
{
|
|
// For video, we'll extract first frame or use thumbnail
|
|
await LoadImageFromFileResult(video);
|
|
}
|
|
}
|
|
catch (PermissionException)
|
|
{
|
|
await ShowError("Camera permission is required to capture video.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await ShowError($"Failed to capture video: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async void OnPickVideoClicked(object? sender, EventArgs e)
|
|
{
|
|
try
|
|
{
|
|
var video = await MediaPicker.Default.PickVideoAsync();
|
|
if (video != null)
|
|
{
|
|
await LoadImageFromFileResult(video);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await ShowError($"Failed to pick video: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private async void OnTransformAnimeClicked(object? sender, EventArgs e)
|
|
{
|
|
if (_currentImageBytes == null || _transformer == null)
|
|
return;
|
|
|
|
await PerformTransformation(TransformationType.Image);
|
|
}
|
|
|
|
private async void OnTransformVideoClicked(object? sender, EventArgs e)
|
|
{
|
|
if (_currentImageBytes == null || _transformer == null)
|
|
return;
|
|
|
|
await PerformTransformation(TransformationType.Video);
|
|
}
|
|
|
|
private async void OnOverlayTransformClicked(object? sender, EventArgs e)
|
|
{
|
|
if (_currentImageBytes == null || _transformer == null)
|
|
return;
|
|
|
|
await PerformTransformation(OverlayTransformType);
|
|
}
|
|
|
|
private void OnResetClicked(object? sender, EventArgs e)
|
|
{
|
|
Reset();
|
|
}
|
|
|
|
private void OnSideBySideClearClicked(object? sender, EventArgs e)
|
|
{
|
|
// Clear only the result side
|
|
SideBySideResultImage.Source = null;
|
|
SideBySideResultImage.IsVisible = false;
|
|
SideBySideResultPlaceholder.IsVisible = true;
|
|
SideBySideResultButtons.IsVisible = false;
|
|
ResultUrl = null;
|
|
}
|
|
|
|
private async void OnSideBySideRedoClicked(object? sender, EventArgs e)
|
|
{
|
|
// Redo transformation with current source image
|
|
if (_currentImageBytes == null || _transformer == null)
|
|
return;
|
|
|
|
await PerformTransformation(TransformationType.Image);
|
|
}
|
|
|
|
private async void OnSideBySideTransformClicked(object? sender, EventArgs e)
|
|
{
|
|
if (_currentImageBytes == null || _transformer == null)
|
|
return;
|
|
|
|
await PerformTransformation(SideBySideTransformType);
|
|
}
|
|
|
|
private void OnDismissErrorClicked(object? sender, EventArgs e)
|
|
{
|
|
ErrorOverlay.IsVisible = false;
|
|
}
|
|
|
|
private void OnPlayVideoClicked(object? sender, EventArgs e)
|
|
{
|
|
if (string.IsNullOrEmpty(_videoUrl))
|
|
return;
|
|
|
|
SwitchToVideoPlayer();
|
|
}
|
|
|
|
private void OnSideBySidePlayVideoClicked(object? sender, EventArgs e)
|
|
{
|
|
if (string.IsNullOrEmpty(_videoUrl))
|
|
return;
|
|
|
|
SwitchToVideoPlayer();
|
|
}
|
|
|
|
private void SwitchToVideoPlayer()
|
|
{
|
|
if (string.IsNullOrEmpty(_videoUrl))
|
|
return;
|
|
|
|
_isShowingVideo = true;
|
|
|
|
if (_layoutMode == TransformerLayoutMode.SideBySide)
|
|
{
|
|
// Hide result image and play button, show video player
|
|
SideBySideResultImage.IsVisible = false;
|
|
SideBySidePlayButtonOverlay.IsVisible = false;
|
|
SideBySideVideoPlayer.Source = MediaSource.FromUri(_videoUrl);
|
|
SideBySideVideoPlayer.IsVisible = true;
|
|
|
|
// Flip buttons to show Back to Image / Clear
|
|
UpdateSideBySideVideoButtons(true);
|
|
}
|
|
else
|
|
{
|
|
// Single layout - hide result image, show video
|
|
ResultImage.IsVisible = false;
|
|
PlayButtonOverlay.IsVisible = false;
|
|
VideoPlayer.Source = MediaSource.FromUri(_videoUrl);
|
|
VideoPlayer.IsVisible = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Switch from video player back to showing the source image (single layout)
|
|
/// or the result thumbnail (side-by-side layout).
|
|
/// </summary>
|
|
public void SwitchToImageView()
|
|
{
|
|
_isShowingVideo = false;
|
|
|
|
if (_layoutMode == TransformerLayoutMode.SideBySide)
|
|
{
|
|
// Stop and hide video player
|
|
SideBySideVideoPlayer.Stop();
|
|
SideBySideVideoPlayer.IsVisible = false;
|
|
|
|
// Show result image with play button overlay
|
|
SideBySideResultImage.IsVisible = true;
|
|
if (!string.IsNullOrEmpty(_videoUrl))
|
|
{
|
|
SideBySidePlayButtonOverlay.IsVisible = true;
|
|
}
|
|
|
|
// Flip buttons back to Clear / Redo
|
|
UpdateSideBySideVideoButtons(false);
|
|
}
|
|
else
|
|
{
|
|
// Single layout
|
|
VideoPlayer.Stop();
|
|
VideoPlayer.IsVisible = false;
|
|
|
|
// Show source image (or result image if we have one)
|
|
if (_currentImageBytes != null)
|
|
{
|
|
SourceImage.IsVisible = true;
|
|
}
|
|
if (!string.IsNullOrEmpty(_videoUrl))
|
|
{
|
|
PlayButtonOverlay.IsVisible = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateSideBySideVideoButtons(bool showVideoButtons)
|
|
{
|
|
if (showVideoButtons)
|
|
{
|
|
// Change buttons to: Back to Image / Clear
|
|
var backToImageConfig = DefaultButtonConfigs.BackToImage;
|
|
SideBySideClearButton.Text = backToImageConfig.GetDisplayText(GetEffectiveDisplayMode());
|
|
SideBySideClearButton.Clicked -= OnSideBySideClearClicked;
|
|
SideBySideClearButton.Clicked += OnSideBySideBackToImageClicked;
|
|
|
|
var clearConfig = DefaultButtonConfigs.Clear;
|
|
SideBySideRedoButton.Text = clearConfig.GetDisplayText(GetEffectiveDisplayMode());
|
|
SideBySideRedoButton.Clicked -= OnSideBySideRedoClicked;
|
|
SideBySideRedoButton.Clicked += OnSideBySideClearVideoClicked;
|
|
|
|
SideBySideResultButtons.IsVisible = true;
|
|
}
|
|
else
|
|
{
|
|
// Restore original buttons: Clear / Redo
|
|
UpdateSideBySideClearButtonAppearance();
|
|
SideBySideClearButton.Clicked -= OnSideBySideBackToImageClicked;
|
|
SideBySideClearButton.Clicked += OnSideBySideClearClicked;
|
|
|
|
UpdateSideBySideRedoButtonAppearance();
|
|
SideBySideRedoButton.Clicked -= OnSideBySideClearVideoClicked;
|
|
SideBySideRedoButton.Clicked += OnSideBySideRedoClicked;
|
|
|
|
UpdateSideBySideResultButtonsVisibility();
|
|
}
|
|
}
|
|
|
|
private void OnSideBySideBackToImageClicked(object? sender, EventArgs e)
|
|
{
|
|
SwitchToImageView();
|
|
}
|
|
|
|
private void OnSideBySideClearVideoClicked(object? sender, EventArgs e)
|
|
{
|
|
// Stop video and clear everything
|
|
SideBySideVideoPlayer.Stop();
|
|
SideBySideVideoPlayer.IsVisible = false;
|
|
SideBySideResultImage.Source = null;
|
|
SideBySideResultImage.IsVisible = false;
|
|
SideBySidePlayButtonOverlay.IsVisible = false;
|
|
SideBySideResultPlaceholder.IsVisible = true;
|
|
_videoUrl = null;
|
|
_isShowingVideo = false;
|
|
|
|
// Restore original buttons
|
|
UpdateSideBySideVideoButtons(false);
|
|
SideBySideResultButtons.IsVisible = false;
|
|
ResultUrl = null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
|
|
public async Task SetImageAsync(byte[] imageBytes)
|
|
{
|
|
_currentImageBytes = imageBytes;
|
|
SourceImageSource = ImageSource.FromStream(() => new MemoryStream(imageBytes));
|
|
|
|
if (_layoutMode == TransformerLayoutMode.SideBySide)
|
|
{
|
|
SideBySideSourceImage.Source = SourceImageSource;
|
|
SideBySidePlaceholder.IsVisible = false;
|
|
SideBySideSourceImage.IsVisible = true;
|
|
// Reset result side
|
|
SideBySideResultImage.IsVisible = false;
|
|
SideBySideResultPlaceholder.IsVisible = true;
|
|
SideBySideResultButtons.IsVisible = false;
|
|
// Show transform button
|
|
UpdateSideBySideTransformButtonVisibility();
|
|
}
|
|
else
|
|
{
|
|
SourceImage.Source = SourceImageSource;
|
|
ShowImageState();
|
|
}
|
|
|
|
ImageSelected?.Invoke(this, new ImageSelectedEventArgs(imageBytes));
|
|
|
|
// Auto-transform if enabled
|
|
if (AutoTransformOnSelect && _transformer != null)
|
|
{
|
|
await PerformTransformation(AutoTransformType);
|
|
}
|
|
}
|
|
|
|
public async Task SetImageAsync(Stream imageStream)
|
|
{
|
|
using var ms = new MemoryStream();
|
|
await imageStream.CopyToAsync(ms);
|
|
await SetImageAsync(ms.ToArray());
|
|
}
|
|
|
|
public async Task SetImageAsync(string filePath)
|
|
{
|
|
var bytes = await File.ReadAllBytesAsync(filePath);
|
|
await SetImageAsync(bytes);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Programmatically trigger an image transformation.
|
|
/// </summary>
|
|
public async Task TransformImageAsync()
|
|
{
|
|
if (_currentImageBytes == null || _transformer == null)
|
|
return;
|
|
await PerformTransformation(TransformationType.Image);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Programmatically trigger a video transformation.
|
|
/// </summary>
|
|
public async Task TransformVideoAsync()
|
|
{
|
|
if (_currentImageBytes == null || _transformer == null)
|
|
return;
|
|
await PerformTransformation(TransformationType.Video);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the path to the last auto-saved image file, if any.
|
|
/// </summary>
|
|
public string? LastSavedImagePath => _lastSavedImagePath;
|
|
|
|
/// <summary>
|
|
/// Gets the path to the last auto-saved video file, if any.
|
|
/// </summary>
|
|
public string? LastSavedVideoPath => _lastSavedVideoPath;
|
|
|
|
/// <summary>
|
|
/// Manually save the current result to a file.
|
|
/// </summary>
|
|
/// <param name="url">The URL of the result to save.</param>
|
|
/// <param name="type">The type of result (Image or Video).</param>
|
|
/// <param name="predictionId">Optional prediction ID for filename pattern.</param>
|
|
/// <returns>The path to the saved file, or null if save failed.</returns>
|
|
public async Task<string?> SaveResultAsync(string url, TransformationType type, string? predictionId = null)
|
|
{
|
|
return await SaveFileFromUrlAsync(url, type, predictionId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Refresh all localized strings. Call this after changing ReplicateStrings.CurrentCulture
|
|
/// to update all UI text to the new language.
|
|
/// </summary>
|
|
public void RefreshLocalization()
|
|
{
|
|
if (UseLocalization)
|
|
{
|
|
ApplyLocalization();
|
|
}
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
_currentImageBytes = null;
|
|
ResultUrl = null;
|
|
SourceImageSource = null;
|
|
_videoUrl = null;
|
|
_isShowingVideo = false;
|
|
|
|
// Reset single image layout
|
|
SourceImage.Source = null;
|
|
SourceImage.IsVisible = false;
|
|
ResultImage.Source = null;
|
|
ResultImage.IsVisible = false;
|
|
PlaceholderView.IsVisible = true;
|
|
ProcessingOverlay.IsVisible = false;
|
|
ErrorOverlay.IsVisible = false;
|
|
|
|
// Reset video player
|
|
VideoPlayer.Stop();
|
|
VideoPlayer.IsVisible = false;
|
|
PlayButtonOverlay.IsVisible = false;
|
|
|
|
// Reset side-by-side layout
|
|
SideBySideSourceImage.Source = null;
|
|
SideBySideSourceImage.IsVisible = false;
|
|
SideBySidePlaceholder.IsVisible = true;
|
|
SideBySideResultImage.Source = null;
|
|
SideBySideResultImage.IsVisible = false;
|
|
SideBySideResultPlaceholder.IsVisible = true;
|
|
SideBySideProcessingOverlay.IsVisible = false;
|
|
|
|
// Reset side-by-side video player
|
|
SideBySideVideoPlayer.Stop();
|
|
SideBySideVideoPlayer.IsVisible = false;
|
|
SideBySidePlayButtonOverlay.IsVisible = false;
|
|
|
|
// Restore original button handlers if they were swapped
|
|
UpdateSideBySideVideoButtons(false);
|
|
|
|
// Reset buttons based on layout
|
|
if (_layoutMode == TransformerLayoutMode.ButtonsBelow || _layoutMode == TransformerLayoutMode.SideBySide)
|
|
{
|
|
SourceButtonsGrid.IsVisible = true;
|
|
TransformButtonsGrid.IsVisible = false;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
|
|
private async Task LoadImageFromFileResult(FileResult file)
|
|
{
|
|
using var stream = await file.OpenReadAsync();
|
|
using var ms = new MemoryStream();
|
|
await stream.CopyToAsync(ms);
|
|
await SetImageAsync(ms.ToArray());
|
|
}
|
|
|
|
private async Task<string?> SaveFileFromUrlAsync(string url, TransformationType type, string? predictionId)
|
|
{
|
|
// Try to get the service from DI if not already set
|
|
_fileSaveService ??= TryGetFileSaveService();
|
|
|
|
if (_fileSaveService == null)
|
|
{
|
|
// Fallback: use inline implementation if service not available
|
|
return await SaveFileFromUrlInlineAsync(url, type, predictionId);
|
|
}
|
|
|
|
var savePath = type == TransformationType.Image ? ImageSavePath : VideoSavePath;
|
|
var pattern = type == TransformationType.Image ? ImageFilenamePattern : VideoFilenamePattern;
|
|
|
|
var result = await _fileSaveService.SaveFromUrlAsync(url, type, savePath, pattern, predictionId);
|
|
|
|
if (result.Success)
|
|
{
|
|
// Store last saved path
|
|
if (type == TransformationType.Image)
|
|
_lastSavedImagePath = result.FilePath;
|
|
else
|
|
_lastSavedVideoPath = result.FilePath;
|
|
|
|
// Raise event
|
|
FileSaved?.Invoke(this, new FileSavedEventArgs(result.FilePath!, type, result.FileSizeBytes));
|
|
return result.FilePath;
|
|
}
|
|
else
|
|
{
|
|
FileSaveError?.Invoke(this, new FileSaveErrorEventArgs(type, result.Error!));
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private Services.IFileSaveService? TryGetFileSaveService()
|
|
{
|
|
try
|
|
{
|
|
return Handler?.MauiContext?.Services?.GetService<Services.IFileSaveService>();
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Fallback implementation when IFileSaveService is not available via DI
|
|
private async Task<string?> SaveFileFromUrlInlineAsync(string url, TransformationType type, string? predictionId)
|
|
{
|
|
try
|
|
{
|
|
using var httpClient = new HttpClient();
|
|
|
|
// Determine save path
|
|
var basePath = type == TransformationType.Image ? ImageSavePath : VideoSavePath;
|
|
if (string.IsNullOrEmpty(basePath))
|
|
{
|
|
basePath = FileSystem.CacheDirectory;
|
|
}
|
|
|
|
// Ensure directory exists
|
|
if (!Directory.Exists(basePath))
|
|
{
|
|
Directory.CreateDirectory(basePath);
|
|
}
|
|
|
|
// Download the file
|
|
var response = await httpClient.GetAsync(url);
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
// Determine file extension from content type or URL
|
|
var extension = GetFileExtension(response.Content.Headers.ContentType?.MediaType, url, type);
|
|
|
|
// Generate filename from pattern
|
|
var pattern = type == TransformationType.Image ? ImageFilenamePattern : VideoFilenamePattern;
|
|
var filename = GenerateFilename(pattern, predictionId) + extension;
|
|
|
|
// Full path
|
|
var filePath = Path.Combine(basePath, filename);
|
|
|
|
// Save the file
|
|
var bytes = await response.Content.ReadAsByteArrayAsync();
|
|
await File.WriteAllBytesAsync(filePath, bytes);
|
|
|
|
// Store last saved path
|
|
if (type == TransformationType.Image)
|
|
_lastSavedImagePath = filePath;
|
|
else
|
|
_lastSavedVideoPath = filePath;
|
|
|
|
// Raise event
|
|
FileSaved?.Invoke(this, new FileSavedEventArgs(filePath, type, bytes.Length));
|
|
|
|
return filePath;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
FileSaveError?.Invoke(this, new FileSaveErrorEventArgs(type, ex));
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static string GetFileExtension(string? contentType, string url, TransformationType type)
|
|
{
|
|
// Try to get from content type
|
|
if (!string.IsNullOrEmpty(contentType))
|
|
{
|
|
return contentType switch
|
|
{
|
|
"image/png" => ".png",
|
|
"image/jpeg" or "image/jpg" => ".jpg",
|
|
"image/webp" => ".webp",
|
|
"image/gif" => ".gif",
|
|
"video/mp4" => ".mp4",
|
|
"video/webm" => ".webm",
|
|
"video/quicktime" => ".mov",
|
|
_ => null
|
|
} ?? GetExtensionFromUrl(url, type);
|
|
}
|
|
|
|
return GetExtensionFromUrl(url, type);
|
|
}
|
|
|
|
private static string GetExtensionFromUrl(string url, TransformationType type)
|
|
{
|
|
// Try to extract from URL
|
|
try
|
|
{
|
|
var uri = new Uri(url);
|
|
var path = uri.AbsolutePath;
|
|
var ext = Path.GetExtension(path);
|
|
if (!string.IsNullOrEmpty(ext))
|
|
return ext;
|
|
}
|
|
catch { }
|
|
|
|
// Default extensions
|
|
return type == TransformationType.Image ? ".png" : ".mp4";
|
|
}
|
|
|
|
private static string GenerateFilename(string pattern, string? predictionId)
|
|
{
|
|
var now = DateTimeOffset.Now;
|
|
var result = pattern
|
|
.Replace("{timestamp}", now.ToUnixTimeSeconds().ToString())
|
|
.Replace("{datetime}", now.ToString("yyyyMMdd_HHmmss"))
|
|
.Replace("{id}", predictionId ?? Guid.NewGuid().ToString("N")[..8])
|
|
.Replace("{guid}", Guid.NewGuid().ToString("N"));
|
|
|
|
// Sanitize filename
|
|
foreach (var c in Path.GetInvalidFileNameChars())
|
|
{
|
|
result = result.Replace(c, '_');
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private async Task AutoSaveIfEnabledAsync(string? url, TransformationType type, string? predictionId)
|
|
{
|
|
if (string.IsNullOrEmpty(url))
|
|
return;
|
|
|
|
var shouldSave = type == TransformationType.Image ? AutoSaveImage : AutoSaveVideo;
|
|
if (!shouldSave)
|
|
return;
|
|
|
|
await SaveFileFromUrlAsync(url, type, predictionId);
|
|
}
|
|
|
|
private void ShowImageState()
|
|
{
|
|
PlaceholderView.IsVisible = false;
|
|
SourceImage.IsVisible = true;
|
|
ResultImage.IsVisible = false;
|
|
|
|
if (_layoutMode == TransformerLayoutMode.ButtonsBelow)
|
|
{
|
|
SourceButtonsGrid.IsVisible = true;
|
|
TransformButtonsGrid.IsVisible = true;
|
|
}
|
|
}
|
|
|
|
private async Task PerformTransformation(TransformationType type)
|
|
{
|
|
if (_currentImageBytes == null || _transformer == null)
|
|
{
|
|
await ShowError(L(ReplicateStrings.Keys.NoTransformerInitialized,
|
|
"No image selected or transformer not initialized. Call Initialize() with IReplicateTransformer."));
|
|
return;
|
|
}
|
|
|
|
IsProcessing = true;
|
|
|
|
// Show processing based on layout
|
|
if (_layoutMode == TransformerLayoutMode.SideBySide)
|
|
{
|
|
SideBySideProcessingOverlay.IsVisible = true;
|
|
SideBySideProcessingLabel.Text = type == TransformationType.Image
|
|
? L(ReplicateStrings.Keys.Transforming, "Transforming...")
|
|
: L(ReplicateStrings.Keys.GeneratingVideo, "Generating video...");
|
|
}
|
|
else
|
|
{
|
|
ProcessingOverlay.IsVisible = true;
|
|
ProcessingLabel.Text = type == TransformationType.Image
|
|
? L(ReplicateStrings.Keys.TransformingImage, "Transforming image...")
|
|
: L(ReplicateStrings.Keys.GeneratingVideo, "Generating video...");
|
|
ProcessingSubLabel.Text = type == TransformationType.Video
|
|
? L(ReplicateStrings.Keys.MayTakeSeveralMinutes, "This may take several minutes")
|
|
: L(ReplicateStrings.Keys.MayTakeAMoment, "This may take a moment");
|
|
}
|
|
|
|
SetButtonsEnabled(false);
|
|
_currentTransformationCts = new CancellationTokenSource();
|
|
TransformationStarted?.Invoke(this, new TransformationStartedEventArgs(type));
|
|
|
|
try
|
|
{
|
|
PredictionResult result;
|
|
if (type == TransformationType.Image)
|
|
{
|
|
// Use preset if configured, otherwise use legacy settings-based approach
|
|
if (_imagePreset != null)
|
|
{
|
|
result = await _transformer.RunPresetAsync(
|
|
_imagePreset,
|
|
_currentImageBytes,
|
|
CustomImagePrompt,
|
|
_customImageParameters,
|
|
cancellationToken: _currentTransformationCts.Token);
|
|
}
|
|
else
|
|
{
|
|
result = await _transformer.TransformToAnimeAsync(
|
|
_currentImageBytes,
|
|
CustomImagePrompt,
|
|
cancellationToken: _currentTransformationCts.Token);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Use preset if configured, otherwise use legacy settings-based approach
|
|
if (_videoPreset != null)
|
|
{
|
|
result = await _transformer.RunPresetAsync(
|
|
_videoPreset,
|
|
_currentImageBytes,
|
|
CustomVideoPrompt,
|
|
_customVideoParameters,
|
|
cancellationToken: _currentTransformationCts.Token);
|
|
}
|
|
else
|
|
{
|
|
result = await _transformer.TransformToVideoAsync(
|
|
_currentImageBytes,
|
|
CustomVideoPrompt,
|
|
cancellationToken: _currentTransformationCts.Token);
|
|
}
|
|
}
|
|
|
|
_currentPredictionId = result.Id;
|
|
ResultUrl = result.Output;
|
|
|
|
// Track prediction in history
|
|
_tracker?.Track(result, type, _currentImageBytes);
|
|
|
|
// Store transformation type
|
|
_lastTransformationType = type;
|
|
|
|
// Display result based on layout and type
|
|
if (type == TransformationType.Image && result.Output != null)
|
|
{
|
|
_videoUrl = null; // Clear video URL for image results
|
|
var resultImageSource = ImageSource.FromUri(new Uri(result.Output));
|
|
|
|
if (_layoutMode == TransformerLayoutMode.SideBySide)
|
|
{
|
|
SideBySideResultImage.Source = resultImageSource;
|
|
SideBySideResultPlaceholder.IsVisible = false;
|
|
SideBySideResultImage.IsVisible = true;
|
|
SideBySidePlayButtonOverlay.IsVisible = false;
|
|
UpdateSideBySideResultButtonsVisibility();
|
|
}
|
|
else
|
|
{
|
|
ResultImage.Source = resultImageSource;
|
|
SourceImage.IsVisible = false;
|
|
ResultImage.IsVisible = true;
|
|
PlayButtonOverlay.IsVisible = false;
|
|
}
|
|
}
|
|
else if (type == TransformationType.Video && result.Output != null)
|
|
{
|
|
// Store video URL for playback
|
|
_videoUrl = result.Output;
|
|
|
|
if (_layoutMode == TransformerLayoutMode.SideBySide)
|
|
{
|
|
// Show source image as thumbnail on result side with play button overlay
|
|
SideBySideResultImage.Source = SourceImageSource;
|
|
SideBySideResultPlaceholder.IsVisible = false;
|
|
SideBySideResultImage.IsVisible = true;
|
|
SideBySidePlayButtonOverlay.IsVisible = true;
|
|
UpdateSideBySideResultButtonsVisibility();
|
|
}
|
|
else
|
|
{
|
|
// Single layout - show source image with play button overlay
|
|
SourceImage.IsVisible = true;
|
|
ResultImage.IsVisible = false;
|
|
PlayButtonOverlay.IsVisible = true;
|
|
}
|
|
}
|
|
|
|
TransformationCompleted?.Invoke(this, new TransformationCompletedEventArgs(type, result));
|
|
|
|
// Auto-save if enabled
|
|
_ = AutoSaveIfEnabledAsync(result.Output, type, result.Id);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
// User canceled - don't show error
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await ShowError(ex.Message);
|
|
TransformationError?.Invoke(this, new TransformationErrorEventArgs(type, ex));
|
|
}
|
|
finally
|
|
{
|
|
IsProcessing = false;
|
|
ProcessingOverlay.IsVisible = false;
|
|
SideBySideProcessingOverlay.IsVisible = false;
|
|
SetButtonsEnabled(true);
|
|
_currentTransformationCts?.Dispose();
|
|
_currentTransformationCts = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cancel the current transformation in progress.
|
|
/// </summary>
|
|
public async Task CancelTransformationAsync()
|
|
{
|
|
_currentTransformationCts?.Cancel();
|
|
|
|
// Also cancel on the server if we have a prediction ID
|
|
if (_currentPredictionId != null && _transformer != null)
|
|
{
|
|
try
|
|
{
|
|
await _transformer.CancelPredictionAsync(_currentPredictionId);
|
|
}
|
|
catch
|
|
{
|
|
// Ignore errors during cancellation
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SetButtonsEnabled(bool enabled)
|
|
{
|
|
// Standard buttons
|
|
CapturePhotoButton.IsEnabled = enabled;
|
|
PickImageButton.IsEnabled = enabled;
|
|
CaptureVideoButton.IsEnabled = enabled;
|
|
PickVideoButton.IsEnabled = enabled;
|
|
TransformAnimeButton.IsEnabled = enabled;
|
|
TransformVideoButton.IsEnabled = enabled;
|
|
ResetButton.IsEnabled = enabled;
|
|
|
|
// Overlay buttons
|
|
OverlayPickButton.IsEnabled = enabled;
|
|
OverlayTransformButton.IsEnabled = enabled;
|
|
|
|
// SideBySide buttons
|
|
SideBySideCaptureButton.IsEnabled = enabled;
|
|
SideBySidePickButton.IsEnabled = enabled;
|
|
SideBySideClearButton.IsEnabled = enabled;
|
|
SideBySideRedoButton.IsEnabled = enabled;
|
|
SideBySideTransformButton.IsEnabled = enabled;
|
|
}
|
|
|
|
private Task ShowError(string message)
|
|
{
|
|
ErrorLabel.Text = message;
|
|
ErrorOverlay.IsVisible = true;
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IDisposable
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (_disposed) return;
|
|
|
|
if (disposing)
|
|
{
|
|
_currentTransformationCts?.Cancel();
|
|
_currentTransformationCts?.Dispose();
|
|
_tracker?.Dispose();
|
|
}
|
|
|
|
_disposed = true;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
// Event args classes are defined in Replicate.Maui/EventArgs.cs
|