diff --git a/10.0/Apps/Procrastinate/README.md b/10.0/Apps/Procrastinate/README.md new file mode 100644 index 000000000..8be25bf1f --- /dev/null +++ b/10.0/Apps/Procrastinate/README.md @@ -0,0 +1,69 @@ +--- +name: .NET MAUI - Procrastinate +description: A fun anti-productivity app demonstrating Shell navigation, AI integration (Groq Cloud & Apple Intelligence), localization, theming, and mini-games. +page_type: sample +languages: +- csharp +- xaml +products: +- dotnet-maui +urlFragment: apps-procrastinate +--- + +# Procrastinate - The Ultimate Anti-Productivity App + +This sample app demonstrates advanced .NET MAUI features through a humorous anti-productivity theme. It showcases Shell navigation with tabs, AI integration with both cloud (Groq) and on-device (Apple Intelligence) providers, multi-language localization, theming with the Nord color palette, and various mini-games. + +![Procrastinate App Screenshots](images/screenshots.png) + +## Features Demonstrated + +- **Shell Navigation** - Tab-based navigation with 4 main pages +- **AI Integration** - Cloud AI (Groq/Llama) and On-Device AI (Apple Intelligence via native xcframework) +- **Localization** - Full translation in 6 languages (English, French, Spanish, Portuguese, Dutch, Czech) +- **Custom Theming** - Nord color palette with dark/light theme support +- **Mini-Games** - 9 games including Simon Says, Minesweeper, Tic Tac Toe (with AI opponent), Snake, Memory Match +- **Native Interop** - Custom Swift xcframework for Apple Intelligence Foundation Models +- **XAML Source Generator** - Uses `MauiXamlInflator=SourceGen` for faster XAML loading +- **Plugin.Maui.Audio** - Sound effects for Simon Says game +- **Preferences API** - Settings persistence +- **Responsive UI** - Adapts to different screen sizes + +## Project Structure + +- `Pages/` - Main pages (Tasks, Games, Excuses, Stats, Settings) +- `Pages/Games/` - Mini-game implementations +- `Services/` - AI generators, stats tracking, click tracking +- `Resources/Strings/` - Localization resource files +- `Platforms/iOS/FMWrapper.xcframework/` - Native Apple Intelligence wrapper + +## AI Setup (Optional) + +### Cloud AI (Groq) +1. Create a free account at [Groq Console](https://console.groq.com/) +2. Generate an API key +3. Enter the key in the app's Settings page + +### On-Device AI (Apple Intelligence) +- Requires iOS 18.4+ or macOS 15.4+ with Apple Intelligence enabled +- Uses the native FMWrapper.xcframework included in the project + +## Building + +```bash +# iOS +dotnet build -f net10.0-ios + +# Android +dotnet build -f net10.0-android + +# macOS +dotnet build -f net10.0-maccatalyst +``` + +## Documentation Links + +- [Shell Navigation](https://docs.microsoft.com/dotnet/maui/fundamentals/shell/navigation) +- [Localization](https://docs.microsoft.com/dotnet/maui/fundamentals/localization) +- [Preferences](https://docs.microsoft.com/dotnet/maui/platform-integration/storage/preferences) +- [Native Library References](https://docs.microsoft.com/dotnet/maui/platform-integration/native-embedding) diff --git a/10.0/Apps/Procrastinate/images/screenshots.png b/10.0/Apps/Procrastinate/images/screenshots.png new file mode 100644 index 000000000..557b5d9c2 Binary files /dev/null and b/10.0/Apps/Procrastinate/images/screenshots.png differ diff --git a/10.0/Apps/Procrastinate/src/App.xaml b/10.0/Apps/Procrastinate/src/App.xaml new file mode 100644 index 000000000..570fd8146 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/App.xaml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/10.0/Apps/Procrastinate/src/App.xaml.cs b/10.0/Apps/Procrastinate/src/App.xaml.cs new file mode 100644 index 000000000..93d888711 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/App.xaml.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace procrastinate; + +public partial class App : Application +{ + public App() + { + InitializeComponent(); + + // Apply saved theme preference + var isLightTheme = Preferences.Get("HighContrastMode", false); + UserAppTheme = isLightTheme ? AppTheme.Light : AppTheme.Dark; + } + + protected override Window CreateWindow(IActivationState? activationState) + { + return new Window(new AppShell()); + } + + protected override async void OnAppLinkRequestReceived(Uri uri) + { + base.OnAppLinkRequestReceived(uri); + + // Handle deep links like procrastinate://ExcusePage + if (uri.Host is string route && !string.IsNullOrEmpty(route)) + { + await Shell.Current.GoToAsync($"//{route}"); + } + } +} \ No newline at end of file diff --git a/10.0/Apps/Procrastinate/src/AppShell.xaml b/10.0/Apps/Procrastinate/src/AppShell.xaml new file mode 100644 index 000000000..12a6ae371 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/AppShell.xaml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/10.0/Apps/Procrastinate/src/AppShell.xaml.cs b/10.0/Apps/Procrastinate/src/AppShell.xaml.cs new file mode 100644 index 000000000..6c3a08158 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/AppShell.xaml.cs @@ -0,0 +1,77 @@ +using procrastinate.Pages; +using procrastinate.Services; + +namespace procrastinate; + +public partial class AppShell : Shell +{ + private string? _previousTab; + + public AppShell() + { + InitializeComponent(); + Routing.RegisterRoute(nameof(SettingsPage), typeof(SettingsPage)); + + // Configure platform-specific navigation + ConfigurePlatformNavigation(); + + // Close settings page when tab changes + Navigated += OnShellNavigated; + } + + private void ConfigurePlatformNavigation() + { +#if MACCATALYST + // Mac Catalyst: Use Flyout sidebar, hide TabBar + MobileTabBar.IsVisible = false; + DesktopFlyout.IsVisible = true; + FlyoutBehavior = FlyoutBehavior.Flyout; +#else + // iOS/Android: Use bottom TabBar, hide Flyout + MobileTabBar.IsVisible = true; + DesktopFlyout.IsVisible = false; + FlyoutBehavior = FlyoutBehavior.Disabled; +#endif + } + + private async void OnShellNavigated(object? sender, ShellNavigatedEventArgs e) + { + var currentLocation = e.Current?.Location?.OriginalString; + + // Only handle root tab navigation (starts with //) + if (currentLocation?.StartsWith("//") != true) + { + return; + } + + // Extract the tab name (e.g., "//TasksPage" -> "TasksPage") + var currentTab = currentLocation.TrimStart('/').Split('/')[0]; + + // Refresh strings to recompute zalgo randomness on navigation + AppStrings.Refresh(); + + // If we're on the same tab but navigated (e.g., pushed settings then came back), + // don't pop - we might have just opened settings + if (_previousTab == currentTab) + { + _previousTab = currentTab; + return; + } + + // Tab changed - pop any pages on the navigation stack + _previousTab = currentTab; + + try + { + var nav = Current?.CurrentPage?.Navigation; + if (nav?.NavigationStack?.Count > 1) + { + await nav.PopToRootAsync(false); + } + } + catch + { + // Ignore navigation errors + } + } +} diff --git a/10.0/Apps/Procrastinate/src/LICENSE b/10.0/Apps/Procrastinate/src/LICENSE new file mode 100644 index 000000000..b4abd8086 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Stephane Delcroix + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/10.0/Apps/Procrastinate/src/MauiProgram.cs b/10.0/Apps/Procrastinate/src/MauiProgram.cs new file mode 100644 index 000000000..6ee8bdbd2 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/MauiProgram.cs @@ -0,0 +1,69 @@ +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Logging; +using Plugin.Maui.Audio; +using procrastinate.Pages; +using procrastinate.Services; + +namespace procrastinate; + +public static class MauiProgram +{ + public static MauiApp CreateMauiApp() + { + var builder = MauiApp.CreateBuilder(); + builder + .UseMauiApp() + .ConfigureFonts(fonts => + { + fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); + fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); + fonts.AddFont("FontAwesome-Solid.otf", "FontAwesomeSolid"); + }); + + // Register Apple Intelligence chat client and NLEmbedding (iOS/macOS only) +#if IOS || MACCATALYST +#pragma warning disable CA1416, MAUIAI0001 + try + { + var appleClient = new Microsoft.Maui.Essentials.AI.AppleIntelligenceChatClient(); + builder.Services.AddSingleton(appleClient); + System.Diagnostics.Debug.WriteLine("Apple Intelligence chat client registered"); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Apple Intelligence not available: {ex.Message}"); + } + + try + { + var nlEmbedding = new Microsoft.Maui.Essentials.AI.NLEmbeddingGenerator(); + builder.Services.AddSingleton>>(nlEmbedding); + System.Diagnostics.Debug.WriteLine("NLEmbedding generator registered"); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"NLEmbedding not available: {ex.Message}"); + } +#pragma warning restore CA1416, MAUIAI0001 +#endif + + // Services + builder.Services.AddSingleton(AudioManager.Current); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + + // Pages + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + builder.Services.AddTransient(); + +#if DEBUG + builder.Logging.AddDebug(); +#endif + + return builder.Build(); + } +} diff --git a/10.0/Apps/Procrastinate/src/NativeLibrary/FMWrapper.swift b/10.0/Apps/Procrastinate/src/NativeLibrary/FMWrapper.swift new file mode 100644 index 000000000..413170d0d --- /dev/null +++ b/10.0/Apps/Procrastinate/src/NativeLibrary/FMWrapper.swift @@ -0,0 +1,118 @@ +import Foundation +import FoundationModels + +/// Objective-C compatible wrapper for Apple's Foundation Models framework +/// Exposes key functionality to .NET MAUI via ObjCRuntime +@available(iOS 26.0, macOS 26.0, *) +@objc(FMWrapper) +public class FMWrapper: NSObject { + + // Shared state for async operation + private static var _lastResult: String? + private static var _lastError: String? + private static var _isGenerating: Bool = false + + @objc public override init() { + super.init() + } + + /// Check if Foundation Models is available on this device + @objc public static func isAvailable() -> Bool { + let model = SystemLanguageModel.default + switch model.availability { + case .available: + return true + case .unavailable(_): + return false + @unknown default: + return false + } + } + + /// Get the reason why Foundation Models is unavailable + @objc public static func getUnavailabilityReason() -> String { + let model = SystemLanguageModel.default + switch model.availability { + case .available: + return "" + case .unavailable(let reason): + switch reason { + case .deviceNotEligible: + return "Device not eligible for Apple Intelligence" + case .modelNotReady: + return "Apple Intelligence model is not ready yet" + case .appleIntelligenceNotEnabled: + return "Enable Apple Intelligence in Settings" + @unknown default: + return "Unknown availability issue" + } + @unknown default: + return "Unable to determine availability" + } + } + + /// Check if generation is in progress + @objc public static func isGenerating() -> Bool { + return _isGenerating + } + + /// Get the last result (nil if not ready or error) + @objc public static func getLastResult() -> String? { + return _lastResult + } + + /// Get the last error (nil if no error) + @objc public static func getLastError() -> String? { + return _lastError + } + + /// Start generating text (async, poll with isGenerating/getLastResult) + @objc public static func startGeneration(prompt: String, instructions: String) { + guard !_isGenerating else { return } + guard isAvailable() else { + _lastError = getUnavailabilityReason() + return + } + + _isGenerating = true + _lastResult = nil + _lastError = nil + + Task { + do { + let session = LanguageModelSession(instructions: instructions) + let response = try await session.respond(to: prompt) + let content = response.content + await MainActor.run { + _lastResult = content + _isGenerating = false + } + } catch let error as LanguageModelSession.GenerationError { + let errorMessage: String + switch error { + case .exceededContextWindowSize: + errorMessage = "Prompt too long" + case .guardrailViolation: + errorMessage = "Content policy violation" + case .unsupportedLanguageOrLocale: + errorMessage = "Language not supported" + case .rateLimited: + errorMessage = "Too many requests, try again" + case .refusal: + errorMessage = "Request was refused" + default: + errorMessage = "Generation failed: \(error.localizedDescription)" + } + await MainActor.run { + _lastError = errorMessage + _isGenerating = false + } + } catch { + await MainActor.run { + _lastError = "Error: \(error.localizedDescription)" + _isGenerating = false + } + } + } + } +} diff --git a/10.0/Apps/Procrastinate/src/Pages/ExcusePage.xaml b/10.0/Apps/Procrastinate/src/Pages/ExcusePage.xaml new file mode 100644 index 000000000..7b6b22f84 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Pages/ExcusePage.xaml @@ -0,0 +1,166 @@ + + + + + + + + diff --git a/10.0/Apps/Procrastinate/src/Pages/ExcusePage.xaml.cs b/10.0/Apps/Procrastinate/src/Pages/ExcusePage.xaml.cs new file mode 100644 index 000000000..9cfb9f77b --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Pages/ExcusePage.xaml.cs @@ -0,0 +1,168 @@ +using procrastinate.Services; + +namespace procrastinate.Pages; + +public partial class ExcusePage : ContentPage +{ + private readonly StatsService _statsService; + private readonly ExcuseService _excuseService; + private string _currentExcuse = ""; + + public ExcusePage(StatsService statsService, ExcuseService excuseService) + { + InitializeComponent(); + _statsService = statsService; + _excuseService = excuseService; + UpdateCounterLabel(); + } + + protected override void OnAppearing() + { + base.OnAppearing(); + UpdateCounterLabel(); + } + + protected override void OnDisappearing() + { + base.OnDisappearing(); + _excuseService.OnPipelineStageChanged = null; + _excuseService.OnAgentOutput = null; + } + + private void UpdateCounterLabel() + { + CounterLabel.Text = AppStrings.GetString("ExcusesGenerated", _statsService.ExcusesGenerated); + } + + private async void OnSettingsClicked(object? sender, EventArgs e) + { + await Shell.Current.GoToAsync(nameof(SettingsPage)); + } + + private async void OnGenerateClicked(object? sender, EventArgs e) + { + // Refresh zalgo randomness on button click + AppStrings.Refresh(); + + ShareIconBtn.IsVisible = false; + GeneratorInfoLabel.IsVisible = false; + PipelineStageLabel.IsVisible = false; + AgentReasoningBorder.IsVisible = false; + AgentReasoningContent.IsVisible = false; + AgentOutputStack.Children.Clear(); + + // Show loading state + GenerateBtn.IsEnabled = false; + ExcuseLabel.Text = AppStrings.GetString("Generating"); + + // Wire up pipeline stage callback for agent pipeline mode + _excuseService.OnPipelineStageChanged = (stage) => + { + MainThread.BeginInvokeOnMainThread(() => + { + PipelineStageLabel.Text = stage; + PipelineStageLabel.IsVisible = true; + }); + }; + + // Wire up agent reasoning output + _excuseService.OnAgentOutput = (agentName, output) => + { + MainThread.BeginInvokeOnMainThread(() => + { + var card = new VerticalStackLayout { Spacing = 4 }; + card.Children.Add(new Label + { + Text = agentName, + FontSize = 13, + FontAttributes = FontAttributes.Bold, + TextColor = Color.FromArgb("#EBCB8B") // Nord13 — yellow, high contrast on both themes + }); + card.Children.Add(new Label + { + Text = output, + FontSize = 13, + TextColor = Application.Current?.RequestedTheme == AppTheme.Light + ? Color.FromArgb("#2E3440") // Nord0 — dark text on light bg + : Color.FromArgb("#ECEFF4"), // Nord6 — bright text on dark bg + LineBreakMode = LineBreakMode.WordWrap + }); + AgentOutputStack.Children.Add(card); + }); + }; + + try + { + var result = await _excuseService.GenerateExcuseAsync(AppStrings.EffectiveLanguage); + _currentExcuse = result.Excuse; + + // Never apply Zalgo to the excuse itself + ExcuseLabel.Text = _currentExcuse; + + _statsService.IncrementExcusesGenerated(); + UpdateCounterLabel(); + + // Show generator info + UpdateGeneratorInfo(result); + + // Show reasoning panel if pipeline mode produced agent outputs + if (AgentOutputStack.Children.Count > 0) + AgentReasoningBorder.IsVisible = true; + + // Show the share button + ShareIconBtn.IsVisible = true; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Excuse generation error: {ex}"); + ExcuseLabel.Text = "Something went wrong. Tap to try again! 🔄"; + } + finally + { + GenerateBtn.IsEnabled = true; + } + } + + private void UpdateGeneratorInfo(ExcuseResult result) + { + var parts = new List(); + + // Show MEAI badge for AI-powered generators + if (result.GeneratorName.Contains("AI")) + parts.Add("MEAI"); + + parts.Add(result.GeneratorName); + + if (result.Model != null) + { + parts.Add(result.Model); + } + + parts.Add($"{result.Duration.TotalMilliseconds:F0}ms"); + + if (result.TokenCount.HasValue) + { + parts.Add($"{result.TokenCount} tokens"); + } + + GeneratorInfoLabel.Text = string.Join(" · ", parts); + GeneratorInfoLabel.IsVisible = true; + } + + private async void OnShareClicked(object? sender, EventArgs e) + { + if (string.IsNullOrEmpty(_currentExcuse)) return; + + await Share.Default.RequestAsync(new ShareTextRequest + { + Text = _currentExcuse, + Title = AppStrings.GetString("ShareExcuse") + }); + } + + private void OnToggleReasoning(object? sender, EventArgs e) + { + AgentReasoningContent.IsVisible = !AgentReasoningContent.IsVisible; + ReasoningToggleIcon.Text = AgentReasoningContent.IsVisible ? "\uf077" : "\uf078"; // chevron-up / chevron-down + } +} diff --git a/10.0/Apps/Procrastinate/src/Pages/Games/ClickSpeedGame.cs b/10.0/Apps/Procrastinate/src/Pages/Games/ClickSpeedGame.cs new file mode 100644 index 000000000..bcea44807 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Pages/Games/ClickSpeedGame.cs @@ -0,0 +1,26 @@ +using procrastinate.Services; + +namespace procrastinate.Pages.Games; + +public class ClickSpeedGame : MiniGame +{ + public override string Name => AppStrings.GetString("ClickSpeed"); + public override string Icon => "\uf0e7"; + public override string IconColor => "#D08770"; + public override string Description => AppStrings.GetString("ClickSpeedDesc"); + + private ClickSpeedGameView? _gameView; + + public override View CreateGameView() + { + _gameView = new ClickSpeedGameView + { + OnGamePlayed = () => OnGamePlayed?.Invoke(), + OnHighScore = score => OnHighScore?.Invoke(Name, score) + }; + return _gameView; + } + + public override void StartGame() { } + public override void StopGame() => _gameView?.Stop(); +} diff --git a/10.0/Apps/Procrastinate/src/Pages/Games/ClickSpeedGameView.xaml b/10.0/Apps/Procrastinate/src/Pages/Games/ClickSpeedGameView.xaml new file mode 100644 index 000000000..c9a3c364c --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Pages/Games/ClickSpeedGameView.xaml @@ -0,0 +1,29 @@ + + + + + + + diff --git a/10.0/Apps/Procrastinate/src/Pages/TasksPage.xaml.cs b/10.0/Apps/Procrastinate/src/Pages/TasksPage.xaml.cs new file mode 100644 index 000000000..2342a3c78 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Pages/TasksPage.xaml.cs @@ -0,0 +1,147 @@ +using procrastinate.Services; + +namespace procrastinate.Pages; + +public partial class TasksPage : ContentPage +{ + private readonly StatsService _statsService; + + public TasksPage(StatsService statsService) + { + InitializeComponent(); + _statsService = statsService; + } + + private async void OnSettingsClicked(object? sender, EventArgs e) + { + await Shell.Current.GoToAsync(nameof(SettingsPage)); + } + + private void OnTaskCardTapped(object? sender, TappedEventArgs e) + { + TaskCheckBox.IsChecked = !TaskCheckBox.IsChecked; + } + + private async void OnTaskChecked(object? sender, CheckedChangedEventArgs e) + { + if (e.Value) + { + _statsService.IncrementBreaksTaken(); + MotivationLabel.Text = AppStrings.GetString("Congratulations"); + await Task.Delay(2000); + TaskCheckBox.IsChecked = false; + MotivationLabel.Text = AppStrings.GetString("NeedAnotherBreak"); + } + } + + private async void OnAddTaskClicked(object? sender, EventArgs e) + { + // Refresh zalgo randomness on button click + AppStrings.Refresh(); + + _statsService.IncrementTasksAvoided(); + var excuses = GetLocalizedExcuses(); + var excuse = excuses[Random.Shared.Next(excuses.Length)]; + + // Apply Zalgo if enabled + var displayExcuse = AppStrings.IsZalgoMode ? AppStrings.Zalgoify(excuse) : excuse; + await DisplayAlertAsync("❌", displayExcuse, "OK"); + } + + private string[] GetLocalizedExcuses() + { + return AppStrings.CurrentLanguage switch + { + "fr" => [ + "Oups! La liste est pleine. Réessayez demain!", + "Erreur 404: Productivité introuvable.", + "Tâche rejetée: Vous méritez une pause!", + "Le serveur fait la sieste. Comme vous devriez!", + "Productivité maximale atteinte! (1 tâche = maximum)", + "Votre quota de tâches est épuisé. Revenez l'année prochaine!", + "Impossible d'ajouter: Le bonheur est plus important.", + "La liste des tâches est en grève. Solidarité!", + "Votre cerveau a besoin de repos. C'est scientifique!", + "Erreur système: Trop de motivation détectée." + ], + "es" => [ + "¡Ups! La lista está llena. ¡Inténtalo mañana!", + "Error 404: Productividad no encontrada.", + "¡Tarea rechazada: Te mereces un descanso!", + "El servidor está durmiendo. ¡Como tú deberías!", + "¡Productividad máxima alcanzada! (1 tarea = máximo)", + "Tu cuota de tareas está agotada. ¡Vuelve el próximo año!", + "Imposible agregar: La felicidad es más importante.", + "La lista de tareas está en huelga. ¡Solidaridad!", + "Tu cerebro necesita descanso. ¡Es científico!", + "Error del sistema: Demasiada motivación detectada." + ], + "pt" => [ + "Ops! A lista está cheia. Tente amanhã!", + "Erro 404: Produtividade não encontrada.", + "Tarefa rejeitada: Você merece uma pausa!", + "O servidor está dormindo. Como você deveria!", + "Produtividade máxima atingida! (1 tarefa = máximo)", + "Sua cota de tarefas acabou. Volte no próximo ano!", + "Impossível adicionar: A felicidade é mais importante.", + "A lista de tarefas está em greve. Solidariedade!", + "Seu cérebro precisa de descanso. É científico!", + "Erro do sistema: Muita motivação detectada." + ], + "nl" => [ + "Oeps! De lijst is vol. Probeer morgen!", + "Fout 404: Productiviteit niet gevonden.", + "Taak afgewezen: Je verdient een pauze!", + "De server slaapt. Net als jij zou moeten!", + "Maximale productiviteit bereikt! (1 taak = maximum)", + "Je takenlimiet is bereikt. Kom volgend jaar terug!", + "Kan niet toevoegen: Geluk is belangrijker.", + "De takenlijst staakt. Solidariteit!", + "Je brein heeft rust nodig. Het is wetenschap!", + "Systeemfout: Te veel motivatie gedetecteerd." + ], + "cs" => [ + "Jejda! Seznam je plný. Zkuste zítra!", + "Chyba 404: Produktivita nenalezena.", + "Úkol odmítnut: Zasloužíte si pauzu!", + "Server spí. Jako byste měli vy!", + "Maximální produktivita dosažena! (1 úkol = maximum)", + "Vaše kvóta úkolů je vyčerpána. Vraťte se příští rok!", + "Nelze přidat: Štěstí je důležitější.", + "Seznam úkolů stávkuje. Solidarita!", + "Váš mozek potřebuje odpočinek. Je to vědecké!", + "Systémová chyba: Detekováno příliš mnoho motivace." + ], + "uk" => [ + "Ой! Список повний. Спробуйте завтра!", + "Помилка 404: Продуктивність не знайдена.", + "Завдання відхилено: Ви заслуговуєте на перерву!", + "Сервер дрімає. Як і ви повинні!", + "Досягнуто максимальну продуктивність! (1 завдання = максимум)", + "Ваша квота завдань вичерпана. Повертайтеся наступного року!", + "Неможливо додати: Щастя важливіше.", + "Список завдань страйкує. Солідарність!", + "Вашому мозку потрібен відпочинок. Це наука!", + "Системна помилка: Виявлено занадто багато мотивації.", + "Переповнення буфера завдань. Будь ласка, прокрастинуйте.", + "Додавання завдань тимчасово вимкнено для вашого блага.", + "Фея завдань у відпустці. Спробуйте ніколи!" + ], + _ => [ + "Oops! The task list is full. Try again tomorrow!", + "Error 404: Productivity not found.", + "Task rejected: You deserve a break instead!", + "Server is napping. Just like you should be!", + "Maximum productivity reached! (1 task = maximum)", + "Your task quota is exhausted. Come back next year!", + "Cannot add: Happiness is more important.", + "The task list is on strike. Solidarity!", + "Your brain needs rest. It's science!", + "System error: Too much motivation detected.", + "Task buffer overflow. Please procrastinate.", + "Adding tasks is temporarily disabled for your wellbeing.", + "The task fairy is on vacation. Try again never!" + ] + }; + } +} diff --git a/10.0/Apps/Procrastinate/src/Platforms/Android/AndroidManifest.xml b/10.0/Apps/Procrastinate/src/Platforms/Android/AndroidManifest.xml new file mode 100644 index 000000000..bdec9b590 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Platforms/Android/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/10.0/Apps/Procrastinate/src/Platforms/Android/MainActivity.cs b/10.0/Apps/Procrastinate/src/Platforms/Android/MainActivity.cs new file mode 100644 index 000000000..2b5d36801 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Platforms/Android/MainActivity.cs @@ -0,0 +1,10 @@ +using Android.App; +using Android.Content.PM; +using Android.OS; + +namespace procrastinate; + +[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] +public class MainActivity : MauiAppCompatActivity +{ +} diff --git a/10.0/Apps/Procrastinate/src/Platforms/Android/MainApplication.cs b/10.0/Apps/Procrastinate/src/Platforms/Android/MainApplication.cs new file mode 100644 index 000000000..2855bb3e6 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Platforms/Android/MainApplication.cs @@ -0,0 +1,15 @@ +using Android.App; +using Android.Runtime; + +namespace procrastinate; + +[Application] +public class MainApplication : MauiApplication +{ + public MainApplication(IntPtr handle, JniHandleOwnership ownership) + : base(handle, ownership) + { + } + + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); +} diff --git a/10.0/Apps/Procrastinate/src/Platforms/Android/Resources/values/colors.xml b/10.0/Apps/Procrastinate/src/Platforms/Android/Resources/values/colors.xml new file mode 100644 index 000000000..5cd160496 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Platforms/Android/Resources/values/colors.xml @@ -0,0 +1,6 @@ + + + #512BD4 + #2B0B98 + #2B0B98 + \ No newline at end of file diff --git a/10.0/Apps/Procrastinate/src/Platforms/MacCatalyst/AppDelegate.cs b/10.0/Apps/Procrastinate/src/Platforms/MacCatalyst/AppDelegate.cs new file mode 100644 index 000000000..f5959229d --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Platforms/MacCatalyst/AppDelegate.cs @@ -0,0 +1,9 @@ +using Foundation; + +namespace procrastinate; + +[Register("AppDelegate")] +public class AppDelegate : MauiUIApplicationDelegate +{ + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); +} diff --git a/10.0/Apps/Procrastinate/src/Platforms/MacCatalyst/Entitlements.plist b/10.0/Apps/Procrastinate/src/Platforms/MacCatalyst/Entitlements.plist new file mode 100644 index 000000000..8e87c0cb0 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Platforms/MacCatalyst/Entitlements.plist @@ -0,0 +1,14 @@ + + + + + + + com.apple.security.app-sandbox + + + com.apple.security.network.client + + + + diff --git a/10.0/Apps/Procrastinate/src/Platforms/MacCatalyst/Info.plist b/10.0/Apps/Procrastinate/src/Platforms/MacCatalyst/Info.plist new file mode 100644 index 000000000..cfd1c83e2 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Platforms/MacCatalyst/Info.plist @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + UIDeviceFamily + + 2 + + LSApplicationCategoryType + public.app-category.lifestyle + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/appicon.appiconset + + diff --git a/10.0/Apps/Procrastinate/src/Platforms/MacCatalyst/Program.cs b/10.0/Apps/Procrastinate/src/Platforms/MacCatalyst/Program.cs new file mode 100644 index 000000000..70a497193 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Platforms/MacCatalyst/Program.cs @@ -0,0 +1,15 @@ +using ObjCRuntime; +using UIKit; + +namespace procrastinate; + +public class Program +{ + // This is the main entry point of the application. + static void Main(string[] args) + { + // if you want to use a different Application Delegate class from "AppDelegate" + // you can specify it here. + UIApplication.Main(args, null, typeof(AppDelegate)); + } +} diff --git a/10.0/Apps/Procrastinate/src/Platforms/Windows/App.xaml b/10.0/Apps/Procrastinate/src/Platforms/Windows/App.xaml new file mode 100644 index 000000000..de25b64c0 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Platforms/Windows/App.xaml @@ -0,0 +1,8 @@ + + + diff --git a/10.0/Apps/Procrastinate/src/Platforms/Windows/App.xaml.cs b/10.0/Apps/Procrastinate/src/Platforms/Windows/App.xaml.cs new file mode 100644 index 000000000..8482bfb42 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Platforms/Windows/App.xaml.cs @@ -0,0 +1,24 @@ +using Microsoft.UI.Xaml; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace procrastinate.WinUI; + +/// +/// Provides application-specific behavior to supplement the default Application class. +/// +public partial class App : MauiWinUIApplication +{ + /// + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). + /// + public App() + { + this.InitializeComponent(); + } + + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); +} + diff --git a/10.0/Apps/Procrastinate/src/Platforms/Windows/Package.appxmanifest b/10.0/Apps/Procrastinate/src/Platforms/Windows/Package.appxmanifest new file mode 100644 index 000000000..4556f6f3e --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Platforms/Windows/Package.appxmanifest @@ -0,0 +1,46 @@ + + + + + + + + + $placeholder$ + User Name + $placeholder$.png + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/10.0/Apps/Procrastinate/src/Platforms/Windows/app.manifest b/10.0/Apps/Procrastinate/src/Platforms/Windows/app.manifest new file mode 100644 index 000000000..87d937921 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Platforms/Windows/app.manifest @@ -0,0 +1,17 @@ + + + + + + + + true/PM + PerMonitorV2, PerMonitor + + true + + + diff --git a/10.0/Apps/Procrastinate/src/Platforms/iOS/AppDelegate.cs b/10.0/Apps/Procrastinate/src/Platforms/iOS/AppDelegate.cs new file mode 100644 index 000000000..fc13e0337 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Platforms/iOS/AppDelegate.cs @@ -0,0 +1,25 @@ +using Foundation; +using UIKit; + +namespace procrastinate; + +[Register("AppDelegate")] +public class AppDelegate : MauiUIApplicationDelegate +{ + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); + + public override bool OpenUrl(UIApplication application, NSUrl url, NSDictionary options) + { + // Forward deep link to MAUI + if (url.Scheme == "procrastinate" && url.Host is string route) + { + MainThread.BeginInvokeOnMainThread(async () => + { + await Task.Delay(500); // Wait for app to be ready + await Shell.Current.GoToAsync($"//{route}"); + }); + return true; + } + return base.OpenUrl(application, url, options); + } +} diff --git a/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/Info.plist b/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/Info.plist new file mode 100644 index 000000000..fa9640ce2 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/Info.plist @@ -0,0 +1,43 @@ + + + + + AvailableLibraries + + + BinaryPath + FMWrapper.framework/FMWrapper + LibraryIdentifier + ios-arm64 + LibraryPath + FMWrapper.framework + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + + + BinaryPath + FMWrapper.framework/FMWrapper + LibraryIdentifier + ios-arm64-simulator + LibraryPath + FMWrapper.framework + SupportedArchitectures + + arm64 + + SupportedPlatform + ios + SupportedPlatformVariant + simulator + + + CFBundlePackageType + XFWK + XCFrameworkFormatVersion + 1.0 + + diff --git a/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64-simulator/FMWrapper.framework/FMWrapper b/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64-simulator/FMWrapper.framework/FMWrapper new file mode 100755 index 000000000..4d7582438 Binary files /dev/null and b/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64-simulator/FMWrapper.framework/FMWrapper differ diff --git a/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64-simulator/FMWrapper.framework/Headers/FMWrapper.h b/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64-simulator/FMWrapper.framework/Headers/FMWrapper.h new file mode 100644 index 000000000..5dfde2eb2 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64-simulator/FMWrapper.framework/Headers/FMWrapper.h @@ -0,0 +1,332 @@ +// Generated by Apple Swift version 6.2.1 effective-5.10 (swiftlang-6.2.1.4.8 clang-1700.4.4.1) +#ifndef FMWRAPPER_SWIFT_H +#define FMWRAPPER_SWIFT_H +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgcc-compat" + +#if !defined(__has_include) +# define __has_include(x) 0 +#endif +#if !defined(__has_attribute) +# define __has_attribute(x) 0 +#endif +#if !defined(__has_feature) +# define __has_feature(x) 0 +#endif +#if !defined(__has_warning) +# define __has_warning(x) 0 +#endif + +#if __has_include() +# include +#endif + +#pragma clang diagnostic ignored "-Wauto-import" +#if defined(__OBJC__) +#include +#endif +#if defined(__cplusplus) +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#if defined(__cplusplus) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-modular-include-in-framework-module" +#if defined(__arm64e__) && __has_include() +# include +#else +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-macro-identifier" +# ifndef __ptrauth_swift_value_witness_function_pointer +# define __ptrauth_swift_value_witness_function_pointer(x) +# endif +# ifndef __ptrauth_swift_class_method_pointer +# define __ptrauth_swift_class_method_pointer(x) +# endif +#pragma clang diagnostic pop +#endif +#pragma clang diagnostic pop +#endif + +#if !defined(SWIFT_TYPEDEFS) +# define SWIFT_TYPEDEFS 1 +# if __has_include() +# include +# elif !defined(__cplusplus) +typedef unsigned char char8_t; +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; +# endif +typedef float swift_float2 __attribute__((__ext_vector_type__(2))); +typedef float swift_float3 __attribute__((__ext_vector_type__(3))); +typedef float swift_float4 __attribute__((__ext_vector_type__(4))); +typedef double swift_double2 __attribute__((__ext_vector_type__(2))); +typedef double swift_double3 __attribute__((__ext_vector_type__(3))); +typedef double swift_double4 __attribute__((__ext_vector_type__(4))); +typedef int swift_int2 __attribute__((__ext_vector_type__(2))); +typedef int swift_int3 __attribute__((__ext_vector_type__(3))); +typedef int swift_int4 __attribute__((__ext_vector_type__(4))); +typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); +typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); +typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); +#endif + +#if !defined(SWIFT_PASTE) +# define SWIFT_PASTE_HELPER(x, y) x##y +# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) +#endif +#if !defined(SWIFT_METATYPE) +# define SWIFT_METATYPE(X) Class +#endif +#if !defined(SWIFT_CLASS_PROPERTY) +# if __has_feature(objc_class_property) +# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ +# else +# define SWIFT_CLASS_PROPERTY(...) +# endif +#endif +#if !defined(SWIFT_RUNTIME_NAME) +# if __has_attribute(objc_runtime_name) +# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) +# else +# define SWIFT_RUNTIME_NAME(X) +# endif +#endif +#if !defined(SWIFT_COMPILE_NAME) +# if __has_attribute(swift_name) +# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) +# else +# define SWIFT_COMPILE_NAME(X) +# endif +#endif +#if !defined(SWIFT_METHOD_FAMILY) +# if __has_attribute(objc_method_family) +# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) +# else +# define SWIFT_METHOD_FAMILY(X) +# endif +#endif +#if !defined(SWIFT_NOESCAPE) +# if __has_attribute(noescape) +# define SWIFT_NOESCAPE __attribute__((noescape)) +# else +# define SWIFT_NOESCAPE +# endif +#endif +#if !defined(SWIFT_RELEASES_ARGUMENT) +# if __has_attribute(ns_consumed) +# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) +# else +# define SWIFT_RELEASES_ARGUMENT +# endif +#endif +#if !defined(SWIFT_WARN_UNUSED_RESULT) +# if __has_attribute(warn_unused_result) +# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# else +# define SWIFT_WARN_UNUSED_RESULT +# endif +#endif +#if !defined(SWIFT_NORETURN) +# if __has_attribute(noreturn) +# define SWIFT_NORETURN __attribute__((noreturn)) +# else +# define SWIFT_NORETURN +# endif +#endif +#if !defined(SWIFT_CLASS_EXTRA) +# define SWIFT_CLASS_EXTRA +#endif +#if !defined(SWIFT_PROTOCOL_EXTRA) +# define SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_ENUM_EXTRA) +# define SWIFT_ENUM_EXTRA +#endif +#if !defined(SWIFT_CLASS) +# if __has_attribute(objc_subclassing_restricted) +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# else +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# endif +#endif +#if !defined(SWIFT_RESILIENT_CLASS) +# if __has_attribute(objc_class_stub) +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub)) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME) +# else +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME) +# endif +#endif +#if !defined(SWIFT_PROTOCOL) +# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_EXTENSION) +# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) +#endif +#if !defined(OBJC_DESIGNATED_INITIALIZER) +# if __has_attribute(objc_designated_initializer) +# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) +# else +# define OBJC_DESIGNATED_INITIALIZER +# endif +#endif +#if !defined(SWIFT_ENUM_ATTR) +# if __has_attribute(enum_extensibility) +# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility))) +# else +# define SWIFT_ENUM_ATTR(_extensibility) +# endif +#endif +#if !defined(SWIFT_ENUM) +# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# if __has_feature(generalized_swift_name) +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# else +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) +# endif +#endif +#if !defined(SWIFT_UNAVAILABLE) +# define SWIFT_UNAVAILABLE __attribute__((unavailable)) +#endif +#if !defined(SWIFT_UNAVAILABLE_MSG) +# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg))) +#endif +#if !defined(SWIFT_AVAILABILITY) +# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__))) +#endif +#if !defined(SWIFT_WEAK_IMPORT) +# define SWIFT_WEAK_IMPORT __attribute__((weak_import)) +#endif +#if !defined(SWIFT_DEPRECATED) +# define SWIFT_DEPRECATED __attribute__((deprecated)) +#endif +#if !defined(SWIFT_DEPRECATED_MSG) +# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) +#endif +#if !defined(SWIFT_DEPRECATED_OBJC) +# if __has_feature(attribute_diagnose_if_objc) +# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) +# else +# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) +# endif +#endif +#if defined(__OBJC__) +#if !defined(IBSegueAction) +# define IBSegueAction +#endif +#endif +#if !defined(SWIFT_EXTERN) +# if defined(__cplusplus) +# define SWIFT_EXTERN extern "C" +# else +# define SWIFT_EXTERN extern +# endif +#endif +#if !defined(SWIFT_CALL) +# define SWIFT_CALL __attribute__((swiftcall)) +#endif +#if !defined(SWIFT_INDIRECT_RESULT) +# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result)) +#endif +#if !defined(SWIFT_CONTEXT) +# define SWIFT_CONTEXT __attribute__((swift_context)) +#endif +#if !defined(SWIFT_ERROR_RESULT) +# define SWIFT_ERROR_RESULT __attribute__((swift_error_result)) +#endif +#if defined(__cplusplus) +# define SWIFT_NOEXCEPT noexcept +#else +# define SWIFT_NOEXCEPT +#endif +#if !defined(SWIFT_C_INLINE_THUNK) +# if __has_attribute(always_inline) +# if __has_attribute(nodebug) +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) __attribute__((nodebug)) +# else +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) +# endif +# else +# define SWIFT_C_INLINE_THUNK inline +# endif +#endif +#if defined(_WIN32) +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport) +#endif +#else +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL +#endif +#endif +#if defined(__OBJC__) +#if __has_feature(objc_modules) +#if __has_warning("-Watimport-in-framework-header") +#pragma clang diagnostic ignored "-Watimport-in-framework-header" +#endif +@import ObjectiveC; +#endif + +#endif +#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" +#pragma clang diagnostic ignored "-Wduplicate-method-arg" +#if __has_warning("-Wpragma-clang-attribute") +# pragma clang diagnostic ignored "-Wpragma-clang-attribute" +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wnullability" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + +#if __has_attribute(external_source_symbol) +# pragma push_macro("any") +# undef any +# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="FMWrapper",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) +# pragma pop_macro("any") +#endif + +#if defined(__OBJC__) + +@class NSString; +/// Objective-C compatible wrapper for Apple’s Foundation Models framework +/// Exposes key functionality to .NET MAUI via ObjCRuntime +SWIFT_CLASS_NAMED("FMWrapper") SWIFT_AVAILABILITY(macos,introduced=26.0) SWIFT_AVAILABILITY(ios,introduced=26.0) +@interface FMWrapper : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +/// Check if Foundation Models is available on this device ++ (BOOL)isAvailable SWIFT_WARN_UNUSED_RESULT; +/// Get the reason why Foundation Models is unavailable ++ (NSString * _Nonnull)getUnavailabilityReason SWIFT_WARN_UNUSED_RESULT; +/// Check if generation is in progress ++ (BOOL)isGenerating SWIFT_WARN_UNUSED_RESULT; +/// Get the last result (nil if not ready or error) ++ (NSString * _Nullable)getLastResult SWIFT_WARN_UNUSED_RESULT; +/// Get the last error (nil if no error) ++ (NSString * _Nullable)getLastError SWIFT_WARN_UNUSED_RESULT; +/// Start generating text (async, poll with isGenerating/getLastResult) ++ (void)startGenerationWithPrompt:(NSString * _Nonnull)prompt instructions:(NSString * _Nonnull)instructions; +@end + +#endif +#if __has_attribute(external_source_symbol) +# pragma clang attribute pop +#endif +#if defined(__cplusplus) +#endif +#pragma clang diagnostic pop +#endif diff --git a/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64-simulator/FMWrapper.framework/Info.plist b/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64-simulator/FMWrapper.framework/Info.plist new file mode 100644 index 000000000..7ba77a46d --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64-simulator/FMWrapper.framework/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + FMWrapper + CFBundleIdentifier + com.procrastinate.FMWrapper + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + FMWrapper + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + MinimumOSVersion + 26.0 + + diff --git a/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64-simulator/FMWrapper.framework/Modules/module.modulemap b/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64-simulator/FMWrapper.framework/Modules/module.modulemap new file mode 100644 index 000000000..514c1fe3d --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64-simulator/FMWrapper.framework/Modules/module.modulemap @@ -0,0 +1,5 @@ +framework module FMWrapper { + umbrella header "FMWrapper.h" + export * + module * { export * } +} diff --git a/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64/FMWrapper.framework/FMWrapper b/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64/FMWrapper.framework/FMWrapper new file mode 100755 index 000000000..8ac92253c Binary files /dev/null and b/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64/FMWrapper.framework/FMWrapper differ diff --git a/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64/FMWrapper.framework/Headers/FMWrapper.h b/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64/FMWrapper.framework/Headers/FMWrapper.h new file mode 100644 index 000000000..5dfde2eb2 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64/FMWrapper.framework/Headers/FMWrapper.h @@ -0,0 +1,332 @@ +// Generated by Apple Swift version 6.2.1 effective-5.10 (swiftlang-6.2.1.4.8 clang-1700.4.4.1) +#ifndef FMWRAPPER_SWIFT_H +#define FMWRAPPER_SWIFT_H +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgcc-compat" + +#if !defined(__has_include) +# define __has_include(x) 0 +#endif +#if !defined(__has_attribute) +# define __has_attribute(x) 0 +#endif +#if !defined(__has_feature) +# define __has_feature(x) 0 +#endif +#if !defined(__has_warning) +# define __has_warning(x) 0 +#endif + +#if __has_include() +# include +#endif + +#pragma clang diagnostic ignored "-Wauto-import" +#if defined(__OBJC__) +#include +#endif +#if defined(__cplusplus) +#include +#include +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif +#if defined(__cplusplus) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-modular-include-in-framework-module" +#if defined(__arm64e__) && __has_include() +# include +#else +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreserved-macro-identifier" +# ifndef __ptrauth_swift_value_witness_function_pointer +# define __ptrauth_swift_value_witness_function_pointer(x) +# endif +# ifndef __ptrauth_swift_class_method_pointer +# define __ptrauth_swift_class_method_pointer(x) +# endif +#pragma clang diagnostic pop +#endif +#pragma clang diagnostic pop +#endif + +#if !defined(SWIFT_TYPEDEFS) +# define SWIFT_TYPEDEFS 1 +# if __has_include() +# include +# elif !defined(__cplusplus) +typedef unsigned char char8_t; +typedef uint_least16_t char16_t; +typedef uint_least32_t char32_t; +# endif +typedef float swift_float2 __attribute__((__ext_vector_type__(2))); +typedef float swift_float3 __attribute__((__ext_vector_type__(3))); +typedef float swift_float4 __attribute__((__ext_vector_type__(4))); +typedef double swift_double2 __attribute__((__ext_vector_type__(2))); +typedef double swift_double3 __attribute__((__ext_vector_type__(3))); +typedef double swift_double4 __attribute__((__ext_vector_type__(4))); +typedef int swift_int2 __attribute__((__ext_vector_type__(2))); +typedef int swift_int3 __attribute__((__ext_vector_type__(3))); +typedef int swift_int4 __attribute__((__ext_vector_type__(4))); +typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2))); +typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3))); +typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4))); +#endif + +#if !defined(SWIFT_PASTE) +# define SWIFT_PASTE_HELPER(x, y) x##y +# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y) +#endif +#if !defined(SWIFT_METATYPE) +# define SWIFT_METATYPE(X) Class +#endif +#if !defined(SWIFT_CLASS_PROPERTY) +# if __has_feature(objc_class_property) +# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__ +# else +# define SWIFT_CLASS_PROPERTY(...) +# endif +#endif +#if !defined(SWIFT_RUNTIME_NAME) +# if __has_attribute(objc_runtime_name) +# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X))) +# else +# define SWIFT_RUNTIME_NAME(X) +# endif +#endif +#if !defined(SWIFT_COMPILE_NAME) +# if __has_attribute(swift_name) +# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X))) +# else +# define SWIFT_COMPILE_NAME(X) +# endif +#endif +#if !defined(SWIFT_METHOD_FAMILY) +# if __has_attribute(objc_method_family) +# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X))) +# else +# define SWIFT_METHOD_FAMILY(X) +# endif +#endif +#if !defined(SWIFT_NOESCAPE) +# if __has_attribute(noescape) +# define SWIFT_NOESCAPE __attribute__((noescape)) +# else +# define SWIFT_NOESCAPE +# endif +#endif +#if !defined(SWIFT_RELEASES_ARGUMENT) +# if __has_attribute(ns_consumed) +# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed)) +# else +# define SWIFT_RELEASES_ARGUMENT +# endif +#endif +#if !defined(SWIFT_WARN_UNUSED_RESULT) +# if __has_attribute(warn_unused_result) +# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +# else +# define SWIFT_WARN_UNUSED_RESULT +# endif +#endif +#if !defined(SWIFT_NORETURN) +# if __has_attribute(noreturn) +# define SWIFT_NORETURN __attribute__((noreturn)) +# else +# define SWIFT_NORETURN +# endif +#endif +#if !defined(SWIFT_CLASS_EXTRA) +# define SWIFT_CLASS_EXTRA +#endif +#if !defined(SWIFT_PROTOCOL_EXTRA) +# define SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_ENUM_EXTRA) +# define SWIFT_ENUM_EXTRA +#endif +#if !defined(SWIFT_CLASS) +# if __has_attribute(objc_subclassing_restricted) +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# else +# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA +# endif +#endif +#if !defined(SWIFT_RESILIENT_CLASS) +# if __has_attribute(objc_class_stub) +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub)) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME) +# else +# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) +# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME) +# endif +#endif +#if !defined(SWIFT_PROTOCOL) +# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA +#endif +#if !defined(SWIFT_EXTENSION) +# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__) +#endif +#if !defined(OBJC_DESIGNATED_INITIALIZER) +# if __has_attribute(objc_designated_initializer) +# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer)) +# else +# define OBJC_DESIGNATED_INITIALIZER +# endif +#endif +#if !defined(SWIFT_ENUM_ATTR) +# if __has_attribute(enum_extensibility) +# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility))) +# else +# define SWIFT_ENUM_ATTR(_extensibility) +# endif +#endif +#if !defined(SWIFT_ENUM) +# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# if __has_feature(generalized_swift_name) +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type +# else +# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility) +# endif +#endif +#if !defined(SWIFT_UNAVAILABLE) +# define SWIFT_UNAVAILABLE __attribute__((unavailable)) +#endif +#if !defined(SWIFT_UNAVAILABLE_MSG) +# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg))) +#endif +#if !defined(SWIFT_AVAILABILITY) +# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__))) +#endif +#if !defined(SWIFT_WEAK_IMPORT) +# define SWIFT_WEAK_IMPORT __attribute__((weak_import)) +#endif +#if !defined(SWIFT_DEPRECATED) +# define SWIFT_DEPRECATED __attribute__((deprecated)) +#endif +#if !defined(SWIFT_DEPRECATED_MSG) +# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__))) +#endif +#if !defined(SWIFT_DEPRECATED_OBJC) +# if __has_feature(attribute_diagnose_if_objc) +# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning"))) +# else +# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg) +# endif +#endif +#if defined(__OBJC__) +#if !defined(IBSegueAction) +# define IBSegueAction +#endif +#endif +#if !defined(SWIFT_EXTERN) +# if defined(__cplusplus) +# define SWIFT_EXTERN extern "C" +# else +# define SWIFT_EXTERN extern +# endif +#endif +#if !defined(SWIFT_CALL) +# define SWIFT_CALL __attribute__((swiftcall)) +#endif +#if !defined(SWIFT_INDIRECT_RESULT) +# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result)) +#endif +#if !defined(SWIFT_CONTEXT) +# define SWIFT_CONTEXT __attribute__((swift_context)) +#endif +#if !defined(SWIFT_ERROR_RESULT) +# define SWIFT_ERROR_RESULT __attribute__((swift_error_result)) +#endif +#if defined(__cplusplus) +# define SWIFT_NOEXCEPT noexcept +#else +# define SWIFT_NOEXCEPT +#endif +#if !defined(SWIFT_C_INLINE_THUNK) +# if __has_attribute(always_inline) +# if __has_attribute(nodebug) +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) __attribute__((nodebug)) +# else +# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) +# endif +# else +# define SWIFT_C_INLINE_THUNK inline +# endif +#endif +#if defined(_WIN32) +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport) +#endif +#else +#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL) +# define SWIFT_IMPORT_STDLIB_SYMBOL +#endif +#endif +#if defined(__OBJC__) +#if __has_feature(objc_modules) +#if __has_warning("-Watimport-in-framework-header") +#pragma clang diagnostic ignored "-Watimport-in-framework-header" +#endif +@import ObjectiveC; +#endif + +#endif +#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch" +#pragma clang diagnostic ignored "-Wduplicate-method-arg" +#if __has_warning("-Wpragma-clang-attribute") +# pragma clang diagnostic ignored "-Wpragma-clang-attribute" +#endif +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma clang diagnostic ignored "-Wnullability" +#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" + +#if __has_attribute(external_source_symbol) +# pragma push_macro("any") +# undef any +# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="FMWrapper",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol)) +# pragma pop_macro("any") +#endif + +#if defined(__OBJC__) + +@class NSString; +/// Objective-C compatible wrapper for Apple’s Foundation Models framework +/// Exposes key functionality to .NET MAUI via ObjCRuntime +SWIFT_CLASS_NAMED("FMWrapper") SWIFT_AVAILABILITY(macos,introduced=26.0) SWIFT_AVAILABILITY(ios,introduced=26.0) +@interface FMWrapper : NSObject +- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER; +/// Check if Foundation Models is available on this device ++ (BOOL)isAvailable SWIFT_WARN_UNUSED_RESULT; +/// Get the reason why Foundation Models is unavailable ++ (NSString * _Nonnull)getUnavailabilityReason SWIFT_WARN_UNUSED_RESULT; +/// Check if generation is in progress ++ (BOOL)isGenerating SWIFT_WARN_UNUSED_RESULT; +/// Get the last result (nil if not ready or error) ++ (NSString * _Nullable)getLastResult SWIFT_WARN_UNUSED_RESULT; +/// Get the last error (nil if no error) ++ (NSString * _Nullable)getLastError SWIFT_WARN_UNUSED_RESULT; +/// Start generating text (async, poll with isGenerating/getLastResult) ++ (void)startGenerationWithPrompt:(NSString * _Nonnull)prompt instructions:(NSString * _Nonnull)instructions; +@end + +#endif +#if __has_attribute(external_source_symbol) +# pragma clang attribute pop +#endif +#if defined(__cplusplus) +#endif +#pragma clang diagnostic pop +#endif diff --git a/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64/FMWrapper.framework/Info.plist b/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64/FMWrapper.framework/Info.plist new file mode 100644 index 000000000..7ba77a46d --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64/FMWrapper.framework/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + FMWrapper + CFBundleIdentifier + com.procrastinate.FMWrapper + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + FMWrapper + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + MinimumOSVersion + 26.0 + + diff --git a/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64/FMWrapper.framework/Modules/module.modulemap b/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64/FMWrapper.framework/Modules/module.modulemap new file mode 100644 index 000000000..514c1fe3d --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Platforms/iOS/FMWrapper.xcframework/ios-arm64/FMWrapper.framework/Modules/module.modulemap @@ -0,0 +1,5 @@ +framework module FMWrapper { + umbrella header "FMWrapper.h" + export * + module * { export * } +} diff --git a/10.0/Apps/Procrastinate/src/Platforms/iOS/Info.plist b/10.0/Apps/Procrastinate/src/Platforms/iOS/Info.plist new file mode 100644 index 000000000..a70d3d0dd --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Platforms/iOS/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleURLTypes + + + CFBundleURLName + org.reblochon.procrastinate + CFBundleURLSchemes + + procrastinate + + + + LSRequiresIPhoneOS + + UIDeviceFamily + + 1 + 2 + + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/appicon.appiconset + ITSAppUsesNonExemptEncryption + + + diff --git a/10.0/Apps/Procrastinate/src/Platforms/iOS/Program.cs b/10.0/Apps/Procrastinate/src/Platforms/iOS/Program.cs new file mode 100644 index 000000000..70a497193 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Platforms/iOS/Program.cs @@ -0,0 +1,15 @@ +using ObjCRuntime; +using UIKit; + +namespace procrastinate; + +public class Program +{ + // This is the main entry point of the application. + static void Main(string[] args) + { + // if you want to use a different Application Delegate class from "AppDelegate" + // you can specify it here. + UIApplication.Main(args, null, typeof(AppDelegate)); + } +} diff --git a/10.0/Apps/Procrastinate/src/Platforms/iOS/Resources/PrivacyInfo.xcprivacy b/10.0/Apps/Procrastinate/src/Platforms/iOS/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..1ea3a5d73 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Platforms/iOS/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,51 @@ + + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryDiskSpace + NSPrivacyAccessedAPITypeReasons + + E174.1 + + + + + + diff --git a/10.0/Apps/Procrastinate/src/Properties/launchSettings.json b/10.0/Apps/Procrastinate/src/Properties/launchSettings.json new file mode 100644 index 000000000..f4c6c8ddd --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Windows Machine": { + "commandName": "Project", + "nativeDebugging": false + } + } +} \ No newline at end of file diff --git a/10.0/Apps/Procrastinate/src/Resources/AppIcon/appicon.svg b/10.0/Apps/Procrastinate/src/Resources/AppIcon/appicon.svg new file mode 100644 index 000000000..f6e60da06 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Resources/AppIcon/appicon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/10.0/Apps/Procrastinate/src/Resources/AppIcon/appiconfg.svg b/10.0/Apps/Procrastinate/src/Resources/AppIcon/appiconfg.svg new file mode 100644 index 000000000..46123af67 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Resources/AppIcon/appiconfg.svg @@ -0,0 +1,4 @@ + + + 🦥 + diff --git a/10.0/Apps/Procrastinate/src/Resources/Fonts/FontAwesome-Solid.otf b/10.0/Apps/Procrastinate/src/Resources/Fonts/FontAwesome-Solid.otf new file mode 100644 index 000000000..6bd19b13b Binary files /dev/null and b/10.0/Apps/Procrastinate/src/Resources/Fonts/FontAwesome-Solid.otf differ diff --git a/10.0/Apps/Procrastinate/src/Resources/Fonts/OpenSans-Regular.ttf b/10.0/Apps/Procrastinate/src/Resources/Fonts/OpenSans-Regular.ttf new file mode 100644 index 000000000..26d4b1704 Binary files /dev/null and b/10.0/Apps/Procrastinate/src/Resources/Fonts/OpenSans-Regular.ttf differ diff --git a/10.0/Apps/Procrastinate/src/Resources/Fonts/OpenSans-Semibold.ttf b/10.0/Apps/Procrastinate/src/Resources/Fonts/OpenSans-Semibold.ttf new file mode 100644 index 000000000..d75d29dd9 Binary files /dev/null and b/10.0/Apps/Procrastinate/src/Resources/Fonts/OpenSans-Semibold.ttf differ diff --git a/10.0/Apps/Procrastinate/src/Resources/Images/dotnet_bot.png b/10.0/Apps/Procrastinate/src/Resources/Images/dotnet_bot.png new file mode 100644 index 000000000..054167e59 Binary files /dev/null and b/10.0/Apps/Procrastinate/src/Resources/Images/dotnet_bot.png differ diff --git a/10.0/Apps/Procrastinate/src/Resources/Raw/AboutAssets.txt b/10.0/Apps/Procrastinate/src/Resources/Raw/AboutAssets.txt new file mode 100644 index 000000000..f22d3bfa8 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Resources/Raw/AboutAssets.txt @@ -0,0 +1,15 @@ +Any raw assets you want to be deployed with your application can be placed in +this directory (and child directories). Deployment of the asset to your application +is automatically handled by the following `MauiAsset` Build Action within your `.csproj`. + + + +These files will be deployed with your package and will be accessible using Essentials: + + async Task LoadMauiAsset() + { + using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt"); + using var reader = new StreamReader(stream); + + var contents = reader.ReadToEnd(); + } diff --git a/10.0/Apps/Procrastinate/src/Resources/Raw/simon_blue.wav b/10.0/Apps/Procrastinate/src/Resources/Raw/simon_blue.wav new file mode 100644 index 000000000..634cc329f Binary files /dev/null and b/10.0/Apps/Procrastinate/src/Resources/Raw/simon_blue.wav differ diff --git a/10.0/Apps/Procrastinate/src/Resources/Raw/simon_green.wav b/10.0/Apps/Procrastinate/src/Resources/Raw/simon_green.wav new file mode 100644 index 000000000..fb5639f0d Binary files /dev/null and b/10.0/Apps/Procrastinate/src/Resources/Raw/simon_green.wav differ diff --git a/10.0/Apps/Procrastinate/src/Resources/Raw/simon_red.wav b/10.0/Apps/Procrastinate/src/Resources/Raw/simon_red.wav new file mode 100644 index 000000000..4aee5b69e Binary files /dev/null and b/10.0/Apps/Procrastinate/src/Resources/Raw/simon_red.wav differ diff --git a/10.0/Apps/Procrastinate/src/Resources/Raw/simon_yellow.wav b/10.0/Apps/Procrastinate/src/Resources/Raw/simon_yellow.wav new file mode 100644 index 000000000..3df6829f8 Binary files /dev/null and b/10.0/Apps/Procrastinate/src/Resources/Raw/simon_yellow.wav differ diff --git a/10.0/Apps/Procrastinate/src/Resources/Splash/splash.svg b/10.0/Apps/Procrastinate/src/Resources/Splash/splash.svg new file mode 100644 index 000000000..d4789bcf8 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Resources/Splash/splash.svg @@ -0,0 +1,4 @@ + + + 🦥 + diff --git a/10.0/Apps/Procrastinate/src/Resources/Strings/AppResources.cs.resx b/10.0/Apps/Procrastinate/src/Resources/Strings/AppResources.cs.resx new file mode 100644 index 000000000..4af5dda74 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Resources/Strings/AppResources.cs.resx @@ -0,0 +1,165 @@ + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Prokrastinovat + Nastavení + Přístupnost + Režim vysokého kontrastu + Použít barvy s vyšším kontrastem + Náhled motivu + Aktuální: {0} motiv + Výchozí + Vysoký kontrast + Změny se projeví okamžitě + Jazyk + + Dnešní úkoly + Váš seznam produktivity: + Dát si pauzu + Jde vám to skvěle! Dnes jen 1 úkol! + Přidat další úkoly + 🎉 Gratulujeme! Dokončili jste VŠECHNY své úkoly! + Počkejte... stále potřebujete další pauzu! + + Mini-hry + Protože produktivita je přeceňovaná! + Zamíchat hry + Skóre: {0} + Konec hry! Finální skóre: {0} + Začít + Nová hra + Váš tah (X) + AI přemýšlí... + Vyhráli jste! 🎉 + AI vyhrála! 🤖 + Remíza! + Kliknutí: {0} + Začít výzvu + Čekejte na zelenou! + KLEPNĚTE! + Příliš brzy! Zkuste znovu. + Reakční čas: {0}ms + Klepněte na 'Začít' + + Generátor výmluv + Potřebujete důvod něco nedělat? Pomůžeme! + Klepněte pro novou výmluvu! + Generovat výmluvu + Kopírovat + Zkopírováno! + Dnes vygenerované výmluvy: {0} + + Vaše statistiky + Buďte hrdí na své úspěchy! + Vyhnuté úkoly + Pauzy + Vygenerované výmluvy + Odehrané hry + Odemčený úspěch + Začátek: Otevřít aplikaci! + Začínající prokrastinátor + LEGENDÁRNÍ PROKRASTINÁTOR + + Šimon říká + Sledujte vzor a opakujte! + Rychlost klikání + Kolik kliknutí za 5 sekund? + Reakční čas + Čekejte na zelenou a klepněte! + Piškvorky + Klasická hra X a O! + Hledání min + Najděte všechna bezpečná pole! + Naklápěcí had + Nakloňte telefon a veďte hada! + Pexeso + Najděte dvojice! + Hádej číslo + Hádejte číslo 1-100! + Mlátička krtků + Mlať krtky co nejrychleji! + + + Klikni na mě! + Konec: {0} kliknutí! ({1:F1}/sek) + Čekej... + Start + Příliš brzy! + Hotovo! + Najdi bezpečná pole! ({0} min) + Bum! Konec hry 💥 + Nakloň pro pohyb! + Akcelerometr není k dispozici + Konec hry! Skóre: {0} + Tahy: {0} | Páry: {1}/{2} + Vyhráno za {0} tahů! 🎉 + Myslím na číslo 1-100... + Pokusy: {0} + Zadej svůj tip + Hádat! + Zadej číslo 1-100 + 🎉 Správně! Bylo to {0}! + 📈 {0} je příliš NÍZKÉ! + 📉 {0} je příliš VYSOKÉ! + Skóre: {0} | Minuto: {1} + Spustit hru (30s) + + + Motor výmluv + Vyberte, jak se generují výmluvy + Náhodný generátor + Cloud AI (Groq) + API Endpoint + http://localhost:11434 + AI Model + Generuji... + Groq API klíč + Zadejte svůj Groq API klíč + Získejte svůj klíč zdarma na console.groq.com + AI na zařízení (Apple) + Používá Apple Intelligence na iOS 26+. Bez internetu, zcela soukromé. + Apple Intelligence je připravena! + Vyžaduje iOS 26+ s povolenou Apple Intelligence + + Dát si šlofíka + + + Dát si kafe + + + Protáhnout se + + + Zírat na strop + + + O aplikaci + + + Ultimátní anti-produktivní aplikace. Postavena s .NET MAUI a zdravým pohrdáním termíny. + + + Vytvořil Stephane Delcroix + + Sdílet + Sdílet tuto výmluvu + Posledních 7 Dní + Vygenerovat kreslený obrázek + Nepodařilo se vygenerovat obrázek. Zkuste to později. + diff --git a/10.0/Apps/Procrastinate/src/Resources/Strings/AppResources.es.resx b/10.0/Apps/Procrastinate/src/Resources/Strings/AppResources.es.resx new file mode 100644 index 000000000..9bf731f4f --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Resources/Strings/AppResources.es.resx @@ -0,0 +1,165 @@ + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Procrastinar + Ajustes + Accesibilidad + Modo alto contraste + Usar colores de mayor contraste + Vista previa del tema + Actual: Tema {0} + Predeterminado + Alto contraste + Los cambios se aplican inmediatamente + Idioma + + Tareas de hoy + Tu lista de productividad: + Tomar un descanso + ¡Lo estás haciendo genial! ¡Solo 1 tarea hoy! + Añadir más tareas + 🎉 ¡Felicidades! ¡Completaste TODAS tus tareas! + Espera... ¡aún necesitas otro descanso! + + Mini-Juegos + ¡Porque la productividad está sobrevalorada! + Mezclar juegos + Puntuación: {0} + ¡Fin del juego! Puntuación final: {0} + Empezar + Nuevo juego + Tu turno (X) + La IA está pensando... + ¡Ganaste! 🎉 + ¡La IA gana! 🤖 + ¡Empate! + Clics: {0} + Empezar desafío + ¡Espera el verde! + ¡TOCA AHORA! + ¡Muy pronto! Intenta de nuevo. + Tiempo de reacción: {0}ms + Toca 'Empezar' para comenzar + + Generador de excusas + ¿Necesitas una razón para no hacer algo? ¡Te ayudamos! + ¡Toca el botón para una nueva excusa! + Generar excusa + Copiar + ¡Copiado! + Excusas generadas hoy: {0} + + Tus estadísticas + ¡Siéntete orgulloso de tus logros! + Tareas evitadas + Descansos tomados + Excusas generadas + Juegos jugados + Logro desbloqueado + Inicio: ¡Abrir la app! + Procrastinador principiante + PROCRASTINADOR LEGENDARIO + + Simón dice + ¡Observa el patrón y repítelo! + Velocidad de clic + ¿Cuántos clics en 5 segundos? + Tiempo de reacción + ¡Espera el verde y toca! + Tres en raya + ¡El clásico X y O! + Buscaminas + ¡Encuentra las celdas seguras! + Serpiente inclinable + ¡Inclina el teléfono para guiar la serpiente! + Memoria + ¡Encuentra los pares! + Adivina el número + ¡Adivina el número 1-100! + Golpea al topo + ¡Golpea los topos lo más rápido posible! + + + ¡Haz clic! + Final: {0} clics! ({1:F1}/seg) + Espera... + Iniciar + ¡Muy pronto! + ¡Listo! + ¡Encuentra las celdas seguras! ({0} minas) + ¡Boom! Fin del juego 💥 + ¡Inclina para mover! + Acelerómetro no disponible + ¡Fin del juego! Puntuación: {0} + Movimientos: {0} | Pares: {1}/{2} + ¡Ganaste en {0} movimientos! 🎉 + Estoy pensando en un número 1-100... + Intentos: {0} + Ingresa tu estimación + ¡Adivinar! + Ingresa un número 1-100 + 🎉 ¡Correcto! ¡Era {0}! + 📈 ¡{0} es muy BAJO! + 📉 ¡{0} es muy ALTO! + Puntuación: {0} | Fallos: {1} + Iniciar Juego (30s) + + + Motor de excusas + Elige cómo se generan las excusas + Generador aleatorio + IA en la nube (Groq) + Punto de acceso API + http://localhost:11434 + Modelo de IA + Generando... + Clave API Groq + Ingresa tu clave API Groq + Obtén tu clave gratis en console.groq.com + IA en dispositivo (Apple) + Usa Apple Intelligence en iOS 26+. Sin internet, completamente privado. + ¡Apple Intelligence está listo! + Requiere iOS 26+ con Apple Intelligence activado + + Echarse una siesta + + + Tomar un café + + + Estirarse un poco + + + Mirar al techo + + + Acerca de + + + La aplicación anti-productividad definitiva. Construida con .NET MAUI y un sano desprecio por los plazos. + + + Hecho por Stephane Delcroix + + Compartir + Compartir esta excusa + Últimos 7 Días + Generar dibujo + No se pudo generar la imagen. Inténtalo más tarde. + diff --git a/10.0/Apps/Procrastinate/src/Resources/Strings/AppResources.fr.resx b/10.0/Apps/Procrastinate/src/Resources/Strings/AppResources.fr.resx new file mode 100644 index 000000000..801477a20 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Resources/Strings/AppResources.fr.resx @@ -0,0 +1,165 @@ + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Procrastiner + Paramètres + Accessibilité + Mode contraste élevé + Utiliser des couleurs plus contrastées + Aperçu du thème + Actuel: Thème {0} + Par défaut + Contraste élevé + Les changements s'appliquent immédiatement + Langue + + Tâches du jour + Votre liste de productivité: + Prendre une pause + Vous êtes super! Une seule tâche aujourd'hui! + Ajouter des tâches + 🎉 Félicitations! Vous avez terminé TOUTES vos tâches! + Attendez... vous avez encore besoin d'une pause! + + Mini-Jeux + Parce que la productivité est surfaite! + Mélanger les jeux + Score: {0} + Fin de partie! Score final: {0} + Commencer + Nouvelle partie + Votre tour (X) + L'IA réfléchit... + Vous avez gagné! 🎉 + L'IA gagne! 🤖 + Match nul! + Clics: {0} + Commencer le défi + Attendez le vert! + APPUYEZ! + Trop tôt! Réessayez. + Temps de réaction: {0}ms + Appuyez sur 'Commencer' + + Générateur d'excuses + Besoin d'une raison pour ne rien faire? On vous aide! + Appuyez pour une nouvelle excuse! + Générer une excuse + Copier + Copié! + Excuses générées aujourd'hui: {0} + + Vos statistiques + Soyez fier de vos accomplissements! + Tâches évitées + Pauses prises + Excuses générées + Jeux joués + Succès débloqué + Début: Ouvrir l'appli! + Procrastinateur débutant + PROCRASTINATEUR LÉGENDAIRE + + Jacques a dit + Observez et répétez le motif! + Vitesse de clic + Combien de clics en 5 secondes? + Temps de réaction + Attendez le vert, puis appuyez! + Morpion + Le classique X et O! + Démineur + Trouvez les cases sûres! + Serpent inclinable + Inclinez pour guider le serpent! + Memory + Trouvez les paires! + Devinez le nombre + Devinez le nombre 1-100! + Tape-taupe + Tapez les taupes rapidement! + + + Cliquez-moi! + Final: {0} clics! ({1:F1}/sec) + Attendez... + Démarrer + Trop tôt! + Terminé! + Trouvez les cellules sûres! ({0} mines) + Boom! Partie terminée 💥 + Inclinez pour bouger! + Accéléromètre non disponible + Partie terminée! Score: {0} + Coups: {0} | Paires: {1}/{2} + Gagné en {0} coups! 🎉 + Je pense à un nombre 1-100... + Tentatives: {0} + Entrez votre estimation + Deviner! + Entrez un nombre 1-100 + 🎉 Correct! C'était {0}! + 📈 {0} est trop BAS! + 📉 {0} est trop HAUT! + Score: {0} | Ratés: {1} + Démarrer (30s) + + + Moteur d'excuses + Choisissez comment les excuses sont générées + Générateur aléatoire + IA Cloud (Groq) + Point d'accès API + http://localhost:11434 + Modèle IA + Génération... + Clé API Groq + Entrez votre clé API Groq + Obtenez votre clé gratuite sur console.groq.com + IA sur l'appareil (Apple) + Utilise Apple Intelligence sur iOS 26+. Aucune connexion internet requise, totalement privé. + Apple Intelligence est prêt! + Nécessite iOS 26+ avec Apple Intelligence activé + + Faire une sieste + + + Prendre un café + + + S'étirer un peu + + + Fixer le plafond + + + À propos + + + L'application anti-productivité ultime. Construite avec .NET MAUI et un sain mépris des délais. + + + Créé par Stephane Delcroix + + Partager + Partager cette excuse + 7 Derniers Jours + Générer un dessin + Impossible de générer l'image. Réessayez plus tard. + diff --git a/10.0/Apps/Procrastinate/src/Resources/Strings/AppResources.nl.resx b/10.0/Apps/Procrastinate/src/Resources/Strings/AppResources.nl.resx new file mode 100644 index 000000000..ae7ec34a3 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Resources/Strings/AppResources.nl.resx @@ -0,0 +1,165 @@ + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Uitstellen + Instellingen + Toegankelijkheid + Hoog contrast modus + Gebruik kleuren met hoger contrast + Thema voorbeeld + Huidig: {0} Thema + Standaard + Hoog contrast + Wijzigingen worden direct toegepast + Taal + + Taken van vandaag + Je productiviteitslijst: + Neem een pauze + Je doet het geweldig! Maar 1 taak vandaag! + Meer taken toevoegen + 🎉 Gefeliciteerd! Je hebt AL je taken voltooid! + Wacht... je hebt nog een pauze nodig! + + Mini-Spelletjes + Omdat productiviteit overschat wordt! + Spelletjes shuffelen + Score: {0} + Game Over! Eindscore: {0} + Starten + Nieuw spel + Jouw beurt (X) + AI denkt na... + Je hebt gewonnen! 🎉 + AI wint! 🤖 + Gelijkspel! + Klikken: {0} + Start uitdaging + Wacht op groen! + TIK NU! + Te vroeg! Probeer opnieuw. + Reactietijd: {0}ms + Tik op 'Starten' om te beginnen + + Excuusgenerator + Een reden nodig om iets niet te doen? Wij helpen! + Tik voor een nieuw excuus! + Genereer excuus + Kopiëren + Gekopieerd! + Excuses gegenereerd vandaag: {0} + + Jouw statistieken + Wees trots op je prestaties! + Taken vermeden + Pauzes genomen + Excuses gegenereerd + Spelletjes gespeeld + Prestatie ontgrendeld + Begin: Open de app! + Beginner uitstelaar + LEGENDARISCHE UITSTELAAR + + Simon zegt + Kijk naar het patroon en herhaal! + Kliksnelheid + Hoeveel klikken in 5 seconden? + Reactietijd + Wacht op groen en tik! + Boter-kaas-en-eieren + Het klassieke X en O spel! + Mijnenveger + Vind alle veilige cellen! + Kantel slang + Kantel je telefoon om de slang te leiden! + Memory + Vind de paren! + Raad het getal + Raad het getal 1-100! + Mep de mol + Mep de mollen zo snel mogelijk! + + + Klik op mij! + Eindstand: {0} klikken! ({1:F1}/sec) + Wacht... + Start + Te vroeg! + Klaar! + Vind de veilige cellen! ({0} mijnen) + Boem! Spel voorbij 💥 + Kantel om te bewegen! + Accelerometer niet beschikbaar + Spel voorbij! Score: {0} + Zetten: {0} | Paren: {1}/{2} + Gewonnen in {0} zetten! 🎉 + Ik denk aan een getal 1-100... + Pogingen: {0} + Voer je gok in + Raden! + Voer een getal 1-100 in + 🎉 Correct! Het was {0}! + 📈 {0} is te LAAG! + 📉 {0} is te HOOG! + Score: {0} | Gemist: {1} + Start Spel (30s) + + + Excuusgenerator + Kies hoe excuses worden gegenereerd + Willekeurige generator + Cloud AI (Groq) + API Endpoint + http://localhost:11434 + AI Model + Genereren... + Groq API-sleutel + Voer uw Groq API-sleutel in + Haal uw gratis sleutel op console.groq.com + On-Device AI (Apple) + Gebruikt Apple Intelligence op iOS 26+. Geen internet nodig, volledig privé. + Apple Intelligence is klaar! + Vereist iOS 26+ met Apple Intelligence ingeschakeld + + Doe een dutje + + + Pak een koffie + + + Strek je even + + + Staar naar het plafond + + + Over + + + De ultieme anti-productiviteitsapp. Gebouwd met .NET MAUI en een gezonde minachting voor deadlines. + + + Gemaakt door Stephane Delcroix + + Delen + Deel dit excuus + Laatste 7 Dagen + Genereer cartoon + Kon afbeelding niet genereren. Probeer later opnieuw. + diff --git a/10.0/Apps/Procrastinate/src/Resources/Strings/AppResources.pt.resx b/10.0/Apps/Procrastinate/src/Resources/Strings/AppResources.pt.resx new file mode 100644 index 000000000..525cd06a3 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Resources/Strings/AppResources.pt.resx @@ -0,0 +1,165 @@ + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Procrastinar + Configurações + Acessibilidade + Modo alto contraste + Usar cores de maior contraste + Prévia do tema + Atual: Tema {0} + Padrão + Alto contraste + As alterações se aplicam imediatamente + Idioma + + Tarefas de hoje + Sua lista de produtividade: + Fazer uma pausa + Você está indo muito bem! Só 1 tarefa hoje! + Adicionar mais tarefas + 🎉 Parabéns! Você completou TODAS as suas tarefas! + Espere... você ainda precisa de outra pausa! + + Mini-Jogos + Porque produtividade é superestimada! + Embaralhar jogos + Pontuação: {0} + Fim de jogo! Pontuação final: {0} + Começar + Novo jogo + Sua vez (X) + A IA está pensando... + Você ganhou! 🎉 + A IA ganhou! 🤖 + Empate! + Cliques: {0} + Iniciar desafio + Espere pelo verde! + TOQUE AGORA! + Muito cedo! Tente novamente. + Tempo de reação: {0}ms + Toque em 'Começar' para iniciar + + Gerador de desculpas + Precisa de uma razão para não fazer algo? Nós ajudamos! + Toque no botão para uma nova desculpa! + Gerar desculpa + Copiar + Copiado! + Desculpas geradas hoje: {0} + + Suas estatísticas + Tenha orgulho das suas conquistas! + Tarefas evitadas + Pausas feitas + Desculpas geradas + Jogos jogados + Conquista desbloqueada + Início: Abrir o app! + Procrastinador iniciante + PROCRASTINADOR LENDÁRIO + + Simon diz + Observe o padrão e repita! + Velocidade de clique + Quantos cliques em 5 segundos? + Tempo de reação + Espere pelo verde e toque! + Jogo da velha + O clássico X e O! + Campo minado + Encontre as células seguras! + Cobra inclinável + Incline o telefone para guiar a cobra! + Jogo da memória + Encontre os pares! + Adivinhe o número + Adivinhe o número 1-100! + Acerte a toupeira + Acerte as toupeiras o mais rápido possível! + + + Clique em mim! + Final: {0} cliques! ({1:F1}/seg) + Aguarde... + Iniciar + Muito cedo! + Pronto! + Encontre as células seguras! ({0} minas) + Boom! Fim de jogo 💥 + Incline para mover! + Acelerômetro não disponível + Fim de jogo! Pontuação: {0} + Movimentos: {0} | Pares: {1}/{2} + Você ganhou em {0} movimentos! 🎉 + Estou pensando em um número 1-100... + Tentativas: {0} + Digite seu palpite + Adivinhar! + Digite um número 1-100 + 🎉 Correto! Era {0}! + 📈 {0} é muito BAIXO! + 📉 {0} é muito ALTO! + Pontuação: {0} | Erros: {1} + Iniciar Jogo (30s) + + + Motor de desculpas + Escolha como as desculpas são geradas + Gerador aleatório + IA na nuvem (Groq) + Endpoint da API + http://localhost:11434 + Modelo de IA + Gerando... + Chave API Groq + Digite sua chave API Groq + Obtenha sua chave grátis em console.groq.com + IA no dispositivo (Apple) + Usa Apple Intelligence no iOS 26+. Sem internet, totalmente privado. + Apple Intelligence está pronto! + Requer iOS 26+ com Apple Intelligence ativado + + Tirar uma soneca + + + Tomar um café + + + Alongar um pouco + + + Olhar para o teto + + + Sobre + + + O aplicativo anti-produtividade definitivo. Construído com .NET MAUI e um saudável desprezo por prazos. + + + Feito por Stephane Delcroix + + Compartilhar + Compartilhar esta desculpa + Últimos 7 Dias + Gerar desenho + Não foi possível gerar a imagem. Tente novamente mais tarde. + diff --git a/10.0/Apps/Procrastinate/src/Resources/Strings/AppResources.resx b/10.0/Apps/Procrastinate/src/Resources/Strings/AppResources.resx new file mode 100644 index 000000000..4233890bd --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Resources/Strings/AppResources.resx @@ -0,0 +1,193 @@ + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Procrastinate + Settings + Tasks + Games + Excuses + Stats + Appearance + Light Theme + Switch to Nord Light color scheme + Dark + Light + Zalgo Randomly + Chaos appears when you least expect it + Theme Preview + Current: Nord {0} + Changes apply immediately + Language + + + Today's Tasks + Your productivity list: + Take a break + You're doing great! Only 1 task today! + Add More Tasks + 🎉 Congratulations! You completed ALL your tasks! + Wait... you still need another break! + + + Mini-Games + Because productivity is overrated! + Shuffle Games + Score: {0} + Game Over! Final Score: {0} + Start Game + New Game + Your turn (X) + AI thinking... + You win! 🎉 + AI wins! 🤖 + It's a draw! + Clicks: {0} + Start Challenge + Wait for green! + TAP NOW! + Too early! Try again. + Reaction time: {0}ms + Minifig Creator + Generate random minifigures! + Generate + A brave adventurer from Brickland! + Expert builder with 10 years experience. + Dreams of becoming a master builder. + Collector of rare bricks since childhood. + Known for incredible MOC creations. + Eggs Catch + Catch falling eggs with your basket! + Tap 'Start' to begin + + + Excuse Generator + Need a reason to not do something? We got you! + Tap the button for a fresh excuse! + Generate Excuse + Copy to Clipboard + Copied! + Excuses generated today: {0} + + + Your Stats + Be proud of your accomplishments! + Tasks Avoided + Breaks Taken + Excuses Generated + Games Played + Achievement Unlocked + Getting Started: Open the app! + Beginner Procrastinator + LEGENDARY PROCRASTINATOR + + + Simon Says + Watch the pattern and repeat it! + Click Speed + How many clicks in 5 seconds? + Reaction Time + Wait for green, then tap! + Tic Tac Toe + Classic X and O game! + Minesweeper + Find all safe cells! + Tilt Snake + Tilt your phone to guide the snake! + Memory Match + Find matching pairs! + Number Guess + Guess the number 1-100! + Whack-a-Mole + Tap the moles as fast as you can! + + + Click Me! + Final: {0} clicks! ({1:F1}/sec) + Wait... + Start + Too soon! + Done! + Find the safe cells! ({0} mines) + Boom! Game over 💥 + Tilt phone to move! + Accelerometer not available + Game Over! Score: {0} + Moves: {0} | Pairs: {1}/{2} + You win in {0} moves! 🎉 + I'm thinking of a number 1-100... + Attempts: {0} + Enter your guess + Guess! + Please enter a number 1-100 + 🎉 Correct! It was {0}! + 📈 {0} is too LOW! + 📉 {0} is too HIGH! + Score: {0} | Misses: {1} + Start Game (30s) + + + Excuse Engine + Choose how excuses are generated + Random Generator + Cloud AI (Groq) + API Endpoint + http://localhost:11434 + AI Model + Generating... + Groq API Key + Enter your Groq API key + Get your free API key at console.groq.com + On-Device AI (Apple) + Uses Apple Intelligence on iOS 26+. No internet required, completely private. + Apple Intelligence is ready! + Requires iOS 26+ with Apple Intelligence enabled + + + AI API Calls + High Scores + No high scores yet. Play some games! + + Take a nap + + + Grab a coffee + + + Stretch a bit + + + Stare at the ceiling + + + About + + + The ultimate anti-productivity app. Built with .NET MAUI and a healthy disregard for deadlines. + + + Made by Stephane Delcroix + + Share + Share this excuse + Last 7 Days + Total Clicks + Generate Cartoon + Could not generate image. Try again later. + diff --git a/10.0/Apps/Procrastinate/src/Resources/Strings/AppResources.uk.resx b/10.0/Apps/Procrastinate/src/Resources/Strings/AppResources.uk.resx new file mode 100644 index 000000000..5cd44e8c4 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Resources/Strings/AppResources.uk.resx @@ -0,0 +1,211 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Прокрастинація + Налаштування + Завдання + Ігри + Виправдання + Статистика + Зовнішній вигляд + Світла тема + Перемкнутися на світлу колірну схему Nord + Темна + Світла + Випадковий Zalgo + Хаос з'являється, коли ви найменше на це очікуєте + Попередній перегляд теми + Поточна: Nord {0} + Зміни застосовуються миттєво + Мова + + Завдання на сьогодні + Ваш список продуктивності: + Зробіть перерву + Ви чудово справляєтесь! Лише 1 завдання на сьогодні! + Додати більше завдань + 🎉 Вітаємо! Ви виконали ВСІ свої завдання! + Зачекайте... вам потрібна ще одна перерва! + + Міні-ігри + Бо продуктивність — це переоцінена річ! + Перемішати ігри + Рахунок: {0} + Гра закінчена! Фінальний рахунок: {0} + Почати гру + Нова гра + Ваш хід (X) + ШІ думає... + Ви перемогли! 🎉 + ШІ переміг! 🤖 + Нічия! + Кліки: {0} + Почати виклик + Чекайте на зелений! + ТИСНІТЬ ЗАРАЗ! + Занадто рано! Спробуйте ще раз. + Час реакції: {0} мс + Творець мініфігурок + Створюйте випадкові мініфігурки! + Згенерувати + Хоробрий шукач пригод із Брікленда! + Експерт-будівельник з 10-річним досвідом. + Мріє стати майстром-будівельником. + Колекціонер рідкісних цеглинок з дитинства. + Відомий своїми неймовірними MOC-творіннями. + Лови яйця + Ловіть яйця, що падають, у свій кошик! + Натисніть «Старт», щоб почати + + Генератор виправдань + Потрібна причина, щоб нічого не робити? Ми допоможемо! + Натисніть кнопку, щоб отримати нове виправдання! + Згенерувати виправдання + Копіювати в буфер + Скопійовано! + Виправдань згенеровано сьогодні: {0} + + Ваша статистика + Пишайтеся своїми досягненнями! + Проігноровані завдання + Зроблені перерви + Згенеровані виправдання + Зіграні ігри + Досягнення розблоковано + Початок покладено: Відкрито додаток! + Прокрастинатор-початківець + ЛЕГЕНДАРНИЙ ПРОКРАСТИНАТОР + + Саймон каже + Спостерігайте за послідовністю та повторюйте її! + Швидкість кліків + Скільки кліків за 5 секунд? + Час реакції + Чекайте на зелений, потім тисніть! + Хрестики-нулики + Класична гра в хрестики та нулики! + Сапер + Знайдіть усі безпечні клітинки! + Змійка нахилом + Нахиляйте телефон, щоб керувати змійкою! + Пари пам'яті + Знайдіть однакові пари! + Вгадай число + Вгадайте число від 1 до 100! + Впіймай крота + Тицяйте на кротів якомога швидше! + + Тисни мене! + Разом: {0} кліків! ({1:F1}/сек) + Чекайте... + Старт + Занадто рано! + Готово! + Знайдіть безпечні клітинки! ({0} мін) + Бум! Гру закінчено 💥 + Нахиляйте телефон для руху! + Акселерометр недоступний + Гра закінчена! Рахунок: {0} + Ходи: {0} | Пари: {1}/{2} + Ви перемогли за {0} ходів! 🎉 + Я загадав число від 1 до 100... + Спроби: {0} + Введіть число + Вгадати! + Будь ласка, введіть число від 1 до 100 + 🎉 Правильно! Це було {0}! + 📈 {0} — це замало! + 📉 {0} — це забагато! + Рахунок: {0} | Промахи: {1} + Почати гру (30с) + + Рушій виправдань + Оберіть, як генеруються виправдання + Випадковий генератор + Хмарний ШІ (Groq) + Кінцева точка API + http://localhost:11434 + Модель ШІ + Генерація... + Ключ API Groq + Введіть ваш API ключ Groq + Отримайте безкоштовний ключ на console.groq.com + Локальний ШІ (Apple) + Використовує Apple Intelligence на iOS 26+. Інтернет не потрібен, повна приватність. + Apple Intelligence готовий до роботи! + Потрібна iOS 26+ з увімкненим Apple Intelligence + + Запити до AI API + Рекорди + Рекордів ще немає. Зіграйте в якусь гру! + Поспіть трохи + Випийте кави + Трохи розімніться + Подивіться у стелю + Про програму + Найкращий антипродуктивний додаток. Побудований на .NET MAUI з повним ігноруванням дедлайнів. + Автор: Стефан Делькруа (Stephane Delcroix) + Поділитися + Поділитися цим виправданням + Останні 7 днів + Згенерувати комікс + Не вдалося згенерувати зображення. Спробуйте пізніше. + \ No newline at end of file diff --git a/10.0/Apps/Procrastinate/src/Resources/Styles/Colors.xaml b/10.0/Apps/Procrastinate/src/Resources/Styles/Colors.xaml new file mode 100644 index 000000000..11f4cb930 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Resources/Styles/Colors.xaml @@ -0,0 +1,44 @@ + + + + + + + + #2E3440 + #3B4252 + #434C5E + #4C566A + + + #D8DEE9 + #E5E9F0 + #ECEFF4 + + + #8FBCBB + #88C0D0 + #81A1C1 + #5E81AC + + + #BF616A + #D08770 + #EBCB8B + #A3BE8C + #B48EAD + + + #FFFFFF + #000000 + Transparent + + + + + + + + \ No newline at end of file diff --git a/10.0/Apps/Procrastinate/src/Resources/Styles/Styles.xaml b/10.0/Apps/Procrastinate/src/Resources/Styles/Styles.xaml new file mode 100644 index 000000000..927520553 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Resources/Styles/Styles.xaml @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/10.0/Apps/Procrastinate/src/Services/AgentPipelineExcuseGenerator.cs b/10.0/Apps/Procrastinate/src/Services/AgentPipelineExcuseGenerator.cs new file mode 100644 index 000000000..93990a3fe --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Services/AgentPipelineExcuseGenerator.cs @@ -0,0 +1,159 @@ +using System.Diagnostics; +using Microsoft.Extensions.AI; + +namespace procrastinate.Services; + +/// +/// Multi-agent excuse pipeline: Researcher → Writer → Editor. +/// Each agent is a separate IChatClient call, chained sequentially. +/// Reports progress via the OnStageChanged callback. +/// +public class AgentPipelineExcuseGenerator : IExcuseGenerator +{ + private readonly IChatClient? _onDeviceChatClient; + + public string Name => "AI Agent Pipeline"; + public bool IsAvailable => _onDeviceChatClient is not null || CloudExcuseGenerator.IsCloudAvailable; + + /// + /// Callback fired when the pipeline moves to a new stage. + /// + public Action? OnStageChanged { get; set; } + + /// + /// Callback fired when an agent produces output. (stageName, agentOutput) + /// + public Action? OnAgentOutput { get; set; } + + public AgentPipelineExcuseGenerator(IChatClient? onDeviceChatClient = null) + { + _onDeviceChatClient = onDeviceChatClient; + } + + public async Task GenerateExcuseAsync(string language) + { + var stopwatch = Stopwatch.StartNew(); + + IChatClient? client = _onDeviceChatClient; + IChatClient? disposableClient = null; + var modelName = "Apple Intelligence"; + + if (client is null && CloudExcuseGenerator.IsCloudAvailable) + { + disposableClient = CloudExcuseGenerator.CreateChatClient(); + client = disposableClient; + modelName = "Cloud AI"; + } + + if (client is null) + { + stopwatch.Stop(); + return new ExcuseResult("No AI available. Configure Cloud AI or use a device with Apple Intelligence.", Name, stopwatch.Elapsed); + } + + try + { + var languageName = GetLanguageName(language); + + // Agent 1: Researcher — picks an absurd scenario + OnStageChanged?.Invoke("🔍 Agent 1: Researching..."); + var scenario = await RunAgentAsync(client, + "You are a creative comedy researcher. Your job is to come up with an absurd, unexpected scenario that could be used as an excuse. Output ONLY the scenario in 1-2 sentences. Be wildly creative.", + $"Come up with a bizarre, funny scenario involving {GetRandomElement()}. Make it unexpected and absurd. Just the scenario, nothing else."); + OnAgentOutput?.Invoke("🔍 Researcher", scenario); + + // Agent 2: Writer — crafts the excuse from the scenario + OnStageChanged?.Invoke("✍️ Agent 2: Writing..."); + var rawExcuse = await RunAgentAsync(client, + "You are a comedy writer. You turn scenarios into first-person excuses that sound like something a real person would say. Keep it to 1-2 sentences. Start with 'I' or 'Sorry'.", + $"Turn this scenario into a funny first-person excuse in {languageName}:\n\n{scenario}\n\nJust the excuse, nothing else."); + OnAgentOutput?.Invoke("✍️ Writer", rawExcuse); + + // Agent 3: Editor — polishes and ensures quality + OnStageChanged?.Invoke("✨ Agent 3: Polishing..."); + var finalExcuse = await RunAgentAsync(client, + $"You are a comedy editor. You polish excuses to be funnier and more natural-sounding. Keep the same language ({languageName}). Output ONLY the polished excuse.", + $"Polish this excuse to be funnier and more natural. Keep it in {languageName}. Keep it to 1-2 sentences:\n\n{rawExcuse}\n\nJust the polished excuse, nothing else."); + OnAgentOutput?.Invoke("✨ Editor", finalExcuse.Trim()); + + OnStageChanged?.Invoke("✅ Done!"); + stopwatch.Stop(); + + return new ExcuseResult( + finalExcuse.Trim(), + Name, + stopwatch.Elapsed, + Model: $"{modelName} (3 agents)"); + } + catch (Exception ex) + { + stopwatch.Stop(); + System.Diagnostics.Debug.WriteLine($"Pipeline error: {ex}"); + + var friendly = ex.Message switch + { + string m when m.Contains("content", StringComparison.OrdinalIgnoreCase) && + m.Contains("unsafe", StringComparison.OrdinalIgnoreCase) + => "The AI thought that excuse was too spicy! Try again for a tamer one. 🌶️", + string m when m.Contains("content", StringComparison.OrdinalIgnoreCase) && + m.Contains("filter", StringComparison.OrdinalIgnoreCase) + => "The AI's content filter kicked in — apparently that excuse was TOO creative. Try again! 🎨", + string m when m.Contains("timeout", StringComparison.OrdinalIgnoreCase) || + m.Contains("timed out", StringComparison.OrdinalIgnoreCase) + => "Even the AI is procrastinating! It took too long. Try again. ⏰", + string m when m.Contains("network", StringComparison.OrdinalIgnoreCase) || + m.Contains("connection", StringComparison.OrdinalIgnoreCase) + => "No connection — the AI agents are on a coffee break. Check your network. ☕", + string m when m.Contains("rate limit", StringComparison.OrdinalIgnoreCase) || + m.Contains("429", StringComparison.OrdinalIgnoreCase) + => "Too many excuses requested! The AI needs a breather. Wait a moment. 😮‍💨", + _ => "The excuse factory had a hiccup. Give it another shot! 🏭" + }; + + return new ExcuseResult(friendly, Name, stopwatch.Elapsed); + } + finally + { + (disposableClient as IDisposable)?.Dispose(); + } + } + + private static async Task RunAgentAsync(IChatClient client, string systemPrompt, string userPrompt) + { + var messages = new List + { + new(ChatRole.System, systemPrompt), + new(ChatRole.User, userPrompt) + }; + + var response = await client.GetResponseAsync(messages); + return response.Text?.Trim() ?? ""; + } + + private static string GetRandomElement() + { + var elements = new[] + { + "a time-traveling pigeon", "sentient office furniture", "a secret society of squirrels", + "a parallel universe where gravity is optional", "a conspiracy involving socks", + "an AI that became a life coach", "a haunted coffee machine", "quantum entangled twins", + "a diplomatic incident with a penguin", "a rogue weather satellite", + "an enchanted parking meter", "a philosophical debate with a cat", + "a mysterious portal in the closet", "an accidental invention", "a cursed alarm clock", + "a runaway sourdough starter", "an overly helpful robot vacuum", + "a neighborhood raccoon uprising", "a telepathic houseplant", "a glitch in spacetime" + }; + return elements[Random.Shared.Next(elements.Length)]; + } + + private static string GetLanguageName(string language) => language switch + { + "fr" => "French", + "es" => "Spanish", + "pt" => "Portuguese", + "nl" => "Dutch", + "cs" => "Czech", + "uk" => "Ukrainian", + _ => "English" + }; +} diff --git a/10.0/Apps/Procrastinate/src/Services/AppStrings.cs b/10.0/Apps/Procrastinate/src/Services/AppStrings.cs new file mode 100644 index 000000000..0507b248c --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Services/AppStrings.cs @@ -0,0 +1,263 @@ +using System.ComponentModel; +using System.Globalization; +using System.Resources; + +namespace procrastinate.Services; + +public class AppStrings : INotifyPropertyChanged +{ + private static readonly Lazy _instance = new(() => new AppStrings()); + public static AppStrings Instance => _instance.Value; + + private static readonly ResourceManager _resourceManager = + new("procrastinate.Resources.Strings.AppResources", typeof(AppStrings).Assembly); + + private CultureInfo _culture; + private bool _zalgoMode; + + // Zalgo combining characters + private static readonly char[] ZalgoUp = { + '\u0300', '\u0301', '\u0302', '\u0303', '\u0304', '\u0305', '\u0306', '\u0307', + '\u0308', '\u0309', '\u030a', '\u030b', '\u030c', '\u030d', '\u030e', '\u030f', + '\u0310', '\u0311', '\u0312', '\u0313', '\u0314', '\u0315', '\u031a', '\u031b', + '\u033d', '\u033e', '\u033f', '\u0340', '\u0341', '\u0342', '\u0343', '\u0344' + }; + private static readonly char[] ZalgoDown = { + '\u0316', '\u0317', '\u0318', '\u0319', '\u031c', '\u031d', '\u031e', '\u031f', + '\u0320', '\u0321', '\u0322', '\u0323', '\u0324', '\u0325', '\u0326', '\u0327', + '\u0328', '\u0329', '\u032a', '\u032b', '\u032c', '\u032d', '\u032e', '\u032f', + '\u0330', '\u0331', '\u0332', '\u0333', '\u0339', '\u033a', '\u033b', '\u033c' + }; + private static readonly char[] ZalgoMid = { '\u0334', '\u0335', '\u0336', '\u0337', '\u0338' }; + private static readonly Random _random = new(); + + public event PropertyChangedEventHandler? PropertyChanged; + + public static readonly Dictionary SupportedLanguages = new() + { + { "", "System Default" }, + { "en", "English" }, + { "fr", "Français" }, + { "es", "Español" }, + { "pt", "Português" }, + { "nl", "Nederlands" }, + { "cs", "Čeština" }, + { "uk", "Українська" } + }; + + private AppStrings() + { + var savedLang = Preferences.Get("AppLanguage", ""); + if (string.IsNullOrEmpty(savedLang)) + { + // Use system language, fall back to English if not supported + var systemLang = CultureInfo.CurrentCulture.TwoLetterISOLanguageName; + savedLang = SupportedLanguages.ContainsKey(systemLang) ? systemLang : "en"; + } + _culture = new CultureInfo(savedLang); + _zalgoMode = Preferences.Get("ZalgoMode", true); // Default ON + } + + public static string CurrentLanguage + { + get => Preferences.Get("AppLanguage", ""); + set + { + Preferences.Set("AppLanguage", value); + if (string.IsNullOrEmpty(value)) + { + // Use system language, fall back to English if not supported + var systemLang = CultureInfo.CurrentCulture.TwoLetterISOLanguageName; + var effectiveLang = SupportedLanguages.ContainsKey(systemLang) ? systemLang : "en"; + Instance._culture = new CultureInfo(effectiveLang); + } + else + { + Instance._culture = new CultureInfo(value); + } + Instance.OnPropertyChanged(null); + } + } + + /// + /// Returns the actual language being used (resolves "System Default" to the actual language code) + /// + public static string EffectiveLanguage + { + get + { + var saved = Preferences.Get("AppLanguage", ""); + if (!string.IsNullOrEmpty(saved)) + return saved; + + // Resolve system language + var systemLang = CultureInfo.CurrentCulture.TwoLetterISOLanguageName; + return SupportedLanguages.ContainsKey(systemLang) ? systemLang : "en"; + } + } + + public static bool IsZalgoMode + { + get => Instance._zalgoMode; + set + { + Instance._zalgoMode = value; + Preferences.Set("ZalgoMode", value); + Instance.OnPropertyChanged(null); + } + } + + public string this[string key] => GetString(key); + + public static string Zalgoify(string text) + { + if (string.IsNullOrEmpty(text)) return text; + + var result = new System.Text.StringBuilder(); + bool inPlaceholder = false; + + foreach (char c in text) + { + // Track if we're inside a format placeholder like {0} + if (c == '{') inPlaceholder = true; + else if (c == '}') inPlaceholder = false; + + result.Append(c); + + // Only add zalgo to letters/digits outside of placeholders + if (!inPlaceholder && c != '}' && char.IsLetterOrDigit(c)) + { + // Add 0-2 combining characters above, middle, and below + for (int i = 0; i < _random.Next(0, 3); i++) + result.Append(ZalgoUp[_random.Next(ZalgoUp.Length)]); + for (int i = 0; i < _random.Next(0, 2); i++) + result.Append(ZalgoMid[_random.Next(ZalgoMid.Length)]); + for (int i = 0; i < _random.Next(0, 3); i++) + result.Append(ZalgoDown[_random.Next(ZalgoDown.Length)]); + } + } + return result.ToString(); + } + + // Break message variations - randomly picks one + private static readonly string[] BreakMessageKeys = { "TakeABreak", "TakeANap", "GrabACoffee", "StretchABit", "StareAtCeiling" }; + + private string GetRandomBreakMessage() + { + var key = BreakMessageKeys[_random.Next(BreakMessageKeys.Length)]; + return this[key]; + } + + public static string GetString(string key) + { + var text = _resourceManager.GetString(key, Instance._culture) ?? key; + // When zalgo mode is on, apply randomly 8% of the time for readability + // When off, never apply zalgo + var applyZalgo = Instance._zalgoMode && _random.Next(100) < 8; + return applyZalgo ? Zalgoify(text) : text; + } + + public static string GetString(string key, params object[] args) + { + var format = GetString(key); + return string.Format(format, args); + } + + // For backwards compatibility + public static string Get(string key) => GetString(key); + public static string Get(string key, params object[] args) => GetString(key, args); + + /// + /// Refresh all string bindings to recompute zalgo randomness + /// Call this on navigation, button clicks, etc. + /// + public static void Refresh() + { + Instance.OnPropertyChanged(null); + } + + protected void OnPropertyChanged(string? propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + // Properties for direct XAML binding + public string AppName => this["AppName"]; + public string Settings => this["Settings"]; + public string TabTasks => this["TabTasks"]; + public string TabGames => this["TabGames"]; + public string TabExcuses => this["TabExcuses"]; + public string TabStats => this["TabStats"]; + public string Accessibility => this["Accessibility"]; + public string HighContrastMode => this["HighContrastMode"]; + public string HighContrastDesc => this["HighContrastDesc"]; + public string DefaultTheme => this["DefaultTheme"]; + public string HighContrast => this["HighContrast"]; + public string ZalgoMode => this["ZalgoMode"]; + public string ZalgoModeDesc => this["ZalgoModeDesc"]; + public string ThemePreview => this["ThemePreview"]; + public string ChangesApply => this["ChangesApply"]; + public string Language => this["Language"]; + public string TodaysTasks => this["TodaysTasks"]; + public string YourProductivityList => this["YourProductivityList"]; + public string TakeABreak => GetRandomBreakMessage(); + public string DoingGreat => this["DoingGreat"]; + public string AddMoreTasks => this["AddMoreTasks"]; + public string Congratulations => this["Congratulations"]; + public string NeedAnotherBreak => this["NeedAnotherBreak"]; + public string MiniGames => this["MiniGames"]; + public string ProductivityOverrated => this["ProductivityOverrated"]; + public string ShuffleGames => this["ShuffleGames"]; + public string ExcuseGenerator => this["ExcuseGenerator"]; + public string NeedAReason => this["NeedAReason"]; + public string TapForExcuse => this["TapForExcuse"]; + public string GenerateExcuse => this["GenerateExcuse"]; + public string CopyToClipboard => this["CopyToClipboard"]; + public string YourStats => this["YourStats"]; + public string BeProud => this["BeProud"]; + public string TasksAvoided => this["TasksAvoided"]; + public string BreaksTaken => this["BreaksTaken"]; + public string ExcusesGeneratedStat => this["ExcusesGeneratedStat"]; + public string GamesPlayed => this["GamesPlayed"]; + public string TotalClicks => this["TotalClicks"]; + public string AIExcuseCalls => this["AIExcuseCalls"]; + public string AchievementUnlocked => this["AchievementUnlocked"]; + + // Game strings + public string ClickMe => this["ClickMe"]; + public string StartChallenge => this["StartChallenge"]; + public string Wait => this["Wait"]; + public string Start => this["Start"]; + public string TapStartToBegin => this["TapStartToBegin"]; + public string StartGame => this["StartGame"]; + public string NewGame => this["NewGame"]; + public string ThinkingOfNumber => this["ThinkingOfNumber"]; + public string EnterGuess => this["EnterGuess"]; + public string Guess => this["Guess"]; + public string YourTurn => this["YourTurn"]; + public string TiltToMove => this["TiltToMove"]; + public string StartGame30s => this["StartGame30s"]; + public string Generate => this["Generate"]; + + // Excuse engine settings + public string ExcuseEngine => this["ExcuseEngine"]; + public string ExcuseEngineDesc => this["ExcuseEngineDesc"]; + public string RandomGenerator => this["RandomGenerator"]; + public string CloudAI => this["CloudAI"]; + public string ApiEndpoint => this["ApiEndpoint"]; + public string ApiEndpointPlaceholder => this["ApiEndpointPlaceholder"]; + public string AiModel => this["AiModel"]; + public string Generating => this["Generating"]; + public string GroqApiKeyLabel => this["GroqApiKeyLabel"]; + public string GroqApiKeyPlaceholder => this["GroqApiKeyPlaceholder"]; + public string GroqGetKeyHint => this["GroqGetKeyHint"]; + public string OnDeviceAI => this["OnDeviceAI"]; + public string OnDeviceAIHint => this["OnDeviceAIHint"]; + public string OnDeviceAIAvailable => this["OnDeviceAIAvailable"]; + public string OnDeviceAIUnavailable => this["OnDeviceAIUnavailable"]; + + // About section + public string About => this["About"]; + public string AboutDescription => this["AboutDescription"]; + public string Author => this["Author"]; +} diff --git a/10.0/Apps/Procrastinate/src/Services/ClickTracking.cs b/10.0/Apps/Procrastinate/src/Services/ClickTracking.cs new file mode 100644 index 000000000..7a84c0136 --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Services/ClickTracking.cs @@ -0,0 +1,39 @@ +namespace procrastinate.Services; + +/// +/// Attached property to enable click tracking on buttons. +/// Usage in XAML: services:ClickTracking.IsEnabled="True" +/// +public static class ClickTracking +{ + public static readonly BindableProperty IsEnabledProperty = + BindableProperty.CreateAttached( + "IsEnabled", + typeof(bool), + typeof(ClickTracking), + false, + propertyChanged: OnIsEnabledChanged); + + public static bool GetIsEnabled(BindableObject view) => (bool)view.GetValue(IsEnabledProperty); + public static void SetIsEnabled(BindableObject view, bool value) => view.SetValue(IsEnabledProperty, value); + + private static void OnIsEnabledChanged(BindableObject bindable, object oldValue, object newValue) + { + if (bindable is Button button) + { + if ((bool)newValue) + { + button.Clicked += OnButtonClicked; + } + else + { + button.Clicked -= OnButtonClicked; + } + } + } + + private static void OnButtonClicked(object? sender, EventArgs e) + { + StatsService.Instance?.IncrementClicks(); + } +} diff --git a/10.0/Apps/Procrastinate/src/Services/ClickTrackingBehavior.cs b/10.0/Apps/Procrastinate/src/Services/ClickTrackingBehavior.cs new file mode 100644 index 000000000..fcec7c54d --- /dev/null +++ b/10.0/Apps/Procrastinate/src/Services/ClickTrackingBehavior.cs @@ -0,0 +1,21 @@ +namespace procrastinate.Services; + +public class ClickTrackingBehavior : Behavior