├── .gitignore
├── sc.sh
├── Praeclarum Resources.sketch
├── global.json
├── Praeclarum.Android
├── Resources
│ ├── values
│ │ └── Strings.xml
│ └── AboutResources.txt
├── UI
│ ├── DocumentListAppActivity.cs
│ ├── DocumentEditor.cs
│ └── Canvas.cs
├── Praeclarum.Android.csproj
└── App
│ └── TextDocument.cs
├── Praeclarum
├── UI
│ ├── IThemeAware.cs
│ ├── UserInterface.cs
│ ├── Theme.cs
│ ├── ITimer.cs
│ ├── IView.cs
│ ├── ITextEditor.cs
│ ├── IDocumentEditor.cs
│ ├── ICanvas.cs
│ ├── IDocumentsView.cs
│ ├── OpenUrlCommand.cs
│ └── PForm.cs
├── App
│ ├── ProPriceSpec.cs
│ ├── IDocument.cs
│ ├── Application.cs
│ ├── IAppSettings.cs
│ ├── AIChat.cs
│ ├── DocumentApplication.cs
│ ├── Document.cs
│ ├── StoreManager.cs
│ ├── DocumentAppSettings.cs
│ └── DocumentReference.cs
├── IO
│ ├── IConsole.cs
│ ├── FileSystemManager.cs
│ └── EmptyFileSystem.cs
├── StringHelper.cs
├── Command.cs
├── Graphics
│ ├── Vector.cs
│ ├── Rectangle.cs
│ ├── NullGraphics.cs
│ └── Point.cs
├── Praeclarum.Shared.shproj
├── Properties
│ └── AssemblyInfo.cs
├── AsyncExtensions.cs
├── Localization.cs
├── Praeclarum.csproj
├── StringRange.cs
├── Praeclarum.Shared.projitems
├── Log.cs
├── ListDiff.cs
└── NumberFormatting.cs
├── README.md
├── Praeclarum.iOS
├── GlobalSuppressions.cs
├── UI
│ ├── ViewAnimation.cs
│ ├── ProceduralImage.cs
│ ├── DocumentThumbnailsAppDelegate.cs
│ ├── StorageSection.cs
│ ├── AnalyticsSection.cs
│ ├── DarkModeSection.cs
│ ├── SelectableButtonItem.cs
│ ├── TitleView.cs
│ ├── Editor.cs
│ ├── ActivityIndicator.cs
│ ├── GalleryViewController.cs
│ ├── TranslateSection.cs
│ ├── MoveDocumentsForm.cs
│ ├── OldForm.cs
│ ├── DocumentEditor.cs
│ ├── Canvas.cs
│ ├── ImageCache.cs
│ ├── DocumentListAppDelegate.cs
│ ├── ScrollableCanvas.cs
│ ├── StorageForm.cs
│ └── TextInputController.cs
├── App
│ ├── ReviewNagging.cs
│ └── TextDocument.cs
├── NSMutableAttributedStringWrapper.cs
└── Praeclarum.iOS.csproj
├── Praeclarum.Mac
├── UI
│ ├── UserInterfaceWindow.cs
│ ├── Timer.cs
│ ├── Canvas.cs
│ └── DocumentAppDelegate.cs
├── Praeclarum.Mac.Shared.projitems
├── Praeclarum.Mac.Shared.shproj
└── Praeclarum.Mac.csproj
├── Praeclarum.Net.sln
├── .github
└── workflows
│ └── build.yml
├── Directory.Packages.props
├── Praeclarum.Utilities
└── Praeclarum.Utilities.csproj
├── Praeclarum.Utilities.sln
└── Praeclarum.sln
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | bin
3 | obj
4 |
5 | *.userprefs
6 | .vs
7 |
--------------------------------------------------------------------------------
/sc.sh:
--------------------------------------------------------------------------------
1 | fsharpi --exec ../StopCrashing/StopCrashing.fsx Praeclarum.sln
2 |
--------------------------------------------------------------------------------
/Praeclarum Resources.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/praeclarum/Praeclarum/HEAD/Praeclarum Resources.sketch
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "10.0.100-rc.1.25451.107",
4 | "workloadVersion": "10.0.100-rc.1.25458.2"
5 | }
6 | }
--------------------------------------------------------------------------------
/Praeclarum.Android/Resources/values/Strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Praeclarum.Android
4 |
5 |
--------------------------------------------------------------------------------
/Praeclarum/UI/IThemeAware.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Praeclarum.UI
4 | {
5 | public interface IThemeAware
6 | {
7 | void ApplyTheme (Theme theme);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Praeclarum/UI/UserInterface.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Praeclarum.UI
4 | {
5 | public class UserInterface
6 | {
7 | public IView View { get; set; }
8 | }
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/Praeclarum/App/ProPriceSpec.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | namespace Praeclarum.App
3 | {
4 | public struct ProPriceSpec
5 | {
6 | public int Months;
7 | public string Name;
8 | }
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Frank's Utilties
2 |
3 | [](https://github.com/praeclarum/Praeclarum/actions/workflows/build.yml)
4 |
5 |
--------------------------------------------------------------------------------
/Praeclarum/IO/IConsole.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 |
3 | namespace Praeclarum
4 | {
5 | public interface IConsole
6 | {
7 | TextReader In { get; }
8 | TextWriter Out { get; }
9 | }
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/Praeclarum/UI/Theme.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Praeclarum.UI
4 | {
5 | public partial class Theme
6 | {
7 | public Graphics.Color DocumentBackgroundGraphicsColor = Graphics.Color.FromWhite (0.0f, 0.0f);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Praeclarum/UI/ITimer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Praeclarum
4 | {
5 | public interface ITimer
6 | {
7 | event EventHandler Tick;
8 | bool Enabled { get; set; }
9 | TimeSpan Interval { get; set; }
10 | }
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/Praeclarum/UI/IView.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Praeclarum.Graphics;
3 |
4 | namespace Praeclarum.UI
5 | {
6 | public interface IView
7 | {
8 | Color BackgroundColor { get; set; }
9 |
10 | RectangleF Bounds { get; }
11 | }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/Praeclarum.iOS/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
1 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage ("Obsoleted APIs", "CA1416", Justification = "False positives")]
2 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage ("Obsoleted APIs", "CA1422", Justification = "False positives")]
3 |
--------------------------------------------------------------------------------
/Praeclarum/UI/ITextEditor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Praeclarum.UI
4 | {
5 | public interface ITextEditor : IView
6 | {
7 | void Modify (Action action);
8 |
9 | StringRange SelectedRange { get; set; }
10 | void ReplaceText (StringRange range, string text);
11 |
12 | }
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/Praeclarum/StringHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Praeclarum
4 | {
5 | public static class StringHelper
6 | {
7 | public static bool IsBlank (this string s)
8 | {
9 | if (s == null) return true;
10 | var len = s.Length;
11 | if (len == 0) return true;
12 | for (var i = 0; i < len; i++)
13 | if (!char.IsWhiteSpace (s[i]))
14 | return false;
15 | return true;
16 | }
17 | }
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/Praeclarum.iOS/UI/ViewAnimation.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using UIKit;
3 |
4 | namespace Praeclarum.UI
5 | {
6 | public static class ViewAnimation
7 | {
8 | public static void Run (Action action, double duration, bool animated)
9 | {
10 | if (animated) {
11 | UIView.Animate (duration, () => {
12 | try {
13 | action();
14 | } catch (Exception ex) {
15 | Log.Error (ex);
16 | }
17 | });
18 | } else {
19 | action ();
20 | }
21 | }
22 | }
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/Praeclarum/UI/IDocumentEditor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Praeclarum.App;
3 | using System.Threading.Tasks;
4 |
5 | namespace Praeclarum.UI
6 | {
7 | public interface IDocumentEditor
8 | {
9 | DocumentReference DocumentReference { get; }
10 | IDocument Document { get; }
11 |
12 | // IView EditorView { get; }
13 |
14 | void DidEnterBackground ();
15 | void WillEnterForeground ();
16 | void OnCreated ();
17 |
18 | void BindDocument ();
19 | Task SaveDocument ();
20 | void UnbindDocument ();
21 |
22 | void UnbindUI ();
23 |
24 | bool IsPreviewing { get; set; }
25 | }
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/Praeclarum/Command.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 |
6 | namespace Praeclarum
7 | {
8 | public delegate Task AsyncAction ();
9 |
10 | public class Command
11 | {
12 | public string Name { get; set; }
13 |
14 | public AsyncAction Action { get; set; }
15 |
16 | public Command (string name, AsyncAction action = null)
17 | {
18 | Name = name.Localize ();
19 | Action = action;
20 | }
21 |
22 | public virtual async Task ExecuteAsync ()
23 | {
24 | if (Action != null)
25 | await Action ();
26 | }
27 | }
28 |
29 |
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/Praeclarum/App/IDocument.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace Praeclarum.App
5 | {
6 | public enum DocumentSaveOperation
7 | {
8 | ForCreating,
9 | ForOverwriting,
10 | }
11 |
12 | public enum DocumentChangeKind
13 | {
14 | Done,
15 | }
16 |
17 | public interface IDocument : IDisposable
18 | {
19 | bool IsOpen { get; }
20 | Task OpenAsync ();
21 | Task SaveAsync (string path, DocumentSaveOperation operation);
22 | Task CloseAsync ();
23 | void UpdateChangeCount (DocumentChangeKind changeKind);
24 | }
25 |
26 | public interface ITextDocument : IDocument
27 | {
28 | string TextData { get; set; }
29 | }
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/Praeclarum/Graphics/Vector.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Praeclarum.Graphics
4 | {
5 | public struct VectorF
6 | {
7 | public float X, Y;
8 |
9 | public VectorF (float x, float y)
10 | {
11 | X = x;
12 | Y = y;
13 | }
14 |
15 | public override string ToString()
16 | {
17 | return string.Format("<{0}, {1}>", X, Y);
18 | }
19 |
20 | public static VectorF operator * (VectorF v, float s)
21 | {
22 | return new VectorF (v.X * s, v.Y * s);
23 | }
24 |
25 | public VectorF Rotate (double angle)
26 | {
27 | var cf = (float)Math.Cos (angle);
28 | var sf = (float)Math.Sin (angle);
29 |
30 | return new VectorF (X * cf - Y * sf, Y * cf + X * sf);
31 | }
32 | }
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/Praeclarum.Android/UI/DocumentListAppActivity.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using Android.App;
6 | using Android.Content;
7 | using Android.OS;
8 | using Android.Runtime;
9 | using Android.Views;
10 | using Android.Widget;
11 |
12 | namespace Praeclarum.UI
13 | {
14 | [Activity (Label = "DocumentListAppActivity")]
15 | public class DocumentListAppActivity : Activity
16 | {
17 | public static DocumentListAppActivity Shared { get; private set; }
18 |
19 | protected override void OnCreate (Bundle bundle)
20 | {
21 | base.OnCreate (bundle);
22 |
23 | Shared = this;
24 |
25 | // Create your application here
26 | }
27 | }
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/Praeclarum.Mac/UI/UserInterfaceWindow.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using AppKit;
3 | using System.Drawing;
4 |
5 | namespace Praeclarum.UI
6 | {
7 | public class UserInterfaceWindow : NSWindow
8 | {
9 | // UserInterface ui;
10 |
11 | public UserInterfaceWindow (UserInterface ui, RectangleF frame, NSScreen screen)
12 | : base (frame,
13 | NSWindowStyle.Titled | NSWindowStyle.Resizable | NSWindowStyle.Closable | NSWindowStyle.Miniaturizable,
14 | NSBackingStore.Buffered,
15 | false,
16 | screen)
17 | {
18 | // this.ui = ui;
19 |
20 | var view = ui.View as NSView;
21 | if (view != null) {
22 |
23 | this.ContentView = view;
24 |
25 | }
26 | }
27 | }
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/Praeclarum.Android/Praeclarum.Android.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net9.0-android
4 | 21
5 | disable
6 | false
7 | 1.0.0
8 | false
9 |
10 |
11 |
12 |
13 | UI\IThemeAware.cs
14 |
15 |
16 | UI\Theme.cs
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Praeclarum.iOS/UI/ProceduralImage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using CoreGraphics;
3 | using UIKit;
4 |
5 | namespace Praeclarum.UI
6 | {
7 | public class ProceduralImage
8 | {
9 | public delegate void DrawFunc(CGContext c);
10 |
11 | public float Width { get; set; }
12 | public float Height { get; set; }
13 | public DrawFunc Draw { get; set; }
14 |
15 | public ProceduralImage (float width, float height, DrawFunc draw)
16 | {
17 | Width = width;
18 | Height = height;
19 | Draw = draw;
20 | }
21 |
22 | public UIImage Generate ()
23 | {
24 | UIGraphics.BeginImageContext (new CGSize (Width, Height));
25 |
26 | var c = UIGraphics.GetCurrentContext ();
27 |
28 | if (Draw != null) {
29 | Draw (c);
30 | }
31 |
32 | var image = UIGraphics.GetImageFromCurrentImageContext ();
33 |
34 | UIGraphics.EndImageContext ();
35 |
36 | return image;
37 | }
38 | }
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/Praeclarum/App/Application.cs:
--------------------------------------------------------------------------------
1 | #nullable enable
2 |
3 | using System;
4 | using Praeclarum.UI;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using Praeclarum.Graphics;
8 |
9 | namespace Praeclarum.App
10 | {
11 | public class Application
12 | {
13 | public virtual string Name { get { return "App"; } }
14 | public virtual string UrlScheme { get { return "app"; } }
15 | public virtual Color TintColor { get { return Colors.Blue; } }
16 | public virtual Color VibrantTintColor { get { return Colors.Blue; } }
17 | public virtual string ProSymbol { get { return "🔷"; } }
18 | public virtual string ProMarketing { get { return "Upgrade to Pro"; } }
19 | public virtual IEnumerable GetProPrices () => Enumerable.Empty ();
20 | public virtual string? AppGroup { get { return null; } }
21 | public virtual string? CloudKitContainerId => null;
22 | }
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/Praeclarum.Mac/Praeclarum.Mac.Shared.projitems:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
5 | true
6 | {4E4684C2-1942-4B53-B8D5-F8E715716A3D}
7 |
8 |
9 | Praeclarum.Mac.Shared
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Praeclarum/Praeclarum.Shared.shproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {B8A8BCD4-7815-4994-A9AF-3E603C844835}
5 | 8.0.30703
6 | 2.0
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Praeclarum.Mac/Praeclarum.Mac.Shared.shproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {4E4684C2-1942-4B53-B8D5-F8E715716A3D}
5 | 8.0.30703
6 | 2.0
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Praeclarum/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 |
4 | // Information about this assembly is defined by the following attributes.
5 | // Change them to the values specific to your project.
6 | [assembly: AssemblyTitle ("Praeclarum")]
7 | [assembly: AssemblyDescription ("")]
8 | [assembly: AssemblyConfiguration ("")]
9 | [assembly: AssemblyCompany ("")]
10 | [assembly: AssemblyProduct ("")]
11 | [assembly: AssemblyCopyright ("2013 Frank A. Krueger")]
12 | [assembly: AssemblyTrademark ("")]
13 | [assembly: AssemblyCulture ("")]
14 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
15 | // The form "{Major}.{Minor}.*" will automatically update the build and revision,
16 | // and "{Major}.{Minor}.{Build}.*" will update just the revision.
17 | [assembly: AssemblyVersion ("1.0.*")]
18 | // The following attributes are used to specify the signing key for the assembly,
19 | // if desired. See the Mono documentation for more information about signing.
20 | //[assembly: AssemblyDelaySign(false)]
21 | //[assembly: AssemblyKeyFile("")]
22 |
23 |
--------------------------------------------------------------------------------
/Praeclarum.iOS/UI/DocumentThumbnailsAppDelegate.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Foundation;
3 | using UIKit;
4 | using System.Linq;
5 | using System.Collections.Generic;
6 |
7 | namespace Praeclarum.UI
8 | {
9 | [Register ("DocumentThumbnailsAppDelegate")]
10 | public class DocumentThumbnailsAppDelegate : DocumentAppDelegate
11 | {
12 | protected override void SetRootViewController ()
13 | {
14 | window.RootViewController = (UIViewController)docListNav ?? docBrowser;
15 | }
16 |
17 | protected override void ShowEditor (int docIndex, bool advance, bool animated, UIViewController newEditorVC)
18 | {
19 | // Debug.WriteLine ("SHOWING EDITOR");
20 | var nc = docListNav;
21 | var vcs = nc.ViewControllers;
22 | var oldEditor = CurrentDocumentEditor;
23 | var nvcs = new List (vcs.OfType ());
24 | nvcs.Add (newEditorVC);
25 | vcs = nvcs.ToArray ();
26 |
27 | nc.SetViewControllers (vcs, false);
28 | }
29 |
30 | protected override DocumentsViewController CreateDirectoryViewController (string path)
31 | {
32 | return new DocumentsViewController (path, DocumentsViewMode.Thumbnails);
33 | }
34 |
35 | }
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/Praeclarum.iOS/UI/StorageSection.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Foundation;
3 |
4 | namespace Praeclarum.UI
5 | {
6 | public class StorageSection : PFormSection
7 | {
8 | public StorageSection ()
9 | : base (new Command ("Storage"))
10 | {
11 | Title = "Storage".Localize ();
12 | // Hint = "Select where to save documents.";
13 | }
14 |
15 | public override bool GetItemNavigates (object item)
16 | {
17 | return true;
18 | }
19 |
20 | public override bool SelectItem (object item)
21 | {
22 | var f = new StorageForm ();
23 | f.NavigationItem.RightBarButtonItem = new UIKit.UIBarButtonItem (UIKit.UIBarButtonSystemItem.Done, (s, e) => {
24 | if (f != null && f.PresentingViewController != null) {
25 | f.DismissViewController (true, null);
26 | }
27 | });
28 | if (this.Form.NavigationController != null) {
29 | this.Form.NavigationController.PushViewController (f, true);
30 | }
31 | return false;
32 | }
33 |
34 | public override string GetItemTitle (object item)
35 | {
36 | var isp = DocumentAppDelegate.Shared.FileSystem;
37 | return isp != null ?
38 | isp.Description :
39 | "Storage".Localize ();
40 | }
41 | }
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/Praeclarum/UI/ICanvas.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Praeclarum.Graphics;
3 |
4 | namespace Praeclarum.UI
5 | {
6 | public interface ICanvas : IView
7 | {
8 | event EventHandler Drawing;
9 |
10 | event EventHandler TouchBegan;
11 | event EventHandler TouchMoved;
12 | event EventHandler TouchCancelled;
13 | event EventHandler TouchEnded;
14 |
15 | void Invalidate ();
16 | void Invalidate (RectangleF frame);
17 | }
18 |
19 | public class CanvasTouchEventArgs : EventArgs
20 | {
21 | public int TouchId { get; set; }
22 | public PointF Location { get; set; }
23 | }
24 |
25 | public class CanvasDrawingEventArgs : EventArgs
26 | {
27 | public CanvasDrawingEventArgs (IGraphics graphics, RectangleF visibleArea)
28 | {
29 | Graphics = graphics;
30 | VisibleArea = visibleArea;
31 | }
32 | public IGraphics Graphics { get; private set; }
33 | public RectangleF VisibleArea { get; private set; }
34 | }
35 |
36 | public static class CanvasEx
37 | {
38 | public static void Invalidate (this ICanvas canvas)
39 | {
40 | canvas.Invalidate (canvas.Bounds);
41 | }
42 | }
43 | }
44 |
45 |
--------------------------------------------------------------------------------
/Praeclarum.iOS/UI/AnalyticsSection.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Praeclarum.UI
4 | {
5 | public class AnalyticsSection : PFormSection
6 | {
7 | readonly Action disable;
8 |
9 | public AnalyticsSection (Action disable)
10 | {
11 | this.disable = disable;
12 | Hint = ("In order to improve iCircuit, anonymous usage data can be collected and sent to the developer. " +
13 | "This data includes which elements you add, which properties you set (not including values), and what errors occur. " +
14 | "To opt out of this, tap the option to turn it off (unchecked).").Localize ();
15 |
16 | Items.Add ("Enable Anonymous Analytics".Localize ());
17 | }
18 |
19 | public override bool GetItemChecked (object item)
20 | {
21 | return !DocumentAppDelegate.Shared.Settings.DisableAnalytics;
22 | }
23 |
24 | public override bool SelectItem (object item)
25 | {
26 | var disabled = !DocumentAppDelegate.Shared.Settings.DisableAnalytics;
27 | DocumentAppDelegate.Shared.Settings.DisableAnalytics = disabled;
28 |
29 | // Wait till next launch to enable to make sure everything is inited
30 | if (disabled) disable ();
31 |
32 | SetNeedsFormat ();
33 | return false;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Praeclarum.iOS/UI/DarkModeSection.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Foundation;
3 |
4 | namespace Praeclarum.UI
5 | {
6 | public class DarkModeSection : PFormSection
7 | {
8 | public DarkModeSection ()
9 | : this (() => DocumentAppDelegate.Shared.Settings.DarkMode, () => {
10 | var appdel = DocumentAppDelegate.Shared;
11 | appdel.Settings.DarkMode = !appdel.Settings.DarkMode;
12 | appdel.UpdateTheme ();
13 | })
14 | {
15 | }
16 |
17 | readonly Func isDarkFunc;
18 | readonly Action toggleAction;
19 |
20 | public DarkModeSection (Func isDark, Action toggle)
21 | {
22 | Title = "Theme".Localize ();
23 |
24 | Items.Add ("Light Mode".Localize ());
25 | Items.Add ("Dark Mode".Localize ());
26 |
27 | this.isDarkFunc = isDark;
28 | this.toggleAction = toggle;
29 | }
30 |
31 | public override bool GetItemChecked (object item)
32 | {
33 | var isDark = isDarkFunc ();
34 | if ("Dark Mode" == item.ToString ()) {
35 | return isDark;
36 | }
37 | return !isDark;
38 | }
39 |
40 | public override bool SelectItem (object item)
41 | {
42 | NSTimer.CreateScheduledTimer (0.1, t => {
43 | toggleAction ();
44 | SetNeedsFormat ();
45 | });
46 | return false;
47 | }
48 | }
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/Praeclarum.Mac/UI/Timer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Foundation;
3 |
4 | namespace Praeclarum.UI
5 | {
6 | public class Timer : ITimer
7 | {
8 | public event EventHandler Tick;
9 |
10 | NSTimer timer;
11 |
12 | public bool Enabled {
13 | get { return timer != null; }
14 | set {
15 | if (value) {
16 | if (timer != null)
17 | return;
18 | try {
19 | timer = NSTimer.CreateRepeatingScheduledTimer (
20 | Interval, NSTimerTick);
21 | } catch (Exception) {
22 | }
23 | } else {
24 | if (timer == null)
25 | return;
26 | try {
27 | timer.Invalidate ();
28 | } catch (Exception) {
29 | }
30 | finally {
31 | timer = null;
32 | }
33 | }
34 | }
35 | }
36 |
37 | TimeSpan interval;
38 |
39 | public TimeSpan Interval {
40 | get {
41 | return interval;
42 | }
43 | set {
44 | if (interval == value)
45 | return;
46 | interval = value;
47 | var en = Enabled;
48 | Enabled = false;
49 | Enabled = en;
50 | }
51 | }
52 |
53 | public Timer ()
54 | {
55 | interval = TimeSpan.FromSeconds (1);
56 | }
57 |
58 | void NSTimerTick (NSTimer t)
59 | {
60 | var ev = Tick;
61 | if (ev != null) {
62 | ev (this, EventArgs.Empty);
63 | }
64 | }
65 | }
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/Praeclarum/AsyncExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using System.Reflection;
4 |
5 | namespace Praeclarum
6 | {
7 | public static class AsyncExtensions
8 | {
9 | public static Task GetEventAsync (this object eventSource, string eventName)
10 | where T : EventArgs
11 | {
12 | var tcs = new TaskCompletionSource();
13 |
14 | var type = eventSource.GetType ();
15 | var ev = type.GetTypeInfo ().GetDeclaredEvent (eventName);
16 |
17 | EventHandler handler = null;
18 |
19 | handler = delegate (object sender, T e) {
20 | ev.RemoveEventHandler (eventSource, handler);
21 | tcs.SetResult (e);
22 | };
23 |
24 | ev.AddEventHandler (eventSource, handler);
25 | return tcs.Task;
26 | }
27 |
28 | public static Task GetEventAsync (this object eventSource, string eventName)
29 | {
30 | var tcs = new TaskCompletionSource();
31 |
32 | var type = eventSource.GetType ();
33 | var ev = type.GetTypeInfo ().GetDeclaredEvent (eventName);
34 |
35 | EventHandler handler = null;
36 |
37 | handler = delegate (object sender, EventArgs e) {
38 | ev.RemoveEventHandler (eventSource, handler);
39 | tcs.SetResult (e);
40 | };
41 |
42 | ev.AddEventHandler (eventSource, handler);
43 | return tcs.Task;
44 | }
45 | }
46 | }
47 |
48 |
--------------------------------------------------------------------------------
/Praeclarum.iOS/UI/SelectableButtonItem.cs:
--------------------------------------------------------------------------------
1 | #nullable enable
2 |
3 | using System;
4 | using UIKit;
5 |
6 | namespace Praeclarum.UI
7 | {
8 | public class SelectableButtonItem
9 | {
10 | public UIBarButtonItem Item { get; private set; }
11 |
12 | readonly UIButton? button = null;
13 |
14 | private bool selected;
15 |
16 | public bool Selected {
17 | get {
18 | return selected;
19 | }
20 | set
21 | {
22 | if (selected == value) return;
23 | selected = value;
24 | if (button is { } b)
25 | {
26 | b.Selected = selected;
27 | }
28 | else
29 | {
30 | if (ios15)
31 | {
32 | Item.Selected = selected;
33 | }
34 | }
35 | }
36 | }
37 |
38 | readonly static bool ios15 = UIDevice.CurrentDevice.CheckSystemVersion (15, 0);
39 |
40 | public SelectableButtonItem (UIImage image, EventHandler handler)
41 | {
42 | if (ios15) {
43 | Item = new UIBarButtonItem (image, UIBarButtonItemStyle.Plain, handler);
44 | Item.Selected = selected;
45 | }
46 | else {
47 | button = UIButton.FromType (UIButtonType.RoundedRect);
48 | button.SetImage (image, UIControlState.Normal);
49 | button.Frame = new CoreGraphics.CGRect (0, 0, 44, 44);
50 | button.TouchUpInside += handler;
51 | button.Selected = selected;
52 | Item = new UIBarButtonItem (button);
53 | }
54 | }
55 | }
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/Praeclarum.iOS/UI/TitleView.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using UIKit;
3 | using Foundation;
4 | using CoreGraphics;
5 | using System.Collections.Generic;
6 | using CoreAnimation;
7 | using System.Threading.Tasks;
8 | using System.IO;
9 | using System.Diagnostics;
10 | using System.Linq;
11 | using Praeclarum.UI;
12 | using Praeclarum;
13 | using Praeclarum.IO;
14 |
15 | namespace Praeclarum.UI
16 | {
17 | public class TitleView : UILabel
18 | {
19 | // UITapGestureRecognizer titleTap;
20 |
21 | public event EventHandler Tapped = delegate {};
22 |
23 | public TitleView ()
24 | {
25 | var isPhone = UIDevice.CurrentDevice.UserInterfaceIdiom == UIUserInterfaceIdiom.Phone;
26 | var titleX = isPhone ? (320 - 176) / 2 : (768 - 624) / 2;
27 |
28 | Frame = new CGRect (titleX + 1, 6, isPhone ? 176 : 624, 30);
29 | BackgroundColor = UIColor.Clear;
30 | Opaque = false;
31 | TextColor = DocumentAppDelegate.Shared.Theme.NavigationTextColor;
32 | // ShadowColor = WhiteTheme.BarTextShadowColor;
33 | // ShadowOffset = new SizeF (WhiteTheme.BarTextShadowOffset.Horizontal, WhiteTheme.BarTextShadowOffset.Vertical);
34 | Font = UIFont.FromName (WhiteTheme.TitleFontName, WhiteTheme.BarTitleFontSize);
35 | AdjustsFontSizeToFitWidth = true;
36 | TextAlignment = UITextAlignment.Center;
37 | UserInteractionEnabled = true;
38 |
39 | AddGestureRecognizer (new UITapGestureRecognizer (HandleTitleTap));
40 | }
41 |
42 | void HandleTitleTap (UITapGestureRecognizer pan)
43 | {
44 | Tapped (this, EventArgs.Empty);
45 | }
46 | }
47 | }
48 |
49 |
--------------------------------------------------------------------------------
/Praeclarum.iOS/UI/Editor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using UIKit;
3 | using Foundation;
4 | using Praeclarum.Graphics;
5 |
6 | namespace Praeclarum.UI
7 | {
8 | public class Editor : UITextView, ITextEditor
9 | {
10 | public Editor (CoreGraphics.CGRect frame)
11 | : base (frame)
12 | {
13 | }
14 |
15 | public Editor (CoreGraphics.CGRect frame, NSTextContainer container)
16 | : base (frame, container)
17 | {
18 | }
19 |
20 | #region IView implementation
21 |
22 | Color IView.BackgroundColor {
23 | get { return base.BackgroundColor.GetColor (); }
24 | set { base.BackgroundColor = value.GetUIColor (); }
25 | }
26 |
27 | RectangleF IView.Bounds {
28 | get {
29 | return Bounds.ToRectangleF ();
30 | }
31 | }
32 |
33 | #endregion
34 |
35 | #region ITextEditor implementation
36 |
37 | public void Modify (Action action)
38 | {
39 | BeginInvokeOnMainThread (() => action ());
40 | }
41 |
42 | void ITextEditor.ReplaceText (StringRange range, string text)
43 | {
44 | var b = this.GetPosition (BeginningOfDocument, range.Location);
45 | var e = this.GetPosition (BeginningOfDocument, range.Location + range.Length);
46 | var r = this.GetTextRange (b, e);
47 | ReplaceText (r, text);
48 | }
49 |
50 | StringRange ITextEditor.SelectedRange {
51 | get {
52 | var r = SelectedRange;
53 | return new StringRange ((int)r.Location, (int)r.Length);
54 | }
55 | set {
56 | SelectedRange = new NSRange (value.Location, value.Length);
57 | }
58 | }
59 |
60 | #endregion
61 | }
62 | }
63 |
64 |
--------------------------------------------------------------------------------
/Praeclarum.Net.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31903.59
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Praeclarum.Mac", "Praeclarum.Mac\Praeclarum.Mac.csproj", "{D02FAE64-9438-4D14-A06A-3C21DCADC101}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Praeclarum", "Praeclarum\Praeclarum.csproj", "{4A09F863-81C8-481B-8017-FDFC1F92D5B8}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(SolutionProperties) = preSolution
16 | HideSolutionNode = FALSE
17 | EndGlobalSection
18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
19 | {D02FAE64-9438-4D14-A06A-3C21DCADC101}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
20 | {D02FAE64-9438-4D14-A06A-3C21DCADC101}.Debug|Any CPU.Build.0 = Debug|Any CPU
21 | {D02FAE64-9438-4D14-A06A-3C21DCADC101}.Release|Any CPU.ActiveCfg = Release|Any CPU
22 | {D02FAE64-9438-4D14-A06A-3C21DCADC101}.Release|Any CPU.Build.0 = Release|Any CPU
23 | {4A09F863-81C8-481B-8017-FDFC1F92D5B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {4A09F863-81C8-481B-8017-FDFC1F92D5B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {4A09F863-81C8-481B-8017-FDFC1F92D5B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
26 | {4A09F863-81C8-481B-8017-FDFC1F92D5B8}.Release|Any CPU.Build.0 = Release|Any CPU
27 | EndGlobalSection
28 | EndGlobal
29 |
--------------------------------------------------------------------------------
/Praeclarum.Android/UI/DocumentEditor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | using Android.App;
8 | using Android.Content;
9 | using Android.OS;
10 | using Android.Runtime;
11 | using Android.Views;
12 | using Android.Widget;
13 | using Praeclarum.App;
14 |
15 | namespace Praeclarum.UI
16 | {
17 | public class DocumentEditor : Fragment, IDocumentEditor
18 | {
19 | public DocumentReference DocumentReference { get; private set; }
20 |
21 | public DocumentEditor (DocumentReference docRef)
22 | {
23 | DocumentReference = docRef;
24 | }
25 |
26 | #region IDocumentEditor implementation
27 |
28 | IView editorView = null;
29 |
30 | public virtual IView EditorView
31 | {
32 | get { return editorView; }
33 | set {
34 | View.AddTouchables (new List {
35 | (global::Android.Views.View)editorView,
36 | });
37 | }
38 | }
39 |
40 | public IDocument Document => null;
41 |
42 | public virtual bool IsPreviewing { get; set; }
43 |
44 | public virtual void OnCreated ()
45 | {
46 | }
47 |
48 | public virtual void DidEnterBackground ()
49 | {
50 | }
51 |
52 | public virtual void WillEnterForeground ()
53 | {
54 | }
55 |
56 | public virtual void BindDocument ()
57 | {
58 | }
59 |
60 | public virtual void UnbindDocument ()
61 | {
62 | }
63 |
64 | public virtual void UnbindUI ()
65 | {
66 | }
67 |
68 | public virtual Task SaveDocument ()
69 | {
70 | return Task.CompletedTask;
71 | }
72 |
73 | #endregion
74 | }
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/Praeclarum/App/IAppSettings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Praeclarum.IO;
3 | using Praeclarum.UI;
4 |
5 | namespace Praeclarum.App
6 | {
7 | public interface IDocumentAppSettings : IAppSettings
8 | {
9 | string LastDocumentPath { get; set; }
10 |
11 | DocumentsSort DocumentsSort { get; set; }
12 |
13 | string FileSystem { get; set; }
14 |
15 | bool UseCloud { get; set; }
16 | bool AskedToUseCloud { get; set; }
17 |
18 | string GetWorkingDirectory (IFileSystem fileSystem);
19 | void SetWorkingDirectory (IFileSystem fileSystem, string path);
20 |
21 | string DocumentationVersion { get; set; }
22 |
23 | bool DarkMode { get; set; }
24 |
25 | bool IsPatron {
26 | get;
27 | set;
28 | }
29 |
30 | DateTime PatronEndDate {
31 | get;
32 | set;
33 | }
34 |
35 | bool DisableAnalytics { get; set; }
36 |
37 | bool HasTipped { get; set; }
38 | DateTime TipDate { get; set; }
39 |
40 | DateTime SubscribedToProDate { get; set; }
41 | string SubscribedToProFromPlatform { get; set; }
42 | int SubscribedToProMonths { get; set; }
43 | }
44 |
45 | public static class DocumentAppSettingsExtensions
46 | {
47 | public static DateTime SubscribedToProEndDate (this IDocumentAppSettings settings) => settings.SubscribedToProDate.AddMonths(settings.SubscribedToProMonths);
48 | }
49 |
50 | public interface IAppSettings
51 | {
52 | int RunCount { get; set; }
53 | bool UseEnglish { get; set; }
54 | }
55 |
56 | public static class AppSettingsEx
57 | {
58 | public static bool IsFirstRun (this IAppSettings settings)
59 | {
60 | return settings.RunCount == 1;
61 | }
62 | }
63 | }
64 |
65 |
--------------------------------------------------------------------------------
/Praeclarum/Localization.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace Praeclarum
5 | {
6 | public static class Localization
7 | {
8 | public static string Localize (object obj)
9 | {
10 | if (obj == null)
11 | return "";
12 | var english = obj.ToString ();
13 | var t = Translate (english);
14 | return Note (english, t);
15 | }
16 |
17 | public static string Localize (this string english)
18 | {
19 | var t = Translate (english);
20 | return Note (english, t);
21 | }
22 |
23 | public static string Localize (this string format, params object[] args)
24 | {
25 | var t = Translate (format);
26 | return string.Format (Note (format, t), args);
27 | }
28 |
29 | static string Translate (string english)
30 | {
31 | #if IOS || MACCATALYST || __IOS__ || __MACOS__
32 | if (Foundation.NSUserDefaults.StandardUserDefaults.BoolForKey ("UseEnglish"))
33 | return english;
34 | return Foundation.NSBundle.MainBundle.GetLocalizedString (key: english, value: english);
35 | #else
36 | return english;
37 | #endif
38 | }
39 |
40 | #if DEBUG
41 | static readonly HashSet notes = new HashSet ();
42 | #endif
43 | static string Note (string english, string translation)
44 | {
45 | #if DEBUG
46 | var lang = System.Globalization.CultureInfo.CurrentCulture.TwoLetterISOLanguageName;
47 | if (lang != "en" && english == translation && english.Length > 0 && !notes.Contains (english)) {
48 | notes.Add (english);
49 | System.Diagnostics.Debug.WriteLine ($"Needs Translation [{lang}]: \"{english}\" = \"\";");
50 | }
51 | #endif
52 | return translation.Length > 0 ? translation : english;
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Praeclarum.Mac/UI/Canvas.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using AppKit;
3 | using CoreGraphics;
4 | using Praeclarum.Graphics;
5 |
6 | namespace Praeclarum.UI
7 | {
8 | public class View : NSView, IView
9 | {
10 | RectangleF IView.Bounds {
11 | get { return base.Bounds.ToRectangleF (); }
12 | }
13 |
14 | public override bool IsFlipped {
15 | get {
16 | return true;
17 | }
18 | }
19 |
20 | Color bgColor = Colors.Black;
21 | public Color BackgroundColor {
22 | get {
23 | return bgColor;
24 | }
25 | set {
26 | bgColor = value;
27 | }
28 | }
29 | }
30 |
31 | public class Canvas : View, ICanvas
32 | {
33 | public event EventHandler TouchBegan = delegate {};
34 | public event EventHandler TouchMoved = delegate {};
35 | public event EventHandler TouchCancelled = delegate {};
36 | public event EventHandler TouchEnded = delegate {};
37 |
38 | public event EventHandler Drawing = delegate {};
39 |
40 | public Canvas ()
41 | {
42 | }
43 |
44 | public void Invalidate ()
45 | {
46 | SetNeedsDisplayInRect (base.Bounds);
47 | }
48 |
49 | public void Invalidate (RectangleF frame)
50 | {
51 | SetNeedsDisplayInRect (frame.ToRectangleF ());
52 | }
53 |
54 | public override void DrawRect (CGRect dirtyRect)
55 | {
56 | try {
57 | var c = NSGraphicsContext.CurrentContext.GraphicsPort;
58 | var g = new CoreGraphicsGraphics (c, true);
59 | var e = new CanvasDrawingEventArgs (g, Bounds.ToRectangleF ());
60 | Drawing (this, e);
61 | } catch (Exception ex) {
62 | Log.Error (ex);
63 | }
64 | }
65 | }
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/Praeclarum.Android/App/TextDocument.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace Praeclarum.App
5 | {
6 | public class TextDocument : ITextDocument
7 | {
8 | string path;
9 |
10 | public TextDocument (string path)
11 | {
12 | this.path = path;
13 | }
14 |
15 | #region IDocument implementation
16 |
17 | bool isOpen = false;
18 |
19 | public async Task OpenAsync ()
20 | {
21 | TextData = System.IO.File.ReadAllText (path);
22 | isOpen = true;
23 | }
24 |
25 | public async Task SaveAsync (string path, DocumentSaveOperation operation)
26 | {
27 | this.path = path;
28 | System.IO.File.WriteAllText (path, TextData);
29 | }
30 |
31 | public async Task CloseAsync ()
32 | {
33 | if (!isOpen)
34 | return;
35 | isOpen = false;
36 | }
37 |
38 | public bool IsOpen {
39 | get {
40 | return isOpen;
41 | }
42 | }
43 |
44 | #endregion
45 |
46 | #region ITextDocument implementation
47 |
48 | public void UpdateChangeCount (DocumentChangeKind changeKind)
49 | {
50 | }
51 |
52 | string textData = "";
53 | private bool _disposedValue;
54 |
55 | public virtual string TextData {
56 | get {
57 | return textData;
58 | }
59 | set {
60 | textData = value ?? "";
61 | }
62 | }
63 |
64 | protected virtual void Dispose (bool disposing)
65 | {
66 | if (!_disposedValue)
67 | {
68 | if (disposing)
69 | {
70 | }
71 | _disposedValue = true;
72 | }
73 | }
74 |
75 | public void Dispose ()
76 | {
77 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
78 | Dispose (disposing: true);
79 | GC.SuppressFinalize (this);
80 | }
81 |
82 | #endregion
83 | }
84 | }
85 |
86 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: macos-26
13 |
14 | steps:
15 | - uses: actions/checkout@v4
16 | - name: Update Versions
17 | env:
18 | VERSION_PREFIX: 0.4
19 | VERSION_SUFFIX: ${{github.run_number}}
20 | run: |
21 | VERSION=$VERSION_PREFIX.$VERSION_SUFFIX
22 | sed -i.bak "s:1.0.0:$VERSION:g" Praeclarum/Praeclarum.csproj
23 | sed -i.bak "s:1.0.0:$VERSION:g" Praeclarum.Android/Praeclarum.Android.csproj
24 | sed -i.bak "s:1.0.0:$VERSION:g" Praeclarum.iOS/Praeclarum.iOS.csproj
25 | sed -i.bak "s:1.0.0:$VERSION:g" Praeclarum.Mac/Praeclarum.Mac.csproj
26 | sed -i.bak "s:1.0.0:$VERSION:g" Praeclarum.Utilities/Praeclarum.Utilities.csproj
27 | - name: Setup Xcode
28 | uses: maxim-lobanov/setup-xcode@v1
29 | with:
30 | xcode-version: 26.0
31 | - name: Setup .NET
32 | uses: actions/setup-dotnet@v4
33 | with:
34 | global-json-file: global.json
35 | - name: Install workloads
36 | run: dotnet workload restore Praeclarum.Utilities.sln
37 | - name: Restore dependencies
38 | run: dotnet restore Praeclarum.Utilities.sln
39 | - name: Build
40 | run: dotnet build --no-restore -c Release Praeclarum.Utilities.sln
41 | - name: Pack
42 | run: dotnet pack -c Release Praeclarum.Utilities.sln -o .
43 | - name: Store nuget
44 | uses: actions/upload-artifact@v4
45 | with:
46 | name: Praeclarum.UtilitiesNuget
47 | path: "*.nupkg"
48 |
--------------------------------------------------------------------------------
/Praeclarum/IO/FileSystemManager.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using System.Collections.ObjectModel;
3 | using System.Linq;
4 | using System;
5 |
6 | namespace Praeclarum.IO
7 | {
8 | public class FileSystemManager
9 | {
10 | public ObservableCollection Providers { get; private set; }
11 |
12 | public ObservableCollection FileSystems { get; private set; }
13 |
14 | public IFileSystem ActiveFileSystem {
15 | get;
16 | set;
17 | }
18 |
19 | public static FileSystemManager Shared { get; } = new FileSystemManager {
20 | ActiveFileSystem = new LoadingFileSystem (),
21 | };
22 |
23 | public FileSystemManager ()
24 | {
25 | Providers = new ObservableCollection ();
26 | FileSystems = new ObservableCollection ();
27 | }
28 |
29 | public void Add (IFileSystem fs)
30 | {
31 | FileSystems.Add (fs);
32 | }
33 |
34 | public void Add (IFileSystemProvider fss)
35 | {
36 | Providers.Add (fss);
37 | foreach (var fs in fss.GetFileSystems ())
38 | FileSystems.Add (fs);
39 | }
40 |
41 | public IFileSystem ChooseFileSystem (string lastFileSystemId)
42 | {
43 | var fs = FileSystems.FirstOrDefault (x => x.IsAvailable && x.Id == lastFileSystemId);
44 | #if __IOS__
45 | if (fs == null)
46 | fs = FileSystems.OfType ().First (x => x.IsAvailable);
47 | #endif
48 | if (fs == null)
49 | fs = FileSystems.First (x => x.IsAvailable);
50 |
51 | return fs;
52 | }
53 |
54 | public static void EnsureDirectoryExists (string dir)
55 | {
56 | if (string.IsNullOrEmpty (dir) || dir == "/")
57 | return;
58 |
59 | if (!System.IO.Directory.Exists (dir)) {
60 | EnsureDirectoryExists (System.IO.Path.GetDirectoryName (dir));
61 | System.IO.Directory.CreateDirectory (dir);
62 | }
63 | }
64 | }
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/Praeclarum.Android/Resources/AboutResources.txt:
--------------------------------------------------------------------------------
1 | Images, layout descriptions, binary blobs and string dictionaries can be included
2 | in your application as resource files. Various Android APIs are designed to
3 | operate on the resource IDs instead of dealing with images, strings or binary blobs
4 | directly.
5 |
6 | For example, a sample Android app that contains a user interface layout (main.axml),
7 | an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png)
8 | would keep its resources in the "Resources" directory of the application:
9 |
10 | Resources/
11 | drawable/
12 | icon.png
13 |
14 | layout/
15 | main.axml
16 |
17 | values/
18 | strings.xml
19 |
20 | In order to get the build system to recognize Android resources, set the build action to
21 | "AndroidResource". The native Android APIs do not operate directly with filenames, but
22 | instead operate on resource IDs. When you compile an Android application that uses resources,
23 | the build system will package the resources for distribution and generate a class called "R"
24 | (this is an Android convention) that contains the tokens for each one of the resources
25 | included. For example, for the above Resources layout, this is what the R class would expose:
26 |
27 | public class R {
28 | public class drawable {
29 | public const int icon = 0x123;
30 | }
31 |
32 | public class layout {
33 | public const int main = 0x456;
34 | }
35 |
36 | public class strings {
37 | public const int first_string = 0xabc;
38 | public const int second_string = 0xbcd;
39 | }
40 | }
41 |
42 | You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main
43 | to reference the layout/main.axml file, or R.strings.first_string to reference the first
44 | string in the dictionary file values/strings.xml.
45 |
--------------------------------------------------------------------------------
/Praeclarum.iOS/UI/ActivityIndicator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using UIKit;
3 | using CoreGraphics;
4 |
5 | namespace Praeclarum.UI
6 | {
7 | public class ActivityIndicator : UIView
8 | {
9 | readonly UILabel titleLabel;
10 | readonly UIActivityIndicatorView activity;
11 |
12 | public string Title {
13 | get { return titleLabel.Text; }
14 | set { titleLabel.Text = value; }
15 | }
16 |
17 | public ActivityIndicator ()
18 | {
19 | Opaque = false;
20 |
21 | var bounds = new CGRect (0, 0, 150, 88);
22 | Frame = bounds;
23 |
24 | var isDark = DocumentAppDelegate.Shared.Theme.IsDark;
25 | BackgroundColor = isDark ?
26 | UIColor.FromWhiteAlpha (1-0.96f, 0.85f) :
27 | UIColor.FromWhiteAlpha (0.96f, 0.85f);
28 | Layer.CornerRadius = 12;
29 |
30 | const float margin = 12;
31 |
32 | activity = new UIActivityIndicatorView (isDark ? UIActivityIndicatorViewStyle.White : UIActivityIndicatorViewStyle.Gray) {
33 | Frame = new CGRect (margin, margin, 21, 21),
34 | HidesWhenStopped = false,
35 | };
36 |
37 | titleLabel = new UILabel (new CGRect (activity.Frame.Right+margin, 0, bounds.Width - activity.Frame.Right - 2*margin, 44)) {
38 | TextAlignment = UITextAlignment.Center,
39 | TextColor = isDark ? UIColor.FromWhiteAlpha (1-0.33f, 1) : UIColor.FromWhiteAlpha (0.33f, 1),
40 | BackgroundColor = UIColor.Clear,
41 | };
42 |
43 | AddSubviews (titleLabel, activity);
44 | }
45 |
46 | public void Show (bool animated = true)
47 | {
48 | Hidden = false;
49 | activity.StartAnimating ();
50 | if (animated)
51 | UIView.Animate (1, () => Alpha = 1);
52 | else
53 | Alpha = 1;
54 | }
55 |
56 | public void Hide (bool animated = true)
57 | {
58 | activity.StopAnimating ();
59 | if (animated)
60 | UIView.Animate (0.25, () => Alpha = 0, () => Hidden = true);
61 | else {
62 | Alpha = 0;
63 | Hidden = true;
64 | }
65 | }
66 | }
67 | }
68 |
69 |
--------------------------------------------------------------------------------
/Praeclarum/UI/IDocumentsView.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Praeclarum.UI;
4 | using Praeclarum.App;
5 | using System.Linq;
6 | using System.Collections.ObjectModel;
7 | using Praeclarum.IO;
8 |
9 | namespace Praeclarum.UI
10 | {
11 | public enum DocumentsSort
12 | {
13 | Date,
14 | Name
15 | }
16 |
17 | public class DocumentsViewItem
18 | {
19 | public DocumentReference Reference { get; set; }
20 | public List SubReferences { get; set; }
21 |
22 | public bool IsDirectory { get { return Reference.File.IsDirectory; } }
23 |
24 | public DateTime ModifiedTime {
25 | get {
26 | if (SubReferences == null || SubReferences.Count == 0)
27 | return Reference.File.ModifiedTime;
28 | return SubReferences.Max (x => x.File.ModifiedTime);
29 | }
30 | }
31 |
32 | public DocumentsViewItem (DocumentReference reference)
33 | {
34 | Reference = reference;
35 | }
36 | }
37 |
38 | public interface IDocumentsView
39 | {
40 | bool IsSyncing { get; set; }
41 | List Items { get; set; }
42 | DocumentsViewItem GetItemAtPoint (Praeclarum.Graphics.PointF p);
43 | void ReloadData ();
44 |
45 | DocumentsSort Sort { get; set; }
46 | event EventHandler SortChanged;
47 |
48 | void InsertItems (int[] docIndices);
49 | void DeleteItems (int[] docIndices, bool animated);
50 | void UpdateItem (int docIndex);
51 | void ShowItem (int docIndex, bool animated);
52 | void RefreshListTimes ();
53 | void SetOpenedDocument (int docIndex, bool animated);
54 |
55 | event Action RenameRequested;
56 |
57 | void SetEditing (bool editing, bool animated);
58 |
59 | void SetSelecting (bool selecting, bool animated);
60 |
61 | void UpdateLayout ();
62 |
63 | ObservableCollection SelectedDocuments { get; }
64 |
65 | bool ShowPatron { get; set; }
66 |
67 | void StopLoadingThumbnails ();
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Praeclarum.iOS/App/ReviewNagging.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using UIKit;
3 | using Foundation;
4 | using System.Linq;
5 | using StoreKit;
6 |
7 | namespace Praeclarum.App
8 | {
9 | public interface IHasReviewNagging
10 | {
11 | ReviewNagging ReviewNagging { get; }
12 | }
13 |
14 | public class ReviewNagging
15 | {
16 | // https://developer.apple.com/documentation/storekit/skstorereviewcontroller/requesting_app_store_reviews
17 |
18 | readonly string numPositiveKey;
19 | readonly string shownKey;
20 | readonly NSUserDefaults defs;
21 |
22 | int MinNumPositiveActions { get; }
23 |
24 | int NumPositiveActions => (int)defs.IntForKey (numPositiveKey);
25 |
26 | bool Shown => defs.BoolForKey (shownKey);
27 |
28 | public bool NeedsReview => !Shown;
29 |
30 | public ReviewNagging (int minNumPositiveActions = 5)
31 | {
32 | var appVersionFull = NSBundle.MainBundle.InfoDictionary["CFBundleShortVersionString"]?.ToString () ?? "1.0";
33 | var appVersionMajorMinor = string.Join (".", appVersionFull.Split ('.').Take (2));
34 |
35 | defs = NSUserDefaults.StandardUserDefaults;
36 | numPositiveKey = "ReviewCount" + appVersionMajorMinor;
37 | shownKey = "ReviewShown" + appVersionMajorMinor;
38 | MinNumPositiveActions = minNumPositiveActions;
39 | }
40 |
41 | public void Reset ()
42 | {
43 | defs.SetInt (0, numPositiveKey);
44 | defs.SetBool (false, shownKey);
45 | }
46 |
47 | public void RegisterPositiveAction ()
48 | {
49 | try {
50 | defs.SetInt (NumPositiveActions + 1, numPositiveKey);
51 | Log.Info ("Num Review Actions = " + NumPositiveActions);
52 | }
53 | catch (Exception ex) {
54 | Log.Error (ex);
55 | }
56 | }
57 |
58 | public bool ShouldPresent {
59 | get {
60 | var osok = UIDevice.CurrentDevice.CheckSystemVersion (10, 3);
61 | var shouldPresent = osok && !Shown && NumPositiveActions >= MinNumPositiveActions;
62 | Log.Info ($"Present Review (os={osok}, s={Shown}, n={NumPositiveActions}) = {shouldPresent}");
63 | return shouldPresent;
64 | }
65 | }
66 |
67 | public void PresentIfAppropriate ()
68 | {
69 | try {
70 | if (ShouldPresent) {
71 |
72 | defs.SetBool (true, shownKey);
73 |
74 | SKStoreReviewController.RequestReview ();
75 | }
76 | }
77 | catch (Exception ex) {
78 | Log.Error (ex);
79 | }
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/Praeclarum/Praeclarum.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net9.0;net9.0-macos26.0;net9.0-ios26.0;net9.0-maccatalyst26.0
4 | disable
5 | false
6 | 1.0.0
7 | false
8 | true
9 | $(NoWarn);NU1900;XCODE_26_0_PREVIEW
10 |
11 |
12 | 12.2
13 |
14 |
15 | 10.0
16 |
17 |
18 | 15.0
19 |
20 |
21 | 12.0
22 |
23 |
24 | 21.0
25 |
26 |
27 | 10.0.17763.0
28 | 10.0.17763.0
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/Praeclarum.iOS/NSMutableAttributedStringWrapper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Praeclarum.Graphics;
3 | using System.Runtime.InteropServices;
4 |
5 | using Foundation;
6 | using ObjCRuntime;
7 | using NativeNSMutableAttributedString = Foundation.NSMutableAttributedString;
8 | using NativeCTStringAttributes = CoreText.CTStringAttributes;
9 | using CGColor = CoreGraphics.CGColor;
10 | #if __IOS__
11 | using NativeColor = UIKit.UIColor;
12 | #elif MONOMAC || __MACOS__
13 | using NativeColor = AppKit.NSColor;
14 | #endif
15 |
16 | namespace Praeclarum
17 | {
18 |
19 |
20 | public class NSMutableAttributedStringWrapper : IRichText
21 | {
22 | NativeNSMutableAttributedString s;
23 |
24 | public NSMutableAttributedStringWrapper (NativeNSMutableAttributedString ns)
25 | {
26 | s = ns;
27 | }
28 |
29 | public NSMutableAttributedStringWrapper (string data)
30 | {
31 | s = new NativeNSMutableAttributedString (data);
32 | }
33 |
34 | public NativeNSMutableAttributedString AttributedText {
35 | get { return s; }
36 | }
37 |
38 | #region NSMutableAttributedString implementation
39 |
40 | public void AddAttributes (IRichTextAttributes styleClass, StringRange range)
41 | {
42 | var attrs = ((NativeStringAttributesWrapper)styleClass).Attributes;
43 | s.AddAttributes (attrs, new NSRange (range.Location, range.Length));
44 | }
45 |
46 | #endregion
47 | }
48 |
49 | public static class StringRangeEx
50 | {
51 | public static StringRange ToStringRange (this NSRange range)
52 | {
53 | return new StringRange ((int)range.Location, (int)range.Length);
54 | }
55 |
56 | public static NSRange ToNSRange (this StringRange r)
57 | {
58 | return new NSRange (r.Location, r.Length);
59 | }
60 | }
61 |
62 | public static class NSDictionaryEx
63 | {
64 | [DllImport ("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation")]
65 | private static extern void CFDictionarySetValue (IntPtr theDict, IntPtr key, IntPtr value);
66 |
67 | public static void SetValue (this NSDictionary theDict, NSString key, INativeObject value)
68 | {
69 | SetValue (theDict.Handle, key.Handle, value.Handle);
70 | }
71 |
72 | static void SetValue (IntPtr theDict, IntPtr key, IntPtr value)
73 | {
74 | CFDictionarySetValue (theDict, key, value);
75 | }
76 |
77 | public static void AddAttributes (this NativeNSMutableAttributedString s, NativeCTStringAttributes a, StringRange r)
78 | {
79 | s.AddAttributes (a, new NSRange (r.Location, r.Length));
80 | }
81 | }
82 | }
83 |
84 |
--------------------------------------------------------------------------------
/Directory.Packages.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/Praeclarum.iOS/UI/GalleryViewController.cs:
--------------------------------------------------------------------------------
1 | #nullable enable
2 |
3 | using System;
4 | using System.Text.RegularExpressions;
5 | using System.Threading.Tasks;
6 | using Foundation;
7 | using UIKit;
8 | using WebKit;
9 |
10 | namespace Praeclarum.UI
11 | {
12 | public class GalleryViewController : UIViewController, IWKNavigationDelegate
13 | {
14 | private readonly string galleryUrl;
15 | private readonly Regex downloadUrlRe;
16 | WKWebView? webBrowser;
17 | UIBarButtonItem backItem;
18 |
19 | public Action<(NSUrl, Match)>? DownloadUrl;
20 |
21 | public GalleryViewController (string galleryUrl, Regex downloadUrlRe)
22 | {
23 | this.galleryUrl = galleryUrl;
24 | this.downloadUrlRe = downloadUrlRe;
25 | Title = "Gallery".Localize ();
26 |
27 | backItem = new UIBarButtonItem("Back", UIBarButtonItemStyle.Plain, (s, e) => {
28 | webBrowser?.GoBack();
29 | });
30 | backItem.Enabled = false;
31 | NavigationItem.LeftBarButtonItems = new UIBarButtonItem[] {
32 | backItem,
33 | };
34 |
35 | NavigationItem.RightBarButtonItems = new UIBarButtonItem[] {
36 | new UIBarButtonItem (UIBarButtonSystemItem.Done, (s, e) => {
37 | DownloadUrl = null;
38 | DismissViewController (true, null);
39 | }),
40 | };
41 | }
42 |
43 | public override void ViewDidLoad ()
44 | {
45 | base.ViewDidLoad ();
46 |
47 | var config = new WKWebViewConfiguration ();
48 | var vc = this;
49 | var vcv = vc.View;
50 | if (vcv is null) {
51 | return;
52 | }
53 | webBrowser = new WKWebView (vcv.Bounds, config) {
54 | AutoresizingMask = UIViewAutoresizing.FlexibleDimensions
55 | };
56 | webBrowser.NavigationDelegate = this;
57 | webBrowser.LoadRequest (new NSUrlRequest (new NSUrl (galleryUrl)));
58 | vcv.AddSubview (webBrowser);
59 | }
60 |
61 | [Export ("webView:decidePolicyForNavigationAction:decisionHandler:")]
62 | public void DecidePolicy (WKWebView webView, WKNavigationAction navigationAction, Action decisionHandler)
63 | {
64 | var url = navigationAction?.Request.Url;
65 | var urls = url?.AbsoluteString ?? "";
66 | var m = downloadUrlRe.Match (urls);
67 | if (url != null && m.Success) {
68 | decisionHandler (WKNavigationActionPolicy.Cancel);
69 | BeginInvokeOnMainThread (() => {
70 | DownloadUrl?.Invoke ((url, m));
71 | });
72 | }
73 | else {
74 | decisionHandler (WKNavigationActionPolicy.Allow);
75 | }
76 | }
77 |
78 | [Export ("webView:didFinishNavigation:")]
79 | public void DidFinishNavigation (WKWebView webView, WKNavigation navigation)
80 | {
81 | backItem.Enabled = webView.CanGoBack;
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/Praeclarum.Utilities/Praeclarum.Utilities.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0;net9.0-android;net9.0-ios26.0;net9.0-maccatalyst26.0;net9.0-macos26.0
5 |
6 | Praeclarum.Utilities
7 | 1.0.0
8 | praeclarum
9 | https://github.com/praeclarum/Praeclarum
10 | MIT
11 |
12 | enable
13 | enable
14 |
15 |
16 |
17 | 12.2
18 |
19 |
20 | 10.0
21 |
22 |
23 | 15.0
24 |
25 |
26 | 12.0
27 |
28 |
29 | 21.0
30 |
31 |
32 | 10.0.17763.0
33 | 10.0.17763.0
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | runtime; build; native; contentfiles; analyzers; buildtransitive
46 | all
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/Praeclarum.Android/UI/Canvas.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using Praeclarum.Graphics;
7 | using Android.Content;
8 |
9 | namespace Praeclarum.UI
10 | {
11 | public class View : global::Android.Views.View, IView
12 | {
13 | public View (Context context) :
14 | base (context)
15 | {
16 | Initialize ();
17 | }
18 |
19 | public View (Context context, global::Android.Util.IAttributeSet attrs) :
20 | base (context, attrs)
21 | {
22 | Initialize ();
23 | }
24 |
25 | public View (Context context, global::Android.Util.IAttributeSet attrs, int defStyle) :
26 | base (context, attrs, defStyle)
27 | {
28 | Initialize ();
29 | }
30 |
31 | void Initialize ()
32 | {
33 | }
34 |
35 | Color defaultBackgroundColor = Colors.Black;
36 |
37 | public Color BackgroundColor {
38 | get {
39 | try {
40 | return base.DrawingCacheBackgroundColor.ToColor ();
41 | } catch (Exception ex) {
42 | Debug.WriteLine (ex);
43 | return defaultBackgroundColor;
44 | }
45 | }
46 | set {
47 | try {
48 | base.SetBackgroundColor (value.ToColor ());
49 | } catch (Exception ex) {
50 | Debug.WriteLine (ex);
51 | }
52 | }
53 | }
54 |
55 | RectangleF IView.Bounds {
56 | get {
57 | return new RectangleF (0, 0, Width, Height);
58 | }
59 | }
60 |
61 | }
62 |
63 | public class Canvas : View, ICanvas
64 | {
65 | public event EventHandler TouchBegan = delegate {};
66 | public event EventHandler TouchMoved = delegate {};
67 | public event EventHandler TouchCancelled = delegate {};
68 | public event EventHandler TouchEnded = delegate {};
69 |
70 | public event EventHandler Drawing = delegate {};
71 |
72 | public Canvas () :
73 | base (DocumentListAppActivity.Shared)
74 | {
75 | Initialize ();
76 | }
77 |
78 | public Canvas (Context context) :
79 | base (context)
80 | {
81 | Initialize ();
82 | }
83 |
84 | public Canvas (Context context, global::Android.Util.IAttributeSet attrs) :
85 | base (context, attrs)
86 | {
87 | Initialize ();
88 | }
89 |
90 | public Canvas (Context context, global::Android.Util.IAttributeSet attrs, int defStyle) :
91 | base (context, attrs, defStyle)
92 | {
93 | Initialize ();
94 | }
95 |
96 | void Initialize ()
97 | {
98 | }
99 |
100 | public override void Draw (global::Android.Graphics.Canvas canvas)
101 | {
102 | base.Draw (canvas);
103 | }
104 |
105 | public void Invalidate (RectangleF dirtyRect)
106 | {
107 | Invalidate (dirtyRect.ToRect ());
108 | }
109 | }
110 | }
111 |
112 |
--------------------------------------------------------------------------------
/Praeclarum/IO/EmptyFileSystem.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using System.Collections.Generic;
4 | using System.Collections.ObjectModel;
5 |
6 | namespace Praeclarum.IO
7 | {
8 | public class EmptyFileSystem : IFileSystem
9 | {
10 | public ICollection FileExtensions { get; private set; }
11 |
12 | public EmptyFileSystem ()
13 | {
14 | FileExtensions = new Collection ();
15 | Description = "Empty";
16 | }
17 |
18 | #pragma warning disable 67
19 | public event EventHandler FilesChanged;
20 | #pragma warning restore 67
21 |
22 | public Task Initialize ()
23 | {
24 | return Task.FromResult