From 9012ec76b102dbb47dbaeebb1a7c40a7d843cce9 Mon Sep 17 00:00:00 2001 From: Xilosof Date: Tue, 13 Jul 2021 22:48:01 +0300 Subject: [PATCH 01/26] feat(AutoCleanForm): change deletion of msgs - made faster deletion of old messages; - added server error handling --- TelegramBotBase/Form/AutoCleanForm.cs | 46 ++++++++++++++++++--------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/TelegramBotBase/Form/AutoCleanForm.cs b/TelegramBotBase/Form/AutoCleanForm.cs index 4a7c6c5..d61f72e 100644 --- a/TelegramBotBase/Form/AutoCleanForm.cs +++ b/TelegramBotBase/Form/AutoCleanForm.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Text; +using System.Threading; using System.Threading.Tasks; +using Telegram.Bot.Exceptions; using Telegram.Bot.Types; using TelegramBotBase.Args; using TelegramBotBase.Attributes; @@ -127,25 +129,39 @@ namespace TelegramBotBase.Form /// public async Task MessageCleanup() { - while (this.OldMessages.Count > 0) + var oldMessages = OldMessages.AsEnumerable(); + + while (oldMessages.Any()) { - var tasks = new List(); - var msgs = this.OldMessages.Take(Constants.Telegram.MessageDeletionsPerSecond); - - foreach (var msg in msgs) + using var cts = new CancellationTokenSource(); + var deletedMessages = new ConcurrentBag(); + var parallelQuery = OldMessages.AsParallel() + .WithCancellation(cts.Token); + Task retryAfterTask = null; + try { - tasks.Add(this.Device.DeleteMessage(msg)); + parallelQuery.ForAll(i => + { + Device.DeleteMessage(i).GetAwaiter().GetResult(); + deletedMessages.Add(i); + }); + } + catch (AggregateException ex) + { + cts.Cancel(); + + var retryAfterSeconds = ex.InnerExceptions + .Where(e => e is ApiRequestException apiEx && apiEx.ErrorCode == 429) + .Max(e =>(int?) ((ApiRequestException)e).Parameters.RetryAfter) ?? 0; + retryAfterTask = Task.Delay(retryAfterSeconds * 1000); } - await Task.WhenAll(tasks); + oldMessages = oldMessages.Where(x => !deletedMessages.Contains(x)); + if (retryAfterTask != null) + await retryAfterTask; + } - foreach(var m in msgs) - { - Device.OnMessageDeleted(new MessageDeletedEventArgs(m)); - } - - this.OldMessages.RemoveRange(0, msgs.Count()); - } + OldMessages.Clear(); } } } From 1f7a9900c48ecb2a8a5e0e48dd35590bb43560ef Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Sat, 17 Jul 2021 18:36:56 +0200 Subject: [PATCH 02/26] Updating TelegramBotBaseTest project - adding new simple AddStartCommand, AddHelpCommand and AddSettingsCommand to Program.cs to make it easier --- TelegramBotBaseTest/Program.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/TelegramBotBaseTest/Program.cs b/TelegramBotBaseTest/Program.cs index d052ae5..6371196 100644 --- a/TelegramBotBaseTest/Program.cs +++ b/TelegramBotBaseTest/Program.cs @@ -7,7 +7,7 @@ using Telegram.Bot.Types; using TelegramBotBase; using TelegramBotBase.Form; using TelegramBotBaseTest.Tests; - +using TelegramBotBase.Commands; namespace TelegramBotBaseTest { class Program @@ -17,7 +17,9 @@ namespace TelegramBotBaseTest BotBase bb = new BotBase(APIKey); - bb.BotCommands.Add(new BotCommand() { Command = "start", Description = "Starts the bot" }); + bb.BotCommands.AddStartCommand("Starts the bot"); + bb.BotCommands.AddHelpCommand("Should show you some help"); + bb.BotCommands.AddSettingsCommand("Should show you some settings"); bb.BotCommands.Add(new BotCommand() { Command = "form1", Description = "Opens test form 1" }); bb.BotCommands.Add(new BotCommand() { Command = "form2", Description = "Opens test form 2" }); bb.BotCommands.Add(new BotCommand() { Command = "params", Description = "Returns all send parameters as a message." }); From 177c1989160f0cf9847d7f389283d1b1a7d34b05 Mon Sep 17 00:00:00 2001 From: Xilosof Date: Fri, 23 Jul 2021 16:47:42 +0300 Subject: [PATCH 03/26] Added event call --- TelegramBotBase/Form/AutoCleanForm.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TelegramBotBase/Form/AutoCleanForm.cs b/TelegramBotBase/Form/AutoCleanForm.cs index d61f72e..e2db50d 100644 --- a/TelegramBotBase/Form/AutoCleanForm.cs +++ b/TelegramBotBase/Form/AutoCleanForm.cs @@ -155,6 +155,8 @@ namespace TelegramBotBase.Form .Max(e =>(int?) ((ApiRequestException)e).Parameters.RetryAfter) ?? 0; retryAfterTask = Task.Delay(retryAfterSeconds * 1000); } + + deletedMessages.AsParallel().ForAll(i => Device.OnMessageDeleted(new MessageDeletedEventArgs(i))); oldMessages = oldMessages.Where(x => !deletedMessages.Contains(x)); if (retryAfterTask != null) From be2660239a857623c1ceff98943e634877570bfa Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Sun, 25 Jul 2021 01:53:55 +0200 Subject: [PATCH 04/26] Creating DeviceSession and other stuff slight earlier in the LoadSessionStates methods --- TelegramBotBase/SessionBase.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/TelegramBotBase/SessionBase.cs b/TelegramBotBase/SessionBase.cs index 410de80..33dc0fb 100644 --- a/TelegramBotBase/SessionBase.cs +++ b/TelegramBotBase/SessionBase.cs @@ -208,6 +208,13 @@ namespace TelegramBotBase } } + form.Client = Client; + var device = new DeviceSession(s.DeviceId, form); + + device.ChatTitle = s.ChatTitle; + + this.SessionList.Add(s.DeviceId, device); + //Is Subclass of IStateForm var iform = form as IStateForm; if (iform != null) @@ -217,14 +224,6 @@ namespace TelegramBotBase iform.LoadState(ls); } - - form.Client = Client; - var device = new DeviceSession(s.DeviceId, form); - - device.ChatTitle = s.ChatTitle; - - this.SessionList.Add(s.DeviceId, device); - try { await form.OnInit(new InitEventArgs()); From ae9dd17ae3b2a55d864d4d1ba1d86922f05d7394 Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Sun, 25 Jul 2021 01:54:16 +0200 Subject: [PATCH 05/26] Adding NavigationController for better and optional Push/Pop navigation --- TelegramBotBase/Base/FormBase.cs | 3 + .../Form/Navigation/NavigationController.cs | 346 ++++++++++++++++++ 2 files changed, 349 insertions(+) create mode 100644 TelegramBotBase/Form/Navigation/NavigationController.cs diff --git a/TelegramBotBase/Base/FormBase.cs b/TelegramBotBase/Base/FormBase.cs index f4ae896..fb1cb69 100644 --- a/TelegramBotBase/Base/FormBase.cs +++ b/TelegramBotBase/Base/FormBase.cs @@ -6,6 +6,7 @@ using System.Text; using System.Threading.Tasks; using TelegramBotBase.Args; using TelegramBotBase.Base; +using TelegramBotBase.Form.Navigation; using TelegramBotBase.Sessions; using static TelegramBotBase.Base.Async; @@ -17,6 +18,8 @@ namespace TelegramBotBase.Form public class FormBase : IDisposable { + public NavigationController NavigationController { get; set; } + public DeviceSession Device { get; set; } public MessageClient Client { get; set; } diff --git a/TelegramBotBase/Form/Navigation/NavigationController.cs b/TelegramBotBase/Form/Navigation/NavigationController.cs new file mode 100644 index 0000000..21d47b7 --- /dev/null +++ b/TelegramBotBase/Form/Navigation/NavigationController.cs @@ -0,0 +1,346 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TelegramBotBase.Args; +using TelegramBotBase.Attributes; +using TelegramBotBase.Base; +using TelegramBotBase.Interfaces; + +namespace TelegramBotBase.Form.Navigation +{ + [DebuggerDisplay("{Index+1} Forms")] + public class NavigationController : FormBase, IStateForm + { + + [SaveState] + private List History { get; set; } + + [SaveState] + public int Index { get; set; } + + /// + /// Will replace the controller when poping a form with last last remaining form. + /// + [SaveState] + public bool ForceCleanupOnLastPop { get; set; } + + public NavigationController() + { + History = new List(); + Index = -1; + ForceCleanupOnLastPop = true; + + this.Init += NavigationController_Init; + this.Opened += NavigationController_Opened; + this.Closed += NavigationController_Closed; + } + + public NavigationController(FormBase startForm, params FormBase[] forms) : this() + { + this.Client = startForm.Client; + this.Device = startForm.Device; + startForm.NavigationController = this; + + History.Add(startForm); + Index = 0; + + if (forms.Length > 0) + { + History.AddRange(forms); + Index = History.Count - 1; + } + } + + private async Task NavigationController_Init(object sender, Args.InitEventArgs e) + { + if (CurrentForm == null) + return; + + await CurrentForm.OnInit(e); + } + + private async Task NavigationController_Opened(object sender, EventArgs e) + { + if (CurrentForm == null) + return; + + await CurrentForm.OnOpened(e); + } + + private async Task NavigationController_Closed(object sender, EventArgs e) + { + if (CurrentForm == null) + return; + + await CurrentForm.OnClosed(e); + } + + + + /// + /// Remove the current active form on the stack. + /// + /// + public virtual async Task PopAsync() + { + if (History.Count == 0) + return; + + var form = History[Index]; + + form.NavigationController = null; + History.Remove(form); + Index--; + + Device.FormSwitched = true; + + await form.OnClosed(new EventArgs()); + + //Leave NavigationController and move to the last one + if (ForceCleanupOnLastPop && History.Count == 1) + { + var last_form = History[0]; + last_form.NavigationController = null; + await this.NavigateTo(last_form); + return; + } + + if (History.Count > 0) + { + form = History[Index]; + await form.OnOpened(new EventArgs()); + } + } + + /// + /// Pushing the given form to the stack and renders it. + /// + /// + /// + public virtual async Task PushAsync(FormBase form, params object[] args) + { + form.Client = this.Client; + form.Device = this.Device; + form.NavigationController = this; + + this.History.Add(form); + Index++; + + Device.FormSwitched = true; + + if (Index < 2) + return; + + await form.OnInit(new InitEventArgs(args)); + + await form.OnOpened(new EventArgs()); + } + + /// + /// Pops the current form and pushes a new one. + /// Will help to remove forms so you can not navigate back to them. + /// + /// + /// + /// + public virtual async Task PushAndReplaceAsync(FormBase form, params object[] args) + { + await PopAsync(); + + await PushAsync(form, args); + } + + /// + /// Returns the current form from the stack. + /// + public FormBase CurrentForm + { + get + { + if (this.History.Count == 0) + return null; + + return this.History[Index]; + } + } + + public List GetAllForms() + { + return History.ToList(); + } + + + public void LoadState(LoadStateEventArgs e) + { + if (e.Get("$controller.history.count") == null) + return; + + + int historyCount = e.GetInt("$controller.history.count"); + + for (int i = 0; i < historyCount; i++) + { + + var c = e.GetObject($"$controller.history[{i}]") as Dictionary; + + + + var qname = e.Get($"$controller.history[{i}].type"); + + if (qname == null) + continue; + + Type t = Type.GetType(qname.ToString()); + if (t == null || !t.IsSubclassOf(typeof(FormBase))) + { + continue; + } + + var form = t.GetConstructor(new Type[] { })?.Invoke(new object[] { }) as FormBase; + + //No default constructor, fallback + if (form == null) + { + continue; + } + + var properties = c.Where(a => a.Key.StartsWith("$")); + + var fields = form.GetType().GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic).Where(a => a.GetCustomAttributes(typeof(SaveState), true).Length != 0).ToList(); + + foreach (var p in properties) + { + var f = fields.FirstOrDefault(a => a.Name == p.Key.Substring(1)); + if (f == null) + continue; + + try + { + if (f.PropertyType.IsEnum) + { + var ent = Enum.Parse(f.PropertyType, p.Value.ToString()); + + f.SetValue(form, ent); + + continue; + } + + + f.SetValue(form, p.Value); + } + catch (ArgumentException ex) + { + + Tools.Conversion.CustomConversionChecks(form, p, f); + + } + catch + { + + } + } + + form.Device = this.Device; + form.Client = this.Client; + form.NavigationController = this; + + form.OnInit(new InitEventArgs()); + + this.History.Add(form); + } + + } + + public void SaveState(SaveStateEventArgs e) + { + e.Set("$controller.history.count", History.Count.ToString()); + + int i = 0; + foreach (var form in History) + { + var fields = form.GetType().GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic).Where(a => a.GetCustomAttributes(typeof(SaveState), true).Length != 0).ToList(); + + var dt = new Dictionary(); + foreach (var f in fields) + { + var val = f.GetValue(form); + + dt.Add("$" + f.Name, val); + } + + e.Set($"$controller.history[{i}].type", form.GetType().AssemblyQualifiedName); + + e.SetObject($"$controller.history[{i}]", dt); + + i++; + } + } + + + #region "Methods passthrough" + + public override async Task NavigateTo(FormBase newForm, params object[] args) + { + await CurrentForm.NavigateTo(newForm, args); + } + + public override async Task LoadControls(MessageResult message) + { + await CurrentForm.LoadControls(message); + } + + public override async Task Load(MessageResult message) + { + await CurrentForm.Load(message); + } + + public override async Task ActionControls(MessageResult message) + { + await CurrentForm.ActionControls(message); + } + + public override async Task Action(MessageResult message) + { + await CurrentForm.Action(message); + } + + + + public override async Task Edited(MessageResult message) + { + await CurrentForm.Edited(message); + } + + public override async Task Render(MessageResult message) + { + await CurrentForm.Render(message); + } + + public override async Task RenderControls(MessageResult message) + { + await CurrentForm.RenderControls(message); + } + + public override async Task PreLoad(MessageResult message) + { + await CurrentForm.PreLoad(message); + } + + public override async Task ReturnFromModal(ModalDialog modal) + { + await CurrentForm.ReturnFromModal(modal); + } + + public override async Task SentData(DataResult message) + { + await CurrentForm.SentData(message); + } + + + #endregion + + } +} From 6f005114bcb62c674897fd75cf1da4b12ad23c5b Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Sun, 25 Jul 2021 16:33:05 +0200 Subject: [PATCH 06/26] Update NavigationController.cs - Adding PopToRootAsync method - small comment change --- .../Form/Navigation/NavigationController.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/TelegramBotBase/Form/Navigation/NavigationController.cs b/TelegramBotBase/Form/Navigation/NavigationController.cs index 21d47b7..07086d8 100644 --- a/TelegramBotBase/Form/Navigation/NavigationController.cs +++ b/TelegramBotBase/Form/Navigation/NavigationController.cs @@ -22,7 +22,7 @@ namespace TelegramBotBase.Form.Navigation public int Index { get; set; } /// - /// Will replace the controller when poping a form with last last remaining form. + /// Will replace the controller when poping a form to the root form. /// [SaveState] public bool ForceCleanupOnLastPop { get; set; } @@ -115,6 +115,21 @@ namespace TelegramBotBase.Form.Navigation } } + /// + /// Pop's through all forms back to the root form. + /// + /// + public virtual async Task PopToRootAsync() + { + if (Index == 0) + return; + + while (History.Count > 1) + { + await PopAsync(); + } + } + /// /// Pushing the given form to the stack and renders it. /// From 9eae63dae6d14b8686df284469b9abe3544f55ba Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Sun, 25 Jul 2021 16:34:29 +0200 Subject: [PATCH 07/26] Updating Testproject - adding example for NavigationController - readability improvement in Menu --- TelegramBotBaseTest/Tests/Menu.cs | 43 +++++---- .../Tests/Navigation/CustomController.cs | 44 ++++++++++ TelegramBotBaseTest/Tests/Navigation/Form1.cs | 88 +++++++++++++++++++ TelegramBotBaseTest/Tests/Navigation/Start.cs | 88 +++++++++++++++++++ 4 files changed, 240 insertions(+), 23 deletions(-) create mode 100644 TelegramBotBaseTest/Tests/Navigation/CustomController.cs create mode 100644 TelegramBotBaseTest/Tests/Navigation/Form1.cs create mode 100644 TelegramBotBaseTest/Tests/Navigation/Start.cs diff --git a/TelegramBotBaseTest/Tests/Menu.cs b/TelegramBotBaseTest/Tests/Menu.cs index 57cf9fb..e4de4f0 100644 --- a/TelegramBotBaseTest/Tests/Menu.cs +++ b/TelegramBotBaseTest/Tests/Menu.cs @@ -41,7 +41,9 @@ namespace TelegramBotBaseTest.Tests if (call == null) return; - switch(call.Value) + message.Handled = true; + + switch (call.Value) { case "text": @@ -93,8 +95,6 @@ namespace TelegramBotBaseTest.Tests case "data": - message.Handled = true; - var data = new DataForm(); await this.NavigateTo(data); @@ -103,8 +103,6 @@ namespace TelegramBotBaseTest.Tests case "calendar": - message.Handled = true; - var calendar = new Controls.CalendarPickerForm(); await this.NavigateTo(calendar); @@ -113,8 +111,6 @@ namespace TelegramBotBaseTest.Tests case "month": - message.Handled = true; - var month = new Controls.MonthPickerForm(); await this.NavigateTo(month); @@ -123,8 +119,6 @@ namespace TelegramBotBaseTest.Tests case "treeview": - message.Handled = true; - var tree = new Controls.TreeViewForms(); await this.NavigateTo(tree); @@ -133,8 +127,6 @@ namespace TelegramBotBaseTest.Tests case "togglebuttons": - message.Handled = true; - var tb = new Controls.ToggleButtons(); await this.NavigateTo(tb); @@ -143,8 +135,6 @@ namespace TelegramBotBaseTest.Tests case "multitogglebuttons": - message.Handled = true; - var mtb = new Controls.MultiToggleButtons(); await this.NavigateTo(mtb); @@ -153,8 +143,6 @@ namespace TelegramBotBaseTest.Tests case "buttongrid": - message.Handled = true; - var bg = new Controls.ButtonGridForm(); await this.NavigateTo(bg); @@ -163,8 +151,6 @@ namespace TelegramBotBaseTest.Tests case "buttongridfilter": - message.Handled = true; - var bg2 = new Controls.ButtonGridPagingForm(); await this.NavigateTo(bg2); @@ -173,8 +159,6 @@ namespace TelegramBotBaseTest.Tests case "buttongridtags": - message.Handled = true; - var bg3 = new Controls.ButtonGridTagForm(); await this.NavigateTo(bg3); @@ -183,8 +167,6 @@ namespace TelegramBotBaseTest.Tests case "multiview": - message.Handled = true; - var mvf = new MultiViewForm(); await NavigateTo(mvf); @@ -194,13 +176,26 @@ namespace TelegramBotBaseTest.Tests case "checkedbuttonlist": - message.Handled = true; - var cbl = new CheckedButtonListForm(); await NavigateTo(cbl); + break; + + case "navigationcontroller": + + var nc = new Navigation.Start(); + + await NavigateTo(nc); + + + break; + + default: + + message.Handled = false; + break; } @@ -240,6 +235,8 @@ namespace TelegramBotBaseTest.Tests btn.AddButtonRow(new ButtonBase("#16 CheckedButtonList", new CallbackData("a", "checkedbuttonlist").Serialize())); + btn.AddButtonRow(new ButtonBase("#17 NavigationController (Push/Pop)", new CallbackData("a", "navigationcontroller").Serialize())); + await this.Device.Send("Choose your test:", btn); } diff --git a/TelegramBotBaseTest/Tests/Navigation/CustomController.cs b/TelegramBotBaseTest/Tests/Navigation/CustomController.cs new file mode 100644 index 0000000..e5b7205 --- /dev/null +++ b/TelegramBotBaseTest/Tests/Navigation/CustomController.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using TelegramBotBase.Form; +using TelegramBotBase.Form.Navigation; + +namespace TelegramBotBaseTest.Tests.Navigation +{ + class CustomController : NavigationController + { + public CustomController(FormBase form) : base(form) + { + + } + + + public override Task PushAsync(FormBase form, params object[] args) + { + Console.WriteLine($"Pushes form (Count on stack {this.Index + 1})"); + //Device.Send($"Pushes form (Count on stack {this.Index + 1})"); + + return base.PushAsync(form, args); + } + + + public override Task PopAsync() + { + Console.WriteLine($"Pops one form (Count on stack {this.Index + 1})"); + //Device.Send($"Pops one form (Count on stack {this.Index + 1})"); + + return base.PopAsync(); + } + + public override Task PopToRootAsync() + { + Console.WriteLine($"Moved back to root (Count on stack {this.Index + 1})"); + //Device.Send($"Moved back to root (Count on stack {this.Index + 1})"); + + return base.PopToRootAsync(); + } + + } +} diff --git a/TelegramBotBaseTest/Tests/Navigation/Form1.cs b/TelegramBotBaseTest/Tests/Navigation/Form1.cs new file mode 100644 index 0000000..29e37fc --- /dev/null +++ b/TelegramBotBaseTest/Tests/Navigation/Form1.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Telegram.Bot.Types; +using TelegramBotBase.Base; +using TelegramBotBase.Form; + +namespace TelegramBotBaseTest.Tests.Navigation +{ + public class Form1 : FormBase + { + Message msg = null; + + public Form1() + { + this.Closed += Form1_Closed; + } + + private async Task Form1_Closed(object sender, EventArgs e) + { + if (msg == null) + return; + + await Device.DeleteMessage(msg); + } + + public override async Task Action(MessageResult message) + { + if (message.Handled) + return; + + await message.ConfirmAction(); + + switch (message.RawData) + { + case "next": + + message.Handled = true; + + var f1 = new Form1(); + + await NavigationController.PushAsync(f1); + + + break; + + case "previous": + + message.Handled = true; + + //Pop's the current form and move the previous one. The root form will be the Start class. + await NavigationController.PopAsync(); + + break; + + case "root": + + message.Handled = true; + + await NavigationController.PopToRootAsync(); + + break; + + + } + + + + } + + public override async Task Render(MessageResult message) + { + if (msg != null) + return; + + var bf = new ButtonForm(); + bf.AddButtonRow("Next page", "next"); + bf.AddButtonRow("Previous page", "previous"); + bf.AddButtonRow("Back to root", "root"); + + msg = await Device.Send($"Choose your options (Count on stack {NavigationController.Index + 1})", bf); + + } + + + } +} diff --git a/TelegramBotBaseTest/Tests/Navigation/Start.cs b/TelegramBotBaseTest/Tests/Navigation/Start.cs new file mode 100644 index 0000000..07402fa --- /dev/null +++ b/TelegramBotBaseTest/Tests/Navigation/Start.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Telegram.Bot.Types; +using TelegramBotBase.Base; +using TelegramBotBase.Form; +using TelegramBotBase.Form.Navigation; + +namespace TelegramBotBaseTest.Tests.Navigation +{ + public class Start : FormBase + { + + Message msg = null; + + public Start() + { + this.Closed += Start_Closed; + } + + private async Task Start_Closed(object sender, EventArgs e) + { + if (msg == null) + return; + + await Device.DeleteMessage(msg); + } + + public override async Task Load(MessageResult message) + { + + + + } + + public override async Task Action(MessageResult message) + { + if (message.Handled) + return; + + await message.ConfirmAction(); + + switch (message.RawData) + { + case "yes": + + message.Handled = true; + + //Create navigation controller and navigate to it, keep the current form as root form so we can get back to here later + var nc = new CustomController(this); + + var f1 = new Form1(); + + await NavigateTo(nc); + + await nc.PushAsync(f1); + + + break; + case "no": + + message.Handled = true; + + var mn = new Menu(); + + await NavigateTo(mn); + + + break; + } + + } + + public override async Task Render(MessageResult message) + { + var bf = new ButtonForm(); + + bf.AddButtonRow("Yes", "yes"); + bf.AddButtonRow("No", "no"); + + msg = await Device.Send("Open controller?", bf); + + + } + + } +} From 5bae650edc69a90966c33e802bca6bc4dabd5a84 Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Sun, 25 Jul 2021 16:35:15 +0200 Subject: [PATCH 08/26] Updating compilation targets - Removing .NET 4.7.2 and and replacing it with .NET 5 --- TelegramBotBase/TelegramBotBase.csproj | 14 +++++++++++--- TelegramBotBaseTest/TelegramBotBaseTest.csproj | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/TelegramBotBase/TelegramBotBase.csproj b/TelegramBotBase/TelegramBotBase.csproj index 3f7a020..a3f2979 100644 --- a/TelegramBotBase/TelegramBotBase.csproj +++ b/TelegramBotBase/TelegramBotBase.csproj @@ -1,13 +1,13 @@  - netstandard2.1;net472;net5;netcoreapp3.1 + netstandard2.1;net5;netcoreapp3.1 false false true https://github.com/MajMcCloud/TelegramBotFramework https://github.com/MajMcCloud/TelegramBotFramework - - moving from .Net Framework 4.7.2 to .Net Standard 2.1 for the Library and .Net Core 3.1 for the test project! + - moving from .Net Framework 4.7.2 to .Net Standard 2.1 for the Library and .Net Core 3.1 for the test project! Debug;Release; MIT false @@ -78,11 +78,19 @@ + + + + + + + + - + diff --git a/TelegramBotBaseTest/TelegramBotBaseTest.csproj b/TelegramBotBaseTest/TelegramBotBaseTest.csproj index a06a668..f52e456 100644 --- a/TelegramBotBaseTest/TelegramBotBaseTest.csproj +++ b/TelegramBotBaseTest/TelegramBotBaseTest.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp3.0;net472 + netcoreapp3.1;net5 false Debug;Release From ce4c7b6f1cf657f49732df394498c9d99002bb3a Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Sun, 25 Jul 2021 16:36:43 +0200 Subject: [PATCH 09/26] Update TelegramBotBase.csproj --- TelegramBotBase/TelegramBotBase.csproj | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/TelegramBotBase/TelegramBotBase.csproj b/TelegramBotBase/TelegramBotBase.csproj index a3f2979..19fe695 100644 --- a/TelegramBotBase/TelegramBotBase.csproj +++ b/TelegramBotBase/TelegramBotBase.csproj @@ -62,32 +62,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - From 47a5b502b906ff9f48c2cb2a363d18ccacfa55f8 Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Sun, 25 Jul 2021 16:38:53 +0200 Subject: [PATCH 10/26] Updating package targetFramework to .NET 5 --- TelegramBotBaseTest/packages.config | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TelegramBotBaseTest/packages.config b/TelegramBotBaseTest/packages.config index 8ea6ef6..d654912 100644 --- a/TelegramBotBaseTest/packages.config +++ b/TelegramBotBaseTest/packages.config @@ -1,6 +1,6 @@  - - - + + + \ No newline at end of file From fd7d72b986d6018f5b22278e517f0d2051cc30c3 Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Sun, 25 Jul 2021 16:40:09 +0200 Subject: [PATCH 11/26] Update NavigationController.cs --- TelegramBotBase/Form/Navigation/NavigationController.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/TelegramBotBase/Form/Navigation/NavigationController.cs b/TelegramBotBase/Form/Navigation/NavigationController.cs index 07086d8..d1858b9 100644 --- a/TelegramBotBase/Form/Navigation/NavigationController.cs +++ b/TelegramBotBase/Form/Navigation/NavigationController.cs @@ -121,10 +121,7 @@ namespace TelegramBotBase.Form.Navigation /// public virtual async Task PopToRootAsync() { - if (Index == 0) - return; - - while (History.Count > 1) + while (Index > 0) { await PopAsync(); } From 2158f53cde20daae1959efef64da540bc11bf6b8 Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Sun, 25 Jul 2021 17:44:43 +0200 Subject: [PATCH 12/26] Fixing ReplyKeyboard flicker on TaggedButtonGrid - fixing ReplyKeyboard flicker - updating message handling on existing messages --- .../Controls/Hybrid/TaggedButtonGrid.cs | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs b/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs index 31028e9..cb9a10a 100644 --- a/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs +++ b/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs @@ -506,21 +506,15 @@ namespace TelegramBotBase.Controls.Hybrid //Reply Keyboard could only be updated with a new keyboard. case eKeyboardType.ReplyKeyboard: - if (this.MessageId != null) + if (form.Count == 0) { - if (form.Count == 0) + if (this.MessageId != null) { await this.Device.HideReplyKeyboard(); this.MessageId = null; - return; } - - if (this.DeletePreviousMessage) - await this.Device.DeleteMessage(this.MessageId.Value); - } - - if (form.Count == 0) return; + } var rkm = (ReplyKeyboardMarkup)form; @@ -528,6 +522,10 @@ namespace TelegramBotBase.Controls.Hybrid rkm.OneTimeKeyboard = this.OneTimeKeyboard; m = await this.Device.Send(this.Title, rkm, disableNotification: true, parseMode: MessageParseMode, MarkdownV2AutoEscape: false); + //Prevent flicker of keyboard + if (this.DeletePreviousMessage && this.MessageId != null) + await this.Device.DeleteMessage(this.MessageId.Value); + break; case eKeyboardType.InlineKeyBoard: @@ -636,21 +634,18 @@ namespace TelegramBotBase.Controls.Hybrid //Reply Keyboard could only be updated with a new keyboard. case eKeyboardType.ReplyKeyboard: - if (this.MessageId != null) + if (bf.Count == 0) { - if (bf.Count == 0) + if (this.MessageId != null) { await this.Device.HideReplyKeyboard(); this.MessageId = null; - return; } - - if (this.DeletePreviousMessage) - await this.Device.DeleteMessage(this.MessageId.Value); + return; } - if (bf.Count == 0) - return; + //if (bf.Count == 0) + // return; var rkm = (ReplyKeyboardMarkup)bf; @@ -658,6 +653,10 @@ namespace TelegramBotBase.Controls.Hybrid rkm.OneTimeKeyboard = this.OneTimeKeyboard; m = await this.Device.Send("Choose category", rkm, disableNotification: true, parseMode: MessageParseMode, MarkdownV2AutoEscape: false); + //Prevent flicker of keyboard + if (this.DeletePreviousMessage && this.MessageId != null) + await this.Device.DeleteMessage(this.MessageId.Value); + break; case eKeyboardType.InlineKeyBoard: From 0db0c00cd042dcbae7bdac04a04a56631d8f3339 Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Sun, 25 Jul 2021 22:25:29 +0200 Subject: [PATCH 13/26] Update example project --- .../Tests/Controls/CheckedButtonListForm.cs | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/TelegramBotBaseTest/Tests/Controls/CheckedButtonListForm.cs b/TelegramBotBaseTest/Tests/Controls/CheckedButtonListForm.cs index 3fd162e..eeeae3a 100644 --- a/TelegramBotBaseTest/Tests/Controls/CheckedButtonListForm.cs +++ b/TelegramBotBaseTest/Tests/Controls/CheckedButtonListForm.cs @@ -35,12 +35,12 @@ namespace TelegramBotBaseTest.Tests.Controls ButtonForm bf = new ButtonForm(); - for(int i = 0;i < 30;i++) + for (int i = 0; i < 30; i++) { - bf.AddButtonRow($"{i}. Item", i.ToString()); + bf.AddButtonRow($"{i + 1}. Item", i.ToString()); } - m_Buttons.ButtonsForm = bf; + m_Buttons.DataSource = new TelegramBotBase.Datasources.ButtonFormDataSource(bf); m_Buttons.ButtonClicked += Bg_ButtonClicked; m_Buttons.CheckedChanged += M_Buttons_CheckedChanged; @@ -58,29 +58,32 @@ namespace TelegramBotBaseTest.Tests.Controls if (e.Button == null) return; - if (e.Button.Value == "back") + switch (e.Button.Value) { - var start = new Menu(); - await this.NavigateTo(start); - } - else if (e.Button.Value == "switch") - { - switch (m_Buttons.KeyboardType) - { - case TelegramBotBase.Enums.eKeyboardType.ReplyKeyboard: - m_Buttons.KeyboardType = TelegramBotBase.Enums.eKeyboardType.InlineKeyBoard; - break; - case TelegramBotBase.Enums.eKeyboardType.InlineKeyBoard: - m_Buttons.KeyboardType = TelegramBotBase.Enums.eKeyboardType.ReplyKeyboard; - break; - } + case "back": + + var start = new Menu(); + await NavigateTo(start); + break; - } - else - { + case "switch": + switch (m_Buttons.KeyboardType) + { + case TelegramBotBase.Enums.eKeyboardType.ReplyKeyboard: + m_Buttons.KeyboardType = TelegramBotBase.Enums.eKeyboardType.InlineKeyBoard; + break; + case TelegramBotBase.Enums.eKeyboardType.InlineKeyBoard: + m_Buttons.KeyboardType = TelegramBotBase.Enums.eKeyboardType.ReplyKeyboard; + break; + } - await this.Device.Send($"Button clicked with Text: {e.Button.Text} and Value {e.Button.Value}"); + + break; + + default: + await Device.Send($"Button clicked with Text: {e.Button.Text} and Value {e.Button.Value}"); + break; } From f28c9b1e2245dc1a59f3f0a7a3341867f7a55b61 Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Mon, 26 Jul 2021 01:22:58 +0200 Subject: [PATCH 14/26] Add DeleteReplyMessage property --- TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs b/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs index cb9a10a..78570e4 100644 --- a/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs +++ b/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs @@ -47,6 +47,11 @@ namespace TelegramBotBase.Controls.Hybrid public bool DeletePreviousMessage { get; set; } = true; + /// + /// Removes the reply message from a user. + /// + public bool DeleteReplyMessage { get; set; } = true; + /// /// Parsemode of the message. /// @@ -213,7 +218,7 @@ namespace TelegramBotBase.Controls.Hybrid await OnButtonClicked(new ButtonClickedEventArgs(button, index)); //Remove button click message - if (this.DeletePreviousMessage) + if (this.DeleteReplyMessage) await Device.DeleteMessage(result.MessageId); result.Handled = true; From 5f49e25458506273d840d2393c1db89538fb669b Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Mon, 26 Jul 2021 01:23:31 +0200 Subject: [PATCH 15/26] Adding CheckAll and UncheckAllTags methods --- .../Controls/Hybrid/TaggedButtonGrid.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs b/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs index 78570e4..7e5022b 100644 --- a/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs +++ b/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs @@ -845,8 +845,27 @@ namespace TelegramBotBase.Controls.Hybrid } + /// + /// Checks all tags for filtering. + /// + public void CheckAllTags() + { + this.SelectedTags.Clear(); + this.SelectedTags = this.Tags.Select(a => a).ToList(); + this.Updated(); + + } + + /// + /// Unchecks all tags for filtering. + /// + public void UncheckAllTags() + { + this.SelectedTags.Clear(); + + this.Updated(); } } From 84d18f6f45aa340a5afe5e34744a688e23d14600 Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Mon, 26 Jul 2021 01:26:33 +0200 Subject: [PATCH 16/26] Small readability improvements --- TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs b/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs index 7e5022b..d97f920 100644 --- a/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs +++ b/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs @@ -199,7 +199,7 @@ namespace TelegramBotBase.Controls.Hybrid if (!result.IsFirstHandler) return; - if (result.MessageText == null) + if (result.MessageText == null || result.MessageText == "") return; var button = HeadLayoutButtonRow?.FirstOrDefault(a => a.Text.Trim() == result.MessageText) @@ -328,16 +328,16 @@ namespace TelegramBotBase.Controls.Hybrid public async override Task Action(MessageResult result, string value = null) { - //Find clicked button depending on Text or Value (depending on markup type) - if (this.KeyboardType != eKeyboardType.InlineKeyBoard) - return; - if (result.Handled) return; if (!result.IsFirstHandler) return; + //Find clicked button depending on Text or Value (depending on markup type) + if (this.KeyboardType != eKeyboardType.InlineKeyBoard) + return; + await result.ConfirmAction(this.ConfirmationText ?? ""); var button = HeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData) From 6c2543630d3a64ef9d9165fc59b619ac19314deb Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Mon, 26 Jul 2021 13:32:32 +0200 Subject: [PATCH 17/26] Adding .NET 4.7.2 back into with conditional compilation https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives --- TelegramBotBase/Form/AutoCleanForm.cs | 38 ++++++++++++++++++++++++++ TelegramBotBase/TelegramBotBase.csproj | 2 +- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/TelegramBotBase/Form/AutoCleanForm.cs b/TelegramBotBase/Form/AutoCleanForm.cs index e2db50d..5617775 100644 --- a/TelegramBotBase/Form/AutoCleanForm.cs +++ b/TelegramBotBase/Form/AutoCleanForm.cs @@ -131,6 +131,7 @@ namespace TelegramBotBase.Form { var oldMessages = OldMessages.AsEnumerable(); +#if !NET472 while (oldMessages.Any()) { using var cts = new CancellationTokenSource(); @@ -162,6 +163,43 @@ namespace TelegramBotBase.Form if (retryAfterTask != null) await retryAfterTask; } +#else + while (oldMessages.Any()) + { + using (var cts = new CancellationTokenSource()) + { + var deletedMessages = new ConcurrentBag(); + var parallelQuery = OldMessages.AsParallel() + .WithCancellation(cts.Token); + Task retryAfterTask = null; + try + { + parallelQuery.ForAll(i => + { + Device.DeleteMessage(i).GetAwaiter().GetResult(); + deletedMessages.Add(i); + }); + } + catch (AggregateException ex) + { + cts.Cancel(); + + var retryAfterSeconds = ex.InnerExceptions + .Where(e => e is ApiRequestException apiEx && apiEx.ErrorCode == 429) + .Max(e => (int?)((ApiRequestException)e).Parameters.RetryAfter) ?? 0; + retryAfterTask = Task.Delay(retryAfterSeconds * 1000); + } + + deletedMessages.AsParallel().ForAll(i => Device.OnMessageDeleted(new MessageDeletedEventArgs(i))); + + oldMessages = oldMessages.Where(x => !deletedMessages.Contains(x)); + if (retryAfterTask != null) + await retryAfterTask; + } + } + + +#endif OldMessages.Clear(); } diff --git a/TelegramBotBase/TelegramBotBase.csproj b/TelegramBotBase/TelegramBotBase.csproj index 19fe695..0b31660 100644 --- a/TelegramBotBase/TelegramBotBase.csproj +++ b/TelegramBotBase/TelegramBotBase.csproj @@ -1,7 +1,7 @@  - netstandard2.1;net5;netcoreapp3.1 + netstandard2.1;net472;net5;netcoreapp3.1 false false true From 894d0799c8abc465862771262743ae2821dc8aca Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Mon, 26 Jul 2021 14:55:29 +0200 Subject: [PATCH 18/26] Localizations updated - moved German Localization to its seperate file - cleaned up Localization class --- TelegramBotBase/Localizations/English.cs | 2 ++ TelegramBotBase/Localizations/German.cs | 27 ++++++++++++++++++- TelegramBotBase/Localizations/Localization.cs | 25 +---------------- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/TelegramBotBase/Localizations/English.cs b/TelegramBotBase/Localizations/English.cs index 1082c21..d340291 100644 --- a/TelegramBotBase/Localizations/English.cs +++ b/TelegramBotBase/Localizations/English.cs @@ -16,6 +16,8 @@ namespace TelegramBotBase.Localizations Values["ButtonGrid_CurrentPage"] = "Page {0} of {1}"; Values["ButtonGrid_SearchFeature"] = "💡 Send a message to filter the list. Click the 🔍 to reset the filter."; Values["ButtonGrid_Back"] = "Back"; + Values["ButtonGrid_CheckAll"] = "Check all"; + Values["ButtonGrid_UncheckAll"] = "Uncheck all"; Values["CalendarPicker_Title"] = "Pick date"; Values["CalendarPicker_PreviousPage"] = "◀️"; Values["CalendarPicker_NextPage"] = "▶️"; diff --git a/TelegramBotBase/Localizations/German.cs b/TelegramBotBase/Localizations/German.cs index 1672959..5861e72 100644 --- a/TelegramBotBase/Localizations/German.cs +++ b/TelegramBotBase/Localizations/German.cs @@ -8,7 +8,32 @@ namespace TelegramBotBase.Localizations { public German() : base() { - + Values["Language"] = "Deutsch (German)"; + Values["ButtonGrid_Title"] = "Menü"; + Values["ButtonGrid_NoItems"] = "Es sind keine Einträge vorhanden."; + Values["ButtonGrid_PreviousPage"] = "◀️"; + Values["ButtonGrid_NextPage"] = "▶️"; + Values["ButtonGrid_CurrentPage"] = "Seite {0} von {1}"; + Values["ButtonGrid_SearchFeature"] = "💡 Sende eine Nachricht um die Liste zu filtern. Klicke die 🔍 um den Filter zurückzusetzen."; + Values["ButtonGrid_Back"] = "Zurück"; + Values["ButtonGrid_CheckAll"] = "Alle auswählen"; + Values["ButtonGrid_UncheckAll"] = "Keine auswählen"; + Values["CalendarPicker_Title"] = "Datum auswählen"; + Values["CalendarPicker_PreviousPage"] = "◀️"; + Values["CalendarPicker_NextPage"] = "▶️"; + Values["TreeView_Title"] = "Knoten auswählen"; + Values["TreeView_LevelUp"] = "🔼 Stufe hoch"; + Values["ToggleButton_On"] = "An"; + Values["ToggleButton_Off"] = "Aus"; + Values["ToggleButton_OnIcon"] = "⚫"; + Values["ToggleButton_OffIcon"] = "⚪"; + Values["ToggleButton_Title"] = "Schalter"; + Values["ToggleButton_Changed"] = "Ausgewählt"; + Values["MultiToggleButton_SelectedIcon"] = "✅"; + Values["MultiToggleButton_Title"] = "Mehrfach-Schalter"; + Values["MultiToggleButton_Changed"] = "Ausgewählt"; + Values["PromptDialog_Back"] = "Zurück"; + Values["ToggleButton_Changed"] = "Einstellung geändert"; } diff --git a/TelegramBotBase/Localizations/Localization.cs b/TelegramBotBase/Localizations/Localization.cs index 7f60247..338177b 100644 --- a/TelegramBotBase/Localizations/Localization.cs +++ b/TelegramBotBase/Localizations/Localization.cs @@ -18,30 +18,7 @@ namespace TelegramBotBase.Localizations public Localization() { - Values["Language"] = "Deutsch (German)"; - Values["ButtonGrid_Title"] = "Menü"; - Values["ButtonGrid_NoItems"] = "Es sind keine Einträge vorhanden."; - Values["ButtonGrid_PreviousPage"] = "◀️"; - Values["ButtonGrid_NextPage"] = "▶️"; - Values["ButtonGrid_CurrentPage"] = "Seite {0} von {1}"; - Values["ButtonGrid_SearchFeature"] = "💡 Sende eine Nachricht um die Liste zu filtern. Klicke die 🔍 um den Filter zurückzusetzen."; - Values["ButtonGrid_Back"] = "Zurück"; - Values["CalendarPicker_Title"] = "Datum auswählen"; - Values["CalendarPicker_PreviousPage"] = "◀️"; - Values["CalendarPicker_NextPage"] = "▶️"; - Values["TreeView_Title"] = "Knoten auswählen"; - Values["TreeView_LevelUp"] = "🔼 Stufe hoch"; - Values["ToggleButton_On"] = "An"; - Values["ToggleButton_Off"] = "Aus"; - Values["ToggleButton_OnIcon"] = "⚫"; - Values["ToggleButton_OffIcon"] = "⚪"; - Values["ToggleButton_Title"] = "Schalter"; - Values["ToggleButton_Changed"] = "Ausgewählt"; - Values["MultiToggleButton_SelectedIcon"] = "✅"; - Values["MultiToggleButton_Title"] = "Mehrfach-Schalter"; - Values["MultiToggleButton_Changed"] = "Ausgewählt"; - Values["PromptDialog_Back"] = "Zurück"; - Values["ToggleButton_Changed"] = "Einstellung geändert"; + } From 71433c0e4e277ceec29191c1d5f7e0bbd2c1a1d4 Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Mon, 26 Jul 2021 15:10:10 +0200 Subject: [PATCH 19/26] MAJOR CHANGE for ButtonGrids, Dynamic data sources, etc - introducing a dynamic data source class (IDataSource) - introducing a ButtonRow class for better managability - replacing that List with ButtonRow object - introducing ButtonFormDataSource with special methods for ButtonGrid controls - updating ButtonGrid and refactoring of the Load/Action methods - updating CheckButtonList and refactoring of the Load/Action methods - updating TaggedButtonGrid and refactoring of the Load/Action methods - adding example to the Test project --- .../Args/ButtonClickedEventArgs.cs | 9 + .../Args/CheckedChangedEventArgs.cs | 5 +- TelegramBotBase/Controls/Hybrid/ButtonGrid.cs | 260 +++++++++------ TelegramBotBase/Controls/Hybrid/ButtonRow.cs | 112 +++++++ .../Controls/Hybrid/CheckedButtonList.cs | 252 ++++++++++----- .../Controls/Hybrid/TaggedButtonGrid.cs | 295 +++++++++++++----- .../Datasources/ButtonFormDataSource.cs | 142 +++++++++ .../Datasources/StaticDataSource.cs | 46 +++ TelegramBotBase/Form/ButtonForm.cs | 74 ++++- TelegramBotBase/Interfaces/IDataSource.cs | 43 +++ .../Tests/Controls/ButtonGridTagForm.cs | 15 +- .../Tests/Datasources/CustomDataSource.cs | 163 ++++++++++ TelegramBotBaseTest/Tests/Datasources/List.cs | 65 ++++ TelegramBotBaseTest/Tests/Menu.cs | 10 + 14 files changed, 1225 insertions(+), 266 deletions(-) create mode 100644 TelegramBotBase/Controls/Hybrid/ButtonRow.cs create mode 100644 TelegramBotBase/Datasources/ButtonFormDataSource.cs create mode 100644 TelegramBotBase/Datasources/StaticDataSource.cs create mode 100644 TelegramBotBase/Interfaces/IDataSource.cs create mode 100644 TelegramBotBaseTest/Tests/Datasources/CustomDataSource.cs create mode 100644 TelegramBotBaseTest/Tests/Datasources/List.cs diff --git a/TelegramBotBase/Args/ButtonClickedEventArgs.cs b/TelegramBotBase/Args/ButtonClickedEventArgs.cs index a99b9f8..ef9ef71 100644 --- a/TelegramBotBase/Args/ButtonClickedEventArgs.cs +++ b/TelegramBotBase/Args/ButtonClickedEventArgs.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using TelegramBotBase.Controls.Hybrid; using TelegramBotBase.Form; namespace TelegramBotBase.Args @@ -18,6 +19,8 @@ namespace TelegramBotBase.Args public object Tag { get; set; } + public ButtonRow Row { get; set; } + public ButtonClickedEventArgs() { @@ -36,5 +39,11 @@ namespace TelegramBotBase.Args this.Index = Index; } + public ButtonClickedEventArgs(ButtonBase button, int Index, ButtonRow row) + { + this.Button = button; + this.Index = Index; + this.Row = row; + } } } diff --git a/TelegramBotBase/Args/CheckedChangedEventArgs.cs b/TelegramBotBase/Args/CheckedChangedEventArgs.cs index 90d4014..4750334 100644 --- a/TelegramBotBase/Args/CheckedChangedEventArgs.cs +++ b/TelegramBotBase/Args/CheckedChangedEventArgs.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using TelegramBotBase.Controls.Hybrid; using TelegramBotBase.Form; namespace TelegramBotBase.Args @@ -17,7 +18,7 @@ namespace TelegramBotBase.Args /// /// Contains all buttons within this row, excluding the checkbox. /// - public List Row { get; set; } + public ButtonRow Row { get; set; } /// @@ -31,7 +32,7 @@ namespace TelegramBotBase.Args } - public CheckedChangedEventArgs(List row, int Index, bool Checked) + public CheckedChangedEventArgs(ButtonRow row, int Index, bool Checked) { this.Row = row; this.Index = Index; diff --git a/TelegramBotBase/Controls/Hybrid/ButtonGrid.cs b/TelegramBotBase/Controls/Hybrid/ButtonGrid.cs index fb116c7..d8bfdb1 100644 --- a/TelegramBotBase/Controls/Hybrid/ButtonGrid.cs +++ b/TelegramBotBase/Controls/Hybrid/ButtonGrid.cs @@ -10,6 +10,7 @@ using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.ReplyMarkups; using TelegramBotBase.Args; using TelegramBotBase.Base; +using TelegramBotBase.Datasources; using TelegramBotBase.Enums; using TelegramBotBase.Exceptions; using TelegramBotBase.Form; @@ -30,7 +31,26 @@ namespace TelegramBotBase.Controls.Hybrid private readonly EventHandlerList Events = new EventHandlerList(); - public ButtonForm ButtonsForm { get; set; } + /// + /// + /// + [Obsolete("This property is obsolete. Please use the DataSource property instead.")] + public ButtonForm ButtonsForm + { + get + { + return DataSource.ButtonForm; + } + set + { + DataSource = new ButtonFormDataSource(value); + } + } + + /// + /// Data source of the items. + /// + public ButtonFormDataSource DataSource { get; set; } public int? MessageId { get; set; } @@ -89,12 +109,12 @@ namespace TelegramBotBase.Controls.Hybrid /// /// Layout of the buttons which should be displayed always on top. /// - public List HeadLayoutButtonRow { get; set; } + public ButtonRow HeadLayoutButtonRow { get; set; } /// /// Layout of columns which should be displayed below the header /// - public List SubHeadLayoutButtonRow { get; set; } + public ButtonRow SubHeadLayoutButtonRow { get; set; } /// /// Defines which type of Button Keyboard should be rendered. @@ -123,8 +143,7 @@ namespace TelegramBotBase.Controls.Hybrid public ButtonGrid() { - this.ButtonsForm = new ButtonForm(); - + this.DataSource = new ButtonFormDataSource(); } @@ -136,7 +155,7 @@ namespace TelegramBotBase.Controls.Hybrid public ButtonGrid(ButtonForm form) { - this.ButtonsForm = form; + this.DataSource = new ButtonFormDataSource(form); } @@ -188,22 +207,49 @@ namespace TelegramBotBase.Controls.Hybrid if (!result.IsFirstHandler) return; - if (result.MessageText == null) + if (result.MessageText == null || result.MessageText == "") return; - var button = HeadLayoutButtonRow?.FirstOrDefault(a => a.Text.Trim() == result.MessageText) - ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Text.Trim() == result.MessageText) - ?? ButtonsForm.ToList().FirstOrDefault(a => a.Text.Trim() == result.MessageText); + var matches = new List(); + ButtonRow match = null; + int index = -1; - var index = ButtonsForm.FindRowByButton(button); - - if (button != null) + if (HeadLayoutButtonRow?.Matches(result.MessageText) ?? false) { - await OnButtonClicked(new ButtonClickedEventArgs(button, index)); + match = HeadLayoutButtonRow; + goto check; + } - //Remove button click message - if (this.DeleteReplyMessage) - await Device.DeleteMessage(result.MessageId); + if (SubHeadLayoutButtonRow?.Matches(result.MessageText) ?? false) + { + match = SubHeadLayoutButtonRow; + goto check; + } + + var br = DataSource.FindRow(result.MessageText); + if (br != null) + { + match = br.Item1; + index = br.Item2; + } + + + //var button = HeadLayoutButtonRow?. .FirstOrDefault(a => a.Text.Trim() == result.MessageText) + // ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Text.Trim() == result.MessageText); + + // bf.ToList().FirstOrDefault(a => a.Text.Trim() == result.MessageText) + + //var index = bf.FindRowByButton(button); + + check: + + //Remove button click message + if (this.DeleteReplyMessage) + await Device.DeleteMessage(result.MessageId); + + if (match != null) + { + await OnButtonClicked(new ButtonClickedEventArgs(match.GetButtonMatch(result.MessageText), index, match)); result.Handled = true; return; @@ -261,22 +307,48 @@ namespace TelegramBotBase.Controls.Hybrid if (!result.IsFirstHandler) return; - await result.ConfirmAction(this.ConfirmationText ?? ""); - //Find clicked button depending on Text or Value (depending on markup type) if (this.KeyboardType != eKeyboardType.InlineKeyBoard) return; + await result.ConfirmAction(this.ConfirmationText ?? ""); - var button = HeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData) - ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData) - ?? ButtonsForm.ToList().FirstOrDefault(a => a.Value == result.RawData); + ButtonRow match = null; + int index = -1; - var index = ButtonsForm.FindRowByButton(button); - - if (button != null) + if (HeadLayoutButtonRow?.Matches(result.RawData, false) ?? false) { - await OnButtonClicked(new ButtonClickedEventArgs(button, index)); + match = HeadLayoutButtonRow; + goto check; + } + + if (SubHeadLayoutButtonRow?.Matches(result.RawData, false) ?? false) + { + match = SubHeadLayoutButtonRow; + goto check; + } + + var br = DataSource.FindRow(result.RawData, false); + if (br != null) + { + match = br.Item1; + index = br.Item2; + } + + + + //var bf = DataSource.ButtonForm; + + //var button = HeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData) + // ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData) + // ?? bf.ToList().FirstOrDefault(a => a.Value == result.RawData); + + //var index = bf.FindRowByButton(button); + + check: + if (match != null) + { + await OnButtonClicked(new ButtonClickedEventArgs(match.GetButtonMatch(result.RawData, false), index, match)); result.Handled = true; return; @@ -313,28 +385,28 @@ namespace TelegramBotBase.Controls.Hybrid { case eKeyboardType.InlineKeyBoard: - if (ButtonsForm.Rows > Constants.Telegram.MaxInlineKeyBoardRows && !this.EnablePaging) + if (DataSource.RowCount > Constants.Telegram.MaxInlineKeyBoardRows && !this.EnablePaging) { - throw new MaximumRowsReachedException() { Value = ButtonsForm.Rows, Maximum = Constants.Telegram.MaxInlineKeyBoardRows }; + throw new MaximumRowsReachedException() { Value = DataSource.RowCount, Maximum = Constants.Telegram.MaxInlineKeyBoardRows }; } - if (ButtonsForm.Cols > Constants.Telegram.MaxInlineKeyBoardCols) + if (DataSource.ColumnCount > Constants.Telegram.MaxInlineKeyBoardCols) { - throw new MaximumColsException() { Value = ButtonsForm.Rows, Maximum = Constants.Telegram.MaxInlineKeyBoardCols }; + throw new MaximumColsException() { Value = DataSource.ColumnCount, Maximum = Constants.Telegram.MaxInlineKeyBoardCols }; } break; case eKeyboardType.ReplyKeyboard: - if (ButtonsForm.Rows > Constants.Telegram.MaxReplyKeyboardRows && !this.EnablePaging) + if (DataSource.RowCount > Constants.Telegram.MaxReplyKeyboardRows && !this.EnablePaging) { - throw new MaximumRowsReachedException() { Value = ButtonsForm.Rows, Maximum = Constants.Telegram.MaxReplyKeyboardRows }; + throw new MaximumRowsReachedException() { Value = DataSource.RowCount, Maximum = Constants.Telegram.MaxReplyKeyboardRows }; } - if (ButtonsForm.Cols > Constants.Telegram.MaxReplyKeyboardCols) + if (DataSource.ColumnCount > Constants.Telegram.MaxReplyKeyboardCols) { - throw new MaximumColsException() { Value = ButtonsForm.Rows, Maximum = Constants.Telegram.MaxReplyKeyboardCols }; + throw new MaximumColsException() { Value = DataSource.ColumnCount, Maximum = Constants.Telegram.MaxReplyKeyboardCols }; } break; @@ -351,22 +423,21 @@ namespace TelegramBotBase.Controls.Hybrid this.RenderNecessary = false; - Message m = null; + ButtonForm form = this.DataSource.PickItems(CurrentPageIndex * ItemRowsPerPage, ItemRowsPerPage, (this.EnableSearch ? this.SearchQuery : null)); - ButtonForm form = this.ButtonsForm; - if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "") - { - form = form.FilterDuplicate(this.SearchQuery, true); - } - else - { - form = form.Duplicate(); - } + //if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "") + //{ + // form = form.FilterDuplicate(this.SearchQuery, true); + //} + //else + //{ + // form = form.Duplicate(); + //} if (this.EnablePaging) { - form = GeneratePagingView(form); + IntegratePagingView(form); } if (this.HeadLayoutButtonRow != null && HeadLayoutButtonRow.Count > 0) @@ -386,26 +457,37 @@ namespace TelegramBotBase.Controls.Hybrid } } + Message m = null; + switch (this.KeyboardType) { //Reply Keyboard could only be updated with a new keyboard. case eKeyboardType.ReplyKeyboard: - if (this.MessageId != null) + + if (form.Count == 0) { - if (form.Count == 0) + if (this.MessageId != null) { await this.Device.HideReplyKeyboard(); this.MessageId = null; - return; } - if (this.DeletePreviousMessage) - await this.Device.DeleteMessage(this.MessageId.Value); + return; } - if (form.Count == 0) - return; + //if (this.MessageId != null) + //{ + // if (form.Count == 0) + // { + // await this.Device.HideReplyKeyboard(); + // this.MessageId = null; + // return; + // } + //} + + //if (form.Count == 0) + // return; var rkm = (ReplyKeyboardMarkup)form; @@ -413,6 +495,10 @@ namespace TelegramBotBase.Controls.Hybrid rkm.OneTimeKeyboard = this.OneTimeKeyboard; m = await this.Device.Send(this.Title, rkm, disableNotification: true, parseMode: MessageParseMode, MarkdownV2AutoEscape: false); + //Prevent flicker of keyboard + if (this.DeletePreviousMessage && this.MessageId != null) + await this.Device.DeleteMessage(this.MessageId.Value); + break; case eKeyboardType.InlineKeyBoard: @@ -443,47 +529,31 @@ namespace TelegramBotBase.Controls.Hybrid } - private ButtonForm GeneratePagingView(ButtonForm dataForm) + private void IntegratePagingView(ButtonForm dataForm) { - - ButtonForm bf = new ButtonForm(); - - - for (int i = 0; i < this.MaximumRow - LayoutRows; i++) - { - int it = (this.CurrentPageIndex * (this.MaximumRow - LayoutRows)) + i; - - if (it > dataForm.Rows - 1) - break; - - bf.AddButtonRow(dataForm[it]); - } - //No Items - if (this.ButtonsForm.Count == 0) + if (dataForm.Rows == 0) { - bf.AddButtonRow(new ButtonBase(NoItemsLabel, "$")); + dataForm.AddButtonRow(new ButtonBase(NoItemsLabel, "$")); } if (this.IsNavigationBarVisible) { //🔍 - List lst = new List(); - lst.Add(new ButtonBase(PreviousPageLabel, "$previous$")); - lst.Add(new ButtonBase(String.Format(Localizations.Default.Language["ButtonGrid_CurrentPage"], this.CurrentPageIndex + 1, this.PageCount), "$site$")); - lst.Add(new ButtonBase(NextPageLabel, "$next$")); + ButtonRow row = new ButtonRow(); + row.Add(new ButtonBase(PreviousPageLabel, "$previous$")); + row.Add(new ButtonBase(String.Format(Localizations.Default.Language["ButtonGrid_CurrentPage"], this.CurrentPageIndex + 1, this.PageCount), "$site$")); + row.Add(new ButtonBase(NextPageLabel, "$next$")); if (this.EnableSearch) { - lst.Insert(2, new ButtonBase("🔍 " + (this.SearchQuery ?? ""), "$search$")); + row.Insert(2, new ButtonBase("🔍 " + (this.SearchQuery ?? ""), "$search$")); } - bf.InsertButtonRow(0, lst); + dataForm.InsertButtonRow(0, row); - bf.AddButtonRow(lst); + dataForm.AddButtonRow(row); } - - return bf; } public bool PagingNecessary @@ -545,7 +615,7 @@ namespace TelegramBotBase.Controls.Hybrid { get { - return this.LayoutRows + ButtonsForm.Rows; + return this.LayoutRows + DataSource.RowCount; } } @@ -572,24 +642,40 @@ namespace TelegramBotBase.Controls.Hybrid } } + /// + /// Returns the number of item rows per page. + /// + public int ItemRowsPerPage + { + get + { + return this.MaximumRow - this.LayoutRows; + } + } + + /// + /// Return the number of pages. + /// public int PageCount { get { - if (this.ButtonsForm.Count == 0) + if (DataSource.RowCount == 0) return 1; - var bf = this.ButtonsForm; + //var bf = this.DataSource.PickAllItems(this.EnableSearch ? this.SearchQuery : null); - if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "") - { - bf = bf.FilterDuplicate(this.SearchQuery); - } + var max = this.DataSource.CalculateMax(this.EnableSearch ? this.SearchQuery : null); - if (bf.Rows == 0) + //if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "") + //{ + // bf = bf.FilterDuplicate(this.SearchQuery); + //} + + if (max == 0) return 1; - return (int)Math.Ceiling((decimal)(bf.Rows / (decimal)(MaximumRow - this.LayoutRows))); + return (int)Math.Ceiling((decimal)((decimal)max / (decimal)ItemRowsPerPage)); } } diff --git a/TelegramBotBase/Controls/Hybrid/ButtonRow.cs b/TelegramBotBase/Controls/Hybrid/ButtonRow.cs new file mode 100644 index 0000000..2d0deab --- /dev/null +++ b/TelegramBotBase/Controls/Hybrid/ButtonRow.cs @@ -0,0 +1,112 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using TelegramBotBase.Form; + +namespace TelegramBotBase.Controls.Hybrid +{ + [DebuggerDisplay("{Count} columns")] + public class ButtonRow + { + + List __buttons = new List(); + + public ButtonRow() + { + + + } + + public ButtonRow(params ButtonBase[] buttons) + { + __buttons = buttons.ToList(); + } + + + public ButtonBase this[int index] + { + get + { + return __buttons[index]; + } + } + + public int Count + { + get + { + return __buttons.Count; + } + } + + public void Add(ButtonBase button) + { + __buttons.Add(button); + } + + public void AddRange(ButtonBase button) + { + __buttons.Add(button); + } + + public void Insert(int index, ButtonBase button) + { + __buttons.Insert(index, button); + } + + public IEnumerator GetEnumerator() + { + return __buttons.GetEnumerator(); + } + + public ButtonBase[] ToArray() + { + return __buttons.ToArray(); + } + + public List ToList() + { + return __buttons.ToList(); + } + + public bool Matches(String text, bool useText = true) + { + foreach (var b in __buttons) + { + if (useText && b.Text.Trim().Equals(text, StringComparison.InvariantCultureIgnoreCase)) + return true; + + if (!useText && b.Value.Equals(text, StringComparison.InvariantCultureIgnoreCase)) + return true; + } + return false; + } + + /// + /// Returns the button inside of the row which matches. + /// + /// + /// + /// + public ButtonBase GetButtonMatch(String text, bool useText = true) + { + foreach (var b in __buttons) + { + if (useText && b.Text.Trim().Equals(text, StringComparison.InvariantCultureIgnoreCase)) + return b; + if (!useText && b.Value.Equals(text, StringComparison.InvariantCultureIgnoreCase)) + return b; + } + return null; + } + + public static implicit operator ButtonRow(List list) + { + return new ButtonRow() { __buttons = list }; + } + } +} diff --git a/TelegramBotBase/Controls/Hybrid/CheckedButtonList.cs b/TelegramBotBase/Controls/Hybrid/CheckedButtonList.cs index fe3b8c9..9c2e414 100644 --- a/TelegramBotBase/Controls/Hybrid/CheckedButtonList.cs +++ b/TelegramBotBase/Controls/Hybrid/CheckedButtonList.cs @@ -10,6 +10,7 @@ using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.ReplyMarkups; using TelegramBotBase.Args; using TelegramBotBase.Base; +using TelegramBotBase.Datasources; using TelegramBotBase.Enums; using TelegramBotBase.Exceptions; using TelegramBotBase.Form; @@ -32,7 +33,23 @@ namespace TelegramBotBase.Controls.Hybrid private readonly EventHandlerList Events = new EventHandlerList(); - public ButtonForm ButtonsForm { get; set; } + [Obsolete("This property is obsolete. Please use the DataSource property instead.")] + public ButtonForm ButtonsForm + { + get + { + return DataSource.ButtonForm; + } + set + { + DataSource = new ButtonFormDataSource(value); + } + } + + /// + /// Data source of the items. + /// + public ButtonFormDataSource DataSource { get; set; } List CheckedRows { get; set; } = new List(); @@ -51,6 +68,11 @@ namespace TelegramBotBase.Controls.Hybrid public bool DeletePreviousMessage { get; set; } = true; + /// + /// Removes the reply message from a user. + /// + public bool DeleteReplyMessage { get; set; } = true; + /// /// Parsemode of the message. /// @@ -92,12 +114,12 @@ namespace TelegramBotBase.Controls.Hybrid /// /// Layout of the buttons which should be displayed always on top. /// - public List HeadLayoutButtonRow { get; set; } + public ButtonRow HeadLayoutButtonRow { get; set; } /// /// Layout of columns which should be displayed below the header /// - public List SubHeadLayoutButtonRow { get; set; } + public ButtonRow SubHeadLayoutButtonRow { get; set; } /// /// Defines which type of Button Keyboard should be rendered. @@ -126,7 +148,7 @@ namespace TelegramBotBase.Controls.Hybrid public CheckedButtonList() { - this.ButtonsForm = new ButtonForm(); + this.DataSource = new ButtonFormDataSource(); } @@ -139,7 +161,7 @@ namespace TelegramBotBase.Controls.Hybrid public CheckedButtonList(ButtonForm form) { - this.ButtonsForm = form; + this.DataSource = new ButtonFormDataSource(form); } public event AsyncEventHandler ButtonClicked @@ -198,16 +220,50 @@ namespace TelegramBotBase.Controls.Hybrid if (!result.IsFirstHandler) return; - if (result.MessageText == null) + if (result.MessageText == null || result.MessageText == "") return; - var button = HeadLayoutButtonRow?.FirstOrDefault(a => a.Text.Trim() == result.MessageText) - ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Text.Trim() == result.MessageText) - ?? ButtonsForm.ToList().FirstOrDefault(a => a.Text.Trim() == result.MessageText); + var matches = new List(); + ButtonRow match = null; + int index = -1; - var index = ButtonsForm.FindRowByButton(button); + if (HeadLayoutButtonRow?.Matches(result.MessageText) ?? false) + { + match = HeadLayoutButtonRow; + goto check; + } - if (button == null) + if (SubHeadLayoutButtonRow?.Matches(result.MessageText) ?? false) + { + match = SubHeadLayoutButtonRow; + goto check; + } + + var br = DataSource.FindRow(result.MessageText); + if (br != null) + { + match = br.Item1; + index = br.Item2; + } + + + //var button = HeadLayoutButtonRow?. .FirstOrDefault(a => a.Text.Trim() == result.MessageText) + // ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Text.Trim() == result.MessageText); + + // bf.ToList().FirstOrDefault(a => a.Text.Trim() == result.MessageText) + + //var index = bf.FindRowByButton(button); + + + + check: + + + //Remove button click message + if (this.DeleteReplyMessage) + await Device.DeleteMessage(result.MessageId); + + if (match == null) { if (result.MessageText == PreviousPageLabel) { @@ -226,7 +282,7 @@ namespace TelegramBotBase.Controls.Hybrid else if (result.MessageText.EndsWith(CheckedIconLabel)) { var s = result.MessageText.Split(' ', '.'); - index = int.Parse(s[0]); + index = int.Parse(s[0]) - 1; if (!this.CheckedRows.Contains(index)) return; @@ -241,7 +297,7 @@ namespace TelegramBotBase.Controls.Hybrid else if (result.MessageText.EndsWith(UncheckedIconLabel)) { var s = result.MessageText.Split(' ', '.'); - index = int.Parse(s[0]); + index = int.Parse(s[0]) - 1; if (this.CheckedRows.Contains(index)) return; @@ -285,14 +341,18 @@ namespace TelegramBotBase.Controls.Hybrid } - await OnButtonClicked(new ButtonClickedEventArgs(button, index)); - - //Remove button click message - if (this.DeletePreviousMessage) - await Device.DeleteMessage(result.MessageId); + await OnButtonClicked(new ButtonClickedEventArgs(match.GetButtonMatch(result.MessageText), index, match)); result.Handled = true; + //await OnButtonClicked(new ButtonClickedEventArgs(button, index)); + + ////Remove button click message + //if (this.DeletePreviousMessage) + // await Device.DeleteMessage(result.MessageId); + + //result.Handled = true; + } public async override Task Action(MessageResult result, string value = null) @@ -309,20 +369,55 @@ namespace TelegramBotBase.Controls.Hybrid await result.ConfirmAction(this.ConfirmationText ?? ""); - var button = HeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData) - ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData) - ?? ButtonsForm.ToList().FirstOrDefault(a => a.Value == result.RawData); + ButtonRow match = null; + int index = -1; - var index = ButtonsForm.FindRowByButton(button); - - if (button != null) + if (HeadLayoutButtonRow?.Matches(result.RawData, false) ?? false) { - await OnButtonClicked(new ButtonClickedEventArgs(button, index)); + match = HeadLayoutButtonRow; + goto check; + } + + if (SubHeadLayoutButtonRow?.Matches(result.RawData, false) ?? false) + { + match = SubHeadLayoutButtonRow; + goto check; + } + + var br = DataSource.FindRow(result.RawData, false); + if (br != null) + { + match = br.Item1; + index = br.Item2; + } + + + + //var bf = DataSource.ButtonForm; + + //var button = HeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData) + // ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData) + // ?? bf.ToList().FirstOrDefault(a => a.Value == result.RawData); + + //var index = bf.FindRowByButton(button); + + check: + if (match != null) + { + await OnButtonClicked(new ButtonClickedEventArgs(match.GetButtonMatch(result.RawData, false), index, match)); result.Handled = true; return; } + //if (button != null) + //{ + // await OnButtonClicked(new ButtonClickedEventArgs(button, index)); + + // result.Handled = true; + // return; + //} + switch (result.RawData) { case "$previous$": @@ -396,28 +491,28 @@ namespace TelegramBotBase.Controls.Hybrid { case eKeyboardType.InlineKeyBoard: - if (ButtonsForm.Rows > Constants.Telegram.MaxInlineKeyBoardRows && !this.EnablePaging) + if (DataSource.RowCount > Constants.Telegram.MaxInlineKeyBoardRows && !this.EnablePaging) { - throw new MaximumRowsReachedException() { Value = ButtonsForm.Rows, Maximum = Constants.Telegram.MaxInlineKeyBoardRows }; + throw new MaximumRowsReachedException() { Value = DataSource.RowCount, Maximum = Constants.Telegram.MaxInlineKeyBoardRows }; } - if (ButtonsForm.Cols > Constants.Telegram.MaxInlineKeyBoardCols) + if (DataSource.ColumnCount > Constants.Telegram.MaxInlineKeyBoardCols) { - throw new MaximumColsException() { Value = ButtonsForm.Rows, Maximum = Constants.Telegram.MaxInlineKeyBoardCols }; + throw new MaximumColsException() { Value = DataSource.ColumnCount, Maximum = Constants.Telegram.MaxInlineKeyBoardCols }; } break; case eKeyboardType.ReplyKeyboard: - if (ButtonsForm.Rows > Constants.Telegram.MaxReplyKeyboardRows && !this.EnablePaging) + if (DataSource.RowCount > Constants.Telegram.MaxReplyKeyboardRows && !this.EnablePaging) { - throw new MaximumRowsReachedException() { Value = ButtonsForm.Rows, Maximum = Constants.Telegram.MaxReplyKeyboardRows }; + throw new MaximumRowsReachedException() { Value = DataSource.RowCount, Maximum = Constants.Telegram.MaxReplyKeyboardRows }; } - if (ButtonsForm.Cols > Constants.Telegram.MaxReplyKeyboardCols) + if (DataSource.ColumnCount > Constants.Telegram.MaxReplyKeyboardCols) { - throw new MaximumColsException() { Value = ButtonsForm.Rows, Maximum = Constants.Telegram.MaxReplyKeyboardCols }; + throw new MaximumColsException() { Value = DataSource.ColumnCount, Maximum = Constants.Telegram.MaxReplyKeyboardCols }; } break; @@ -436,7 +531,7 @@ namespace TelegramBotBase.Controls.Hybrid Message m = null; - ButtonForm form = this.ButtonsForm; + ButtonForm form = this.DataSource.PickItems(CurrentPageIndex * ItemRowsPerPage, ItemRowsPerPage, null); //if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "") //{ @@ -444,12 +539,12 @@ namespace TelegramBotBase.Controls.Hybrid //} //else //{ - form = form.Duplicate(); + //form = form.Duplicate(); //} if (this.EnablePaging) { - form = GeneratePagingView(form); + IntegratePagingView(form); } else { @@ -458,18 +553,18 @@ namespace TelegramBotBase.Controls.Hybrid if (this.HeadLayoutButtonRow != null && HeadLayoutButtonRow.Count > 0) { - form.InsertButtonRow(0, this.HeadLayoutButtonRow); + form.InsertButtonRow(0, this.HeadLayoutButtonRow.ToArray()); } if (this.SubHeadLayoutButtonRow != null && SubHeadLayoutButtonRow.Count > 0) { if (this.IsNavigationBarVisible) { - form.InsertButtonRow(2, this.SubHeadLayoutButtonRow); + form.InsertButtonRow(2, this.SubHeadLayoutButtonRow.ToArray()); } else { - form.InsertButtonRow(1, this.SubHeadLayoutButtonRow); + form.InsertButtonRow(1, this.SubHeadLayoutButtonRow.ToArray()); } } @@ -478,21 +573,19 @@ namespace TelegramBotBase.Controls.Hybrid //Reply Keyboard could only be updated with a new keyboard. case eKeyboardType.ReplyKeyboard: - if (this.MessageId != null) + if (form.Count == 0) { - if (form.Count == 0) + if (this.MessageId != null) { await this.Device.HideReplyKeyboard(); this.MessageId = null; - return; } - if (this.DeletePreviousMessage) - await this.Device.DeleteMessage(this.MessageId.Value); + return; } - if (form.Count == 0) - return; + //if (form.Count == 0) + // return; var rkm = (ReplyKeyboardMarkup)form; @@ -500,6 +593,10 @@ namespace TelegramBotBase.Controls.Hybrid rkm.OneTimeKeyboard = this.OneTimeKeyboard; m = await this.Device.Send(this.Title, rkm, disableNotification: true, parseMode: MessageParseMode, MarkdownV2AutoEscape: false); + //Prevent flicker of keyboard + if (this.DeletePreviousMessage && this.MessageId != null) + await this.Device.DeleteMessage(this.MessageId.Value); + break; case eKeyboardType.InlineKeyBoard: @@ -524,58 +621,52 @@ namespace TelegramBotBase.Controls.Hybrid } - private ButtonForm GeneratePagingView(ButtonForm dataForm) + private void IntegratePagingView(ButtonForm dataForm) { + //No Items + if (dataForm.Rows == 0) + { + dataForm.AddButtonRow(new ButtonBase(NoItemsLabel, "$")); + } ButtonForm bf = new ButtonForm(); bf = PrepareCheckableLayout(dataForm); - //No Items - if (this.ButtonsForm.Count == 0) - { - bf.AddButtonRow(new ButtonBase(NoItemsLabel, "$")); - } if (this.IsNavigationBarVisible) { //🔍 - List lst = new List(); - lst.Add(new ButtonBase(PreviousPageLabel, "$previous$")); - lst.Add(new ButtonBase(String.Format(Localizations.Default.Language["ButtonGrid_CurrentPage"], this.CurrentPageIndex + 1, this.PageCount), "$site$")); - lst.Add(new ButtonBase(NextPageLabel, "$next$")); + ButtonRow row = new ButtonRow(); + row.Add(new ButtonBase(PreviousPageLabel, "$previous$")); + row.Add(new ButtonBase(String.Format(Localizations.Default.Language["ButtonGrid_CurrentPage"], this.CurrentPageIndex + 1, this.PageCount), "$site$")); + row.Add(new ButtonBase(NextPageLabel, "$next$")); - //if (this.EnableSearch) - //{ - // lst.Insert(2, new ButtonBase("🔍 " + (this.SearchQuery ?? ""), "$search$")); - //} - bf.InsertButtonRow(0, lst); + dataForm.InsertButtonRow(0, row); - bf.AddButtonRow(lst); + dataForm.AddButtonRow(row); } - - return bf; } private ButtonForm PrepareCheckableLayout(ButtonForm dataForm) { var bf = new ButtonForm(); - for (int i = 0; i < this.MaximumRow - LayoutRows; i++) + for (int i = 0; i < dataForm.Rows; i++) { int it = (this.CurrentPageIndex * (this.MaximumRow - LayoutRows)) + i; - if (it > dataForm.Rows - 1) - break; + //if (it > dataForm.Rows - 1) + // break; - var r = dataForm[it]; + var r = dataForm[i]; String s = CheckedRows.Contains(it) ? this.CheckedIconLabel : this.UncheckedIconLabel; //On reply keyboards we need a unique text. if (this.KeyboardType == eKeyboardType.ReplyKeyboard) { - s = $"{it}. " + s; + s = $"{it + 1}. " + s; } if (CheckedRows.Contains(it)) @@ -652,7 +743,7 @@ namespace TelegramBotBase.Controls.Hybrid { get { - return this.LayoutRows + ButtonsForm.Rows; + return this.LayoutRows + DataSource.RowCount; } } @@ -679,24 +770,37 @@ namespace TelegramBotBase.Controls.Hybrid } } + /// + /// Returns the number of item rows per page. + /// + public int ItemRowsPerPage + { + get + { + return this.MaximumRow - this.LayoutRows; + } + } + public int PageCount { get { - if (this.ButtonsForm.Count == 0) + if (DataSource.RowCount == 0) return 1; - var bf = this.ButtonsForm; + //var bf = this.DataSource.PickAllItems(this.EnableSearch ? this.SearchQuery : null); + + var max = this.DataSource.RowCount; //if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "") //{ // bf = bf.FilterDuplicate(this.SearchQuery); //} - if (bf.Rows == 0) + if (max == 0) return 1; - return (int)Math.Ceiling((decimal)(bf.Rows / (decimal)(MaximumRow - this.LayoutRows))); + return (int)Math.Ceiling((decimal)((decimal)max / (decimal)ItemRowsPerPage)); } } diff --git a/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs b/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs index d97f920..55ed12e 100644 --- a/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs +++ b/TelegramBotBase/Controls/Hybrid/TaggedButtonGrid.cs @@ -7,9 +7,11 @@ using System.Text; using System.Threading.Tasks; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; +using Telegram.Bot.Types.InlineQueryResults; using Telegram.Bot.Types.ReplyMarkups; using TelegramBotBase.Args; using TelegramBotBase.Base; +using TelegramBotBase.Datasources; using TelegramBotBase.Enums; using TelegramBotBase.Exceptions; using TelegramBotBase.Form; @@ -30,7 +32,23 @@ namespace TelegramBotBase.Controls.Hybrid private readonly EventHandlerList Events = new EventHandlerList(); - public ButtonForm ButtonsForm { get; set; } + [Obsolete("This property is obsolete. Please use the DataSource property instead.")] + public ButtonForm ButtonsForm + { + get + { + return DataSource.ButtonForm; + } + set + { + DataSource = new ButtonFormDataSource(value); + } + } + + /// + /// Data source of the items. + /// + public ButtonFormDataSource DataSource { get; set; } public int? MessageId { get; set; } @@ -62,6 +80,11 @@ namespace TelegramBotBase.Controls.Hybrid /// public bool EnablePaging { get; set; } = false; + /// + /// Shows un-/check all tags options + /// + public bool EnableCheckAllTools { get; set; } = false; + /// /// Enabled a search function. /// @@ -87,15 +110,24 @@ namespace TelegramBotBase.Controls.Hybrid public String BackLabel = Localizations.Default.Language["ButtonGrid_Back"]; + public String CheckAllLabel = Localizations.Default.Language["ButtonGrid_CheckAll"]; + + public String UncheckAllLabel = Localizations.Default.Language["ButtonGrid_UncheckAll"]; + /// /// Layout of the buttons which should be displayed always on top. /// - public List HeadLayoutButtonRow { get; set; } + public ButtonRow HeadLayoutButtonRow { get; set; } /// /// Layout of columns which should be displayed below the header /// - public List SubHeadLayoutButtonRow { get; set; } + public ButtonRow SubHeadLayoutButtonRow { get; set; } + + /// + /// Layout of columns which should be displayed below the header + /// + private ButtonRow TagsSubHeadLayoutButtonRow { get; set; } /// /// List of Tags which will be allowed to filter by. @@ -134,7 +166,7 @@ namespace TelegramBotBase.Controls.Hybrid public TaggedButtonGrid() { - this.ButtonsForm = new ButtonForm(); + this.DataSource = new ButtonFormDataSource(); this.SelectedViewIndex = 0; } @@ -147,7 +179,7 @@ namespace TelegramBotBase.Controls.Hybrid public TaggedButtonGrid(ButtonForm form) { - this.ButtonsForm = form; + this.DataSource = new ButtonFormDataSource(form); } @@ -202,27 +234,60 @@ namespace TelegramBotBase.Controls.Hybrid if (result.MessageText == null || result.MessageText == "") return; - var button = HeadLayoutButtonRow?.FirstOrDefault(a => a.Text.Trim() == result.MessageText) - ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Text.Trim() == result.MessageText) - ?? ButtonsForm.ToList().FirstOrDefault(a => a.Text.Trim() == result.MessageText); + var matches = new List(); + ButtonRow match = null; + int index = -1; + + if (HeadLayoutButtonRow?.Matches(result.MessageText) ?? false) + { + match = HeadLayoutButtonRow; + goto check; + } + + if (SubHeadLayoutButtonRow?.Matches(result.MessageText) ?? false) + { + match = SubHeadLayoutButtonRow; + goto check; + } + + if (TagsSubHeadLayoutButtonRow?.Matches(result.MessageText) ?? false) + { + match = TagsSubHeadLayoutButtonRow; + goto check; + } + + var br = DataSource.FindRow(result.MessageText); + if (br != null) + { + match = br.Item1; + index = br.Item2; + } + + + //var button = HeadLayoutButtonRow?. .FirstOrDefault(a => a.Text.Trim() == result.MessageText) + // ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Text.Trim() == result.MessageText); + + // bf.ToList().FirstOrDefault(a => a.Text.Trim() == result.MessageText) + + //var index = bf.FindRowByButton(button); + + check: - var index = ButtonsForm.FindRowByButton(button); switch (this.SelectedViewIndex) { case 0: - if (button != null) - { - await OnButtonClicked(new ButtonClickedEventArgs(button, index)); + //Remove button click message + if (this.DeleteReplyMessage) + await Device.DeleteMessage(result.MessageId); - //Remove button click message - if (this.DeleteReplyMessage) - await Device.DeleteMessage(result.MessageId); + if (match != null) + { + await OnButtonClicked(new ButtonClickedEventArgs(match.GetButtonMatch(result.MessageText), index, match)); result.Handled = true; - return; } @@ -287,17 +352,25 @@ namespace TelegramBotBase.Controls.Hybrid break; case 1: + //Remove button click message + if (this.DeleteReplyMessage) + await Device.DeleteMessage(result.MessageId); + if (result.MessageText == this.BackLabel) { - //Remove button click message - if (this.DeletePreviousMessage) - await Device.DeleteMessage(result.MessageId); - this.SelectedViewIndex = 0; this.Updated(); result.Handled = true; return; } + else if (result.MessageText == this.CheckAllLabel) + { + this.CheckAllTags(); + } + else if (result.MessageText == this.UncheckAllLabel) + { + this.UncheckAllTags(); + } var i = result.MessageText.LastIndexOf(" "); if (i == -1) @@ -340,15 +413,48 @@ namespace TelegramBotBase.Controls.Hybrid await result.ConfirmAction(this.ConfirmationText ?? ""); - var button = HeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData) - ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData) - ?? ButtonsForm.ToList().FirstOrDefault(a => a.Value == result.RawData); + ButtonRow match = null; + int index = -1; - var index = ButtonsForm.FindRowByButton(button); - - if (button != null) + if (HeadLayoutButtonRow?.Matches(result.RawData, false) ?? false) { - await OnButtonClicked(new ButtonClickedEventArgs(button, index)); + match = HeadLayoutButtonRow; + goto check; + } + + if (SubHeadLayoutButtonRow?.Matches(result.RawData, false) ?? false) + { + match = SubHeadLayoutButtonRow; + goto check; + } + + if (TagsSubHeadLayoutButtonRow?.Matches(result.RawData) ?? false) + { + match = TagsSubHeadLayoutButtonRow; + goto check; + } + + var br = DataSource.FindRow(result.RawData, false); + if (br != null) + { + match = br.Item1; + index = br.Item2; + } + + + + //var bf = DataSource.ButtonForm; + + //var button = HeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData) + // ?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData) + // ?? bf.ToList().FirstOrDefault(a => a.Value == result.RawData); + + //var index = bf.FindRowByButton(button); + + check: + if (match != null) + { + await OnButtonClicked(new ButtonClickedEventArgs(match.GetButtonMatch(result.RawData, false), index, match)); result.Handled = true; return; @@ -386,6 +492,18 @@ namespace TelegramBotBase.Controls.Hybrid this.SelectedViewIndex = 0; this.Updated(); + break; + + case "$checkall$": + + this.CheckAllTags(); + + break; + + case "$uncheckall$": + + this.UncheckAllTags(); + break; } @@ -400,28 +518,28 @@ namespace TelegramBotBase.Controls.Hybrid { case eKeyboardType.InlineKeyBoard: - if (ButtonsForm.Rows > Constants.Telegram.MaxInlineKeyBoardRows && !this.EnablePaging) + if (DataSource.RowCount > Constants.Telegram.MaxInlineKeyBoardRows && !this.EnablePaging) { - throw new MaximumRowsReachedException() { Value = ButtonsForm.Rows, Maximum = Constants.Telegram.MaxInlineKeyBoardRows }; + throw new MaximumRowsReachedException() { Value = DataSource.RowCount, Maximum = Constants.Telegram.MaxInlineKeyBoardRows }; } - if (ButtonsForm.Cols > Constants.Telegram.MaxInlineKeyBoardCols) + if (DataSource.ColumnCount > Constants.Telegram.MaxInlineKeyBoardCols) { - throw new MaximumColsException() { Value = ButtonsForm.Rows, Maximum = Constants.Telegram.MaxInlineKeyBoardCols }; + throw new MaximumColsException() { Value = DataSource.ColumnCount, Maximum = Constants.Telegram.MaxInlineKeyBoardCols }; } break; case eKeyboardType.ReplyKeyboard: - if (ButtonsForm.Rows > Constants.Telegram.MaxReplyKeyboardRows && !this.EnablePaging) + if (DataSource.RowCount > Constants.Telegram.MaxReplyKeyboardRows && !this.EnablePaging) { - throw new MaximumRowsReachedException() { Value = ButtonsForm.Rows, Maximum = Constants.Telegram.MaxReplyKeyboardRows }; + throw new MaximumRowsReachedException() { Value = DataSource.RowCount, Maximum = Constants.Telegram.MaxReplyKeyboardRows }; } - if (ButtonsForm.Cols > Constants.Telegram.MaxReplyKeyboardCols) + if (DataSource.ColumnCount > Constants.Telegram.MaxReplyKeyboardCols) { - throw new MaximumColsException() { Value = ButtonsForm.Rows, Maximum = Constants.Telegram.MaxReplyKeyboardCols }; + throw new MaximumColsException() { Value = DataSource.ColumnCount, Maximum = Constants.Telegram.MaxReplyKeyboardCols }; } break; @@ -468,16 +586,16 @@ namespace TelegramBotBase.Controls.Hybrid { Message m = null; - ButtonForm form = this.ButtonsForm; + ButtonForm form = this.DataSource.PickItems(CurrentPageIndex * ItemRowsPerPage, ItemRowsPerPage, (this.EnableSearch ? this.SearchQuery : null)); - if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "") - { - form = form.FilterDuplicate(this.SearchQuery, true); - } - else - { - form = form.Duplicate(); - } + //if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "") + //{ + // form = form.FilterDuplicate(this.SearchQuery, true); + //} + //else + //{ + // form = form.Duplicate(); + //} if (this.Tags != null && this.SelectedTags != null) { @@ -486,23 +604,23 @@ namespace TelegramBotBase.Controls.Hybrid if (this.EnablePaging) { - form = GeneratePagingView(form); + IntegratePagingView(form); } if (this.HeadLayoutButtonRow != null && HeadLayoutButtonRow.Count > 0) { - form.InsertButtonRow(0, this.HeadLayoutButtonRow); + form.InsertButtonRow(0, this.HeadLayoutButtonRow.ToArray()); } if (this.SubHeadLayoutButtonRow != null && SubHeadLayoutButtonRow.Count > 0) { if (this.IsNavigationBarVisible) { - form.InsertButtonRow(2, this.SubHeadLayoutButtonRow); + form.InsertButtonRow(2, this.SubHeadLayoutButtonRow.ToArray()); } else { - form.InsertButtonRow(1, this.SubHeadLayoutButtonRow); + form.InsertButtonRow(1, this.SubHeadLayoutButtonRow.ToArray()); } } @@ -559,52 +677,37 @@ namespace TelegramBotBase.Controls.Hybrid } } - private ButtonForm GeneratePagingView(ButtonForm dataForm) + private void IntegratePagingView(ButtonForm dataForm) { - ButtonForm bf = new ButtonForm(); - - - for (int i = 0; i < this.MaximumRow - LayoutRows; i++) - { - int it = (this.CurrentPageIndex * (this.MaximumRow - LayoutRows)) + i; - - if (it > dataForm.Rows - 1) - break; - - bf.AddButtonRow(dataForm[it]); - } - //No Items - if (this.ButtonsForm.Count == 0) + if (dataForm.Rows == 0) { - bf.AddButtonRow(new ButtonBase(NoItemsLabel, "$")); + dataForm.AddButtonRow(new ButtonBase(NoItemsLabel, "$")); } if (this.IsNavigationBarVisible) { //🔍 - List lst = new List(); - lst.Add(new ButtonBase(PreviousPageLabel, "$previous$")); - lst.Add(new ButtonBase(String.Format(Localizations.Default.Language["ButtonGrid_CurrentPage"], this.CurrentPageIndex + 1, this.PageCount), "$site$")); + ButtonRow row = new ButtonRow(); + row.Add(new ButtonBase(PreviousPageLabel, "$previous$")); + row.Add(new ButtonBase(String.Format(Localizations.Default.Language["ButtonGrid_CurrentPage"], this.CurrentPageIndex + 1, this.PageCount), "$site$")); if (this.Tags != null && this.Tags.Count > 0) { - lst.Add(new ButtonBase("📁", "$filter$")); + row.Add(new ButtonBase("📁", "$filter$")); } - lst.Add(new ButtonBase(NextPageLabel, "$next$")); + row.Add(new ButtonBase(NextPageLabel, "$next$")); if (this.EnableSearch) { - lst.Insert(2, new ButtonBase("🔍 " + (this.SearchQuery ?? ""), "$search$")); + row.Insert(2, new ButtonBase("🔍 " + (this.SearchQuery ?? ""), "$search$")); } - bf.InsertButtonRow(0, lst); + dataForm.InsertButtonRow(0, row); - bf.AddButtonRow(lst); + dataForm.AddButtonRow(row); } - - return bf; } #endregion @@ -620,6 +723,12 @@ namespace TelegramBotBase.Controls.Hybrid bf.AddButtonRow(this.BackLabel, "$back$"); + if (EnableCheckAllTools) + { + this.TagsSubHeadLayoutButtonRow = new ButtonRow(new ButtonBase(CheckAllLabel, "$checkall$"), new ButtonBase(UncheckAllLabel, "$uncheckall$")); + bf.AddButtonRow(TagsSubHeadLayoutButtonRow); + } + foreach (var t in this.Tags) { @@ -748,7 +857,7 @@ namespace TelegramBotBase.Controls.Hybrid { get { - return this.LayoutRows + ButtonsForm.Rows; + return this.LayoutRows + DataSource.RowCount; } } @@ -771,28 +880,46 @@ namespace TelegramBotBase.Controls.Hybrid if (this.SubHeadLayoutButtonRow != null && this.SubHeadLayoutButtonRow.Count > 0) layoutRows++; + if (EnableCheckAllTools && this.SelectedViewIndex == 1) + { + layoutRows++; + } + return layoutRows; } } + /// + /// Returns the number of item rows per page. + /// + public int ItemRowsPerPage + { + get + { + return this.MaximumRow - this.LayoutRows; + } + } + public int PageCount { get { - if (this.ButtonsForm.Count == 0) + if (DataSource.RowCount == 0) return 1; - var bf = this.ButtonsForm; + //var bf = this.DataSource.PickAllItems(this.EnableSearch ? this.SearchQuery : null); - if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "") - { - bf = bf.FilterDuplicate(this.SearchQuery); - } + var max = this.DataSource.CalculateMax(this.EnableSearch ? this.SearchQuery : null); - if (bf.Rows == 0) + //if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "") + //{ + // bf = bf.FilterDuplicate(this.SearchQuery); + //} + + if (max == 0) return 1; - return (int)Math.Ceiling((decimal)(bf.Rows / (decimal)(MaximumRow - this.LayoutRows))); + return (int)Math.Ceiling((decimal)((decimal)max / (decimal)ItemRowsPerPage)); } } @@ -844,6 +971,8 @@ namespace TelegramBotBase.Controls.Hybrid break; } + } + /// /// Checks all tags for filtering. diff --git a/TelegramBotBase/Datasources/ButtonFormDataSource.cs b/TelegramBotBase/Datasources/ButtonFormDataSource.cs new file mode 100644 index 0000000..e8fbfd4 --- /dev/null +++ b/TelegramBotBase/Datasources/ButtonFormDataSource.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.Text; +using TelegramBotBase.Controls.Hybrid; +using TelegramBotBase.Form; +using TelegramBotBase.Interfaces; + +namespace TelegramBotBase.Datasources +{ + public class ButtonFormDataSource : Interfaces.IDataSource + { + public virtual ButtonForm ButtonForm + { + get + { + return __buttonform; + } + set + { + __buttonform = value; + } + } + + private ButtonForm __buttonform = null; + + public ButtonFormDataSource() + { + __buttonform = new ButtonForm(); + } + + public ButtonFormDataSource(ButtonForm bf) + { + __buttonform = bf; + } + + + /// + /// Returns the amount of rows exisiting. + /// + /// + public virtual int Count => ButtonForm.Count; + + + /// + /// Returns the amount of rows. + /// + public virtual int RowCount => ButtonForm.Rows; + + /// + /// Returns the maximum amount of columns. + /// + public virtual int ColumnCount => ButtonForm.Cols; + + /// + /// Returns the row with the specific index. + /// + /// + /// + public virtual ButtonRow ItemAt(int index) + { + return ButtonForm[index]; + } + + public virtual List ItemRange(int start, int count) + { + return ButtonForm.GetRange(start, count); + } + + public virtual List AllItems() + { + return ButtonForm.ToArray(); + } + + public virtual ButtonForm PickItems(int start, int count, String filter = null) + { + ButtonForm bf = new ButtonForm(); + ButtonForm dataForm = null; + + if (filter == null) + { + dataForm = ButtonForm.Duplicate(); + } + else + { + dataForm = ButtonForm.FilterDuplicate(filter, true); + } + + for (int i = 0; i < count; i++) + { + int it = start + i; + + if (it > dataForm.Rows - 1) + break; + + bf.AddButtonRow(dataForm[it]); + } + + return bf; + } + + public virtual ButtonForm PickAllItems(String filter = null) + { + if (filter == null) + return ButtonForm.Duplicate(); + + + return ButtonForm.FilterDuplicate(filter, true); + } + + public virtual Tuple FindRow(String text, bool useText = true) + { + return ButtonForm.FindRow(text, useText); + } + + /// + /// Returns the maximum items of this data source. + /// + /// + /// + public virtual int CalculateMax(String filter = null) + { + return PickAllItems(filter).Rows; + } + + public virtual ButtonRow Render(object data) + { + return data as ButtonRow; + } + + + public static implicit operator ButtonFormDataSource(ButtonForm bf) + { + return new ButtonFormDataSource(bf); + } + + public static implicit operator ButtonForm(ButtonFormDataSource ds) + { + return ds.ButtonForm; + } + + } +} diff --git a/TelegramBotBase/Datasources/StaticDataSource.cs b/TelegramBotBase/Datasources/StaticDataSource.cs new file mode 100644 index 0000000..19ff96c --- /dev/null +++ b/TelegramBotBase/Datasources/StaticDataSource.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace TelegramBotBase.Datasources +{ + public class StaticDataSource : Interfaces.IDataSource + { + List Data { get; set; } + + public StaticDataSource() + { + + } + + public StaticDataSource(List data) + { + this.Data = data; + } + + + public int Count + { + get + { + return Data.Count; + } + } + + public T ItemAt(int index) + { + return Data[index]; + } + + public List ItemRange(int start, int count) + { + return Data.Skip(start).Take(count).ToList(); + } + + public List AllItems() + { + return Data; + } + } +} diff --git a/TelegramBotBase/Form/ButtonForm.cs b/TelegramBotBase/Form/ButtonForm.cs index be32f49..c477b37 100644 --- a/TelegramBotBase/Form/ButtonForm.cs +++ b/TelegramBotBase/Form/ButtonForm.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading.Tasks; using Telegram.Bot.Types.ReplyMarkups; using TelegramBotBase.Base; +using TelegramBotBase.Controls.Hybrid; namespace TelegramBotBase.Form { @@ -13,7 +14,7 @@ namespace TelegramBotBase.Form /// public class ButtonForm { - List> Buttons = new List>(); + List Buttons = new List(); public IReplyMarkup Markup { get; set; } @@ -43,7 +44,7 @@ namespace TelegramBotBase.Form } - public List this[int row] + public ButtonRow this[int row] { get { @@ -66,9 +67,14 @@ namespace TelegramBotBase.Form Buttons.Add(new List() { new ButtonBase(Text, Value, Url) }); } - public void AddButtonRow(IEnumerable row) + //public void AddButtonRow(ButtonRow row) + //{ + // Buttons.Add(row.ToList()); + //} + + public void AddButtonRow(ButtonRow row) { - Buttons.Add(row.ToList()); + Buttons.Add(row); } public void AddButtonRow(params ButtonBase[] row) @@ -76,16 +82,26 @@ namespace TelegramBotBase.Form AddButtonRow(row.ToList()); } + public void AddButtonRows(IEnumerable rows) + { + Buttons.AddRange(rows); + } + public void InsertButtonRow(int index, IEnumerable row) { Buttons.Insert(index, row.ToList()); } - public void InsertButtonRow(int index, params ButtonBase[] row) + public void InsertButtonRow(int index, ButtonRow row) { - InsertButtonRow(index, row.ToList()); + Buttons.Insert(index, row); } + //public void InsertButtonRow(int index, params ButtonBase[] row) + //{ + // InsertButtonRow(index, row.ToList()); + //} + public static T[][] SplitTo(IEnumerable items, int itemsPerRow = 2) { T[][] splitted = default(T[][]); @@ -132,34 +148,62 @@ namespace TelegramBotBase.Form } } + /// + /// Returns a range of rows from the buttons. + /// + /// + /// + /// + public List GetRange(int start, int count) + { + return Buttons.Skip(start).Take(count).ToList(); + } + + public List ToList() { - return this.Buttons.DefaultIfEmpty(new List()).Aggregate((a, b) => a.Union(b).ToList()); + return this.Buttons.DefaultIfEmpty(new List()).Select(a => a.ToList()).Aggregate((a, b) => a.Union(b).ToList()); } public InlineKeyboardButton[][] ToInlineButtonArray() { - var ikb = this.Buttons.Select(a => a.Select(b => b.ToInlineButton(this)).ToArray()).ToArray(); + var ikb = this.Buttons.Select(a => a.ToArray().Select(b => b.ToInlineButton(this)).ToArray()).ToArray(); return ikb; } public KeyboardButton[][] ToReplyButtonArray() { - var ikb = this.Buttons.Select(a => a.Select(b => b.ToKeyboardButton(this)).ToArray()).ToArray(); + var ikb = this.Buttons.Select(a => a.ToArray().Select(b => b.ToKeyboardButton(this)).ToArray()).ToArray(); return ikb; } + public List ToArray() + { + return Buttons; + } + public int FindRowByButton(ButtonBase button) { - var row = this.Buttons.FirstOrDefault(a => a.Count(b => b == button) > 0); + var row = this.Buttons.FirstOrDefault(a => a.ToArray().Count(b => b == button) > 0); if (row == null) return -1; return this.Buttons.IndexOf(row); } + public Tuple FindRow(String text, bool useText = true) + { + var r = this.Buttons.FirstOrDefault(a => a.Matches(text, useText)); + if (r == null) + return null; + + var i = this.Buttons.IndexOf(r); + return new Tuple(r, i); + } + + /// /// Returns the first Button with the given value. /// @@ -204,7 +248,7 @@ namespace TelegramBotBase.Form foreach (var b in Buttons) { - var lst = new List(); + var lst = new ButtonRow(); foreach (var b2 in b) { lst.Add(b2); @@ -229,7 +273,7 @@ namespace TelegramBotBase.Form foreach (var b in Buttons) { - var lst = new List(); + var lst = new ButtonRow(); foreach (var b2 in b) { if (b2.Text.IndexOf(filter, StringComparison.InvariantCultureIgnoreCase) == -1) @@ -238,7 +282,7 @@ namespace TelegramBotBase.Form //Copy full row, when at least one match has found. if (ByRow) { - lst.AddRange(b); + lst = b; break; } else @@ -268,7 +312,7 @@ namespace TelegramBotBase.Form foreach (var b in Buttons) { - var lst = new List(); + var lst = new ButtonRow(); foreach (var b2 in b) { if (!(b2 is TagButtonBase tb)) @@ -280,7 +324,7 @@ namespace TelegramBotBase.Form //Copy full row, when at least one match has found. if (ByRow) { - lst.AddRange(b); + lst = b; break; } else diff --git a/TelegramBotBase/Interfaces/IDataSource.cs b/TelegramBotBase/Interfaces/IDataSource.cs new file mode 100644 index 0000000..d3662e5 --- /dev/null +++ b/TelegramBotBase/Interfaces/IDataSource.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Text; +using TelegramBotBase.Controls.Hybrid; + +namespace TelegramBotBase.Interfaces +{ + public interface IDataSource + { + + /// + /// Returns the amount of items within this source. + /// + /// + int Count { get; } + + + /// + /// Returns the item at the specific index. + /// + /// + /// + T ItemAt(int index); + + + /// + /// Get all items from this source within this range. + /// + /// + /// + /// + List ItemRange(int start, int count); + + + /// + /// Gets a list of all items of this datasource. + /// + /// + List AllItems(); + + + } +} diff --git a/TelegramBotBaseTest/Tests/Controls/ButtonGridTagForm.cs b/TelegramBotBaseTest/Tests/Controls/ButtonGridTagForm.cs index 1060754..c737d10 100644 --- a/TelegramBotBaseTest/Tests/Controls/ButtonGridTagForm.cs +++ b/TelegramBotBaseTest/Tests/Controls/ButtonGridTagForm.cs @@ -32,6 +32,7 @@ namespace TelegramBotBaseTest.Tests.Controls m_Buttons.EnablePaging = true; m_Buttons.HeadLayoutButtonRow = new List() { new ButtonBase("Back", "back") }; + var countries = CultureInfo.GetCultures(CultureTypes.SpecificCultures); @@ -45,7 +46,9 @@ namespace TelegramBotBaseTest.Tests.Controls m_Buttons.Tags = countries.Select(a => a.Parent.EnglishName).Distinct().OrderBy(a => a).ToList(); m_Buttons.SelectedTags = countries.Select(a => a.Parent.EnglishName).Distinct().OrderBy(a => a).ToList(); - m_Buttons.ButtonsForm = bf; + m_Buttons.EnableCheckAllTools = true; + + m_Buttons.DataSource = new TelegramBotBase.Datasources.ButtonFormDataSource(bf); m_Buttons.ButtonClicked += Bg_ButtonClicked; @@ -57,11 +60,13 @@ namespace TelegramBotBaseTest.Tests.Controls if (e.Button == null) return; - if (e.Button.Value == "back") + switch (e.Button.Value) { - var start = new Menu(); - await this.NavigateTo(start); - return; + + case "back": + var start = new Menu(); + await this.NavigateTo(start); + return; } diff --git a/TelegramBotBaseTest/Tests/Datasources/CustomDataSource.cs b/TelegramBotBaseTest/Tests/Datasources/CustomDataSource.cs new file mode 100644 index 0000000..3187968 --- /dev/null +++ b/TelegramBotBaseTest/Tests/Datasources/CustomDataSource.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using TelegramBotBase.Controls.Hybrid; +using TelegramBotBase.Datasources; +using TelegramBotBase.Form; + +namespace TelegramBotBaseTest.Tests.Datasources +{ + public class CustomDataSource : ButtonFormDataSource + { + + public List Countries = new List() { "Country 1", "Country 2", "Country 3" }; + + public CustomDataSource() + { + loadData(); + } + + /// + /// This method has the example purpose of creating and loading some example data. + /// When using a database you do not need this kind of method. + /// + private void loadData() + { + //Exists data source? Read it + if (File.Exists(AppContext.BaseDirectory + "countries.json")) + { + try + { + var List = Newtonsoft.Json.JsonConvert.DeserializeObject>(File.ReadAllText("countries.json")); + + + Countries = List; + } + catch + { + + } + + + return; + } + + //If not, create it + try + { + var countries = CultureInfo.GetCultures(CultureTypes.SpecificCultures).Select(a => a.DisplayName).ToList(); + + Countries = countries; + + var tmp = Newtonsoft.Json.JsonConvert.SerializeObject(countries); + + File.WriteAllText( AppContext.BaseDirectory + "countries.json", tmp); + } + catch + { + + } + + } + + public override ButtonRow ItemAt(int index) + { + var item = Countries.ElementAt(index); + if (item == null) + return new ButtonRow(); + + return Render(item); + } + + public override List ItemRange(int start, int count) + { + var items = Countries.Skip(start).Take(count); + + List lst = new List(); + foreach (var c in items) + { + lst.Add(Render(c)); + } + + return lst; + } + + public override List AllItems() + { + List lst = new List(); + foreach (var c in Countries) + { + lst.Add(Render(c)); + } + return lst; + } + + public override ButtonForm PickItems(int start, int count, string filter = null) + { + List rows = ItemRange(start, count); + + ButtonForm lst = new ButtonForm(); + foreach (var c in rows) + { + lst.AddButtonRow(c); + } + return lst; + } + + public override ButtonForm PickAllItems(string filter = null) + { + List rows = AllItems(); + + ButtonForm bf = new ButtonForm(); + + bf.AddButtonRows(rows); + + return bf; + } + + public override int CalculateMax(string filter = null) + { + if (filter == null) + return Countries.Count; + + return Countries.Where(a => a.IndexOf(filter, StringComparison.InvariantCultureIgnoreCase) != -1).Count(); + } + + public override ButtonRow Render(object data) + { + var s = data as String; + if (s == null) + return new ButtonRow(new ButtonBase("Empty", "zero")); + + return new ButtonRow(new ButtonBase(s, s)); + } + + public override int Count + { + get + { + return Countries.Count; + } + } + + public override int ColumnCount + { + get + { + return 1; + } + } + + public override int RowCount + { + get + { + return this.Count; + } + } + + } +} diff --git a/TelegramBotBaseTest/Tests/Datasources/List.cs b/TelegramBotBaseTest/Tests/Datasources/List.cs new file mode 100644 index 0000000..b2ec372 --- /dev/null +++ b/TelegramBotBaseTest/Tests/Datasources/List.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using TelegramBotBase.Base; +using TelegramBotBase.Controls.Hybrid; +using TelegramBotBase.Form; + +namespace TelegramBotBaseTest.Tests.Datasources +{ + public class List : FormBase + { + ButtonGrid __buttons = null; + + public List() + { + this.Init += List_Init; + } + + private async Task List_Init(object sender, TelegramBotBase.Args.InitEventArgs e) + { + + __buttons = new ButtonGrid(); + + __buttons.EnablePaging = true; + __buttons.EnableSearch = false; + __buttons.ButtonClicked += __buttons_ButtonClicked; + __buttons.KeyboardType = TelegramBotBase.Enums.eKeyboardType.ReplyKeyboard; + __buttons.DeleteReplyMessage = true; + + __buttons.HeadLayoutButtonRow = new ButtonRow(new ButtonBase("Back", "back")); + + var cds = new CustomDataSource(); + __buttons.DataSource = cds; + + AddControl(__buttons); + } + + private async Task __buttons_ButtonClicked(object sender, TelegramBotBase.Args.ButtonClickedEventArgs e) + { + switch(e.Button.Value) + { + case "back": + + var mn = new Menu(); + await NavigateTo(mn); + + break; + } + } + + public override async Task Load(MessageResult message) + { + + } + + + public override async Task Render(MessageResult message) + { + + } + + + } +} diff --git a/TelegramBotBaseTest/Tests/Menu.cs b/TelegramBotBaseTest/Tests/Menu.cs index e4de4f0..8f96053 100644 --- a/TelegramBotBaseTest/Tests/Menu.cs +++ b/TelegramBotBaseTest/Tests/Menu.cs @@ -190,6 +190,14 @@ namespace TelegramBotBaseTest.Tests await NavigateTo(nc); + break; + + case "dynamicbuttongrid": + + var dg = new Datasources.List(); + + await NavigateTo(dg); + break; default: @@ -237,6 +245,8 @@ namespace TelegramBotBaseTest.Tests btn.AddButtonRow(new ButtonBase("#17 NavigationController (Push/Pop)", new CallbackData("a", "navigationcontroller").Serialize())); + btn.AddButtonRow(new ButtonBase("#18 Dynamic ButtonGrid (DataSources)", new CallbackData("a", "dynamicbuttongrid").Serialize())); + await this.Device.Send("Choose your test:", btn); } From aabc7f04b7ec0671ae3220f26bf744a671cc12c7 Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Mon, 26 Jul 2021 15:38:47 +0200 Subject: [PATCH 20/26] Adding documentation about NavigationController --- README.md | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/README.md b/README.md index 11e605d..a345a76 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,16 @@ Thanks ! * [IgnoreState](#ignorestate) + +[Navigation and NavigationController (v4.0.0)](#navigiation-and-navigationcontroller) + + * [As of Now](#as-of-now) + + * [How to use](#how-to-use) + + + + - [Examples](#examples) --- @@ -1073,6 +1083,82 @@ public class Registration : STForm ``` +## Navigation and NavigationController + +### As of now + +As from earlier topics on this readme you alread know the default way for (cross) navigating between Forms. +It will look something like this: + +``` + +var f = new FormBase(); +await this.NavigateTo(f); + + +``` + +Depending on the model and structure of your bot it can make sense, to have more linear navigation instead of "cross" navigation. + +In example you have a bot which shows a list of football teams. And when clicking on it you want to open the team details and latest matches. + +After the matches you want to maybe switch to a different teams and take a look at their statistics and matches. + +At some point, you "just" want to get back to the first team so like on Android your clicking the "back" button multiple times. + +This can become really complicated, when not having some controller below which handle these "Push/Pop" calls. + +Thats why I introduced a NavigationController class which manages these situations and the stack. + + +### How to use ? + +First you need to create a NavigationController instance at the same position in code, where you want to start the navigation. + +You will use the current FormBase instance as a root class within the constructor. So you can later come back to this one. +Where are using the same Form instance as above. + + +``` + +var nc = new NavigationController(this); + +var f = new FormBase(); + +//Replace the current form in the context with the controller. +await this.NavigateTo(nc); + +//Push the new from onto the stack to render it +nc.PushAsync(f); + + +``` + +**Tip**: *By default the NavigationController has ForceCleanupOnLastPop enabled, which means that when the stack is again at 1 (due to PopAsync or PopToRootAsync calls) it will replace the controller automatically with the root form you have given to the constructor at the beginning.* + + +Later to open a new form use PushAsync again: + +``` + +await this.NavigationController.PushAsync(newForm); + +``` + +When you want to go back one Form on the stack use PopAsync: + + +``` + +await this.NavigationController.PopAsync(); + +``` + + + + + + ## Examples From 6ed91224ebf298bb8131b7d14ba40507aacc500e Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Mon, 26 Jul 2021 15:39:47 +0200 Subject: [PATCH 21/26] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a345a76..ec59c25 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ Thanks ! * [IgnoreState](#ignorestate) -[Navigation and NavigationController (v4.0.0)](#navigiation-and-navigationcontroller) +- [Navigation and NavigationController (v4.0.0)](#navigation-and-navigationcontroller) * [As of Now](#as-of-now) From 9fa3df288912d2ee8b5ae62635094666c564ce05 Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Mon, 26 Jul 2021 15:42:04 +0200 Subject: [PATCH 22/26] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index ec59c25..1e0adb7 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ Thanks ! * [As of Now](#as-of-now) - * [How to use](#how-to-use) + * [How to use](#how-to-use-) @@ -1095,7 +1095,6 @@ It will look something like this: var f = new FormBase(); await this.NavigateTo(f); - ``` Depending on the model and structure of your bot it can make sense, to have more linear navigation instead of "cross" navigation. @@ -1131,7 +1130,6 @@ await this.NavigateTo(nc); //Push the new from onto the stack to render it nc.PushAsync(f); - ``` **Tip**: *By default the NavigationController has ForceCleanupOnLastPop enabled, which means that when the stack is again at 1 (due to PopAsync or PopToRootAsync calls) it will replace the controller automatically with the root form you have given to the constructor at the beginning.* From 23969881c19a463176e18d252121f43df4a695b2 Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Mon, 26 Jul 2021 15:44:06 +0200 Subject: [PATCH 23/26] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 1e0adb7..12a9ae9 100644 --- a/README.md +++ b/README.md @@ -1132,8 +1132,6 @@ nc.PushAsync(f); ``` -**Tip**: *By default the NavigationController has ForceCleanupOnLastPop enabled, which means that when the stack is again at 1 (due to PopAsync or PopToRootAsync calls) it will replace the controller automatically with the root form you have given to the constructor at the beginning.* - Later to open a new form use PushAsync again: @@ -1152,7 +1150,7 @@ await this.NavigationController.PopAsync(); ``` - +**Notice**: *By default the NavigationController has ForceCleanupOnLastPop enabled, which means that when the stack is again at 1 (due to PopAsync or PopToRootAsync calls) it will replace the controller automatically with the root form you have given to the constructor at the beginning.* From d06e1eaa52a7c05494722d1067808944512ed4ee Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Mon, 26 Jul 2021 15:52:59 +0200 Subject: [PATCH 24/26] Update README.md --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 12a9ae9..67379a4 100644 --- a/README.md +++ b/README.md @@ -1087,7 +1087,7 @@ public class Registration : STForm ### As of now -As from earlier topics on this readme you alread know the default way for (cross) navigating between Forms. +As from earlier topics on this readme you already know the default way for (cross) navigation between Forms. It will look something like this: ``` @@ -1115,7 +1115,10 @@ Thats why I introduced a NavigationController class which manages these situatio First you need to create a NavigationController instance at the same position in code, where you want to start the navigation. You will use the current FormBase instance as a root class within the constructor. So you can later come back to this one. -Where are using the same Form instance as above. + +**Tip**: *You can add also a completely new instance of i.e. a main menu form here to get back to it then. So you are free to choose.* + +We are using the same FormBase instance as above. ``` From f021f12486a99306b5dfee0452d2665d41e30722 Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Tue, 27 Jul 2021 17:00:16 +0200 Subject: [PATCH 25/26] Update Localization.cs - change to abstract so it can't be used accidentally --- TelegramBotBase/Localizations/Localization.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TelegramBotBase/Localizations/Localization.cs b/TelegramBotBase/Localizations/Localization.cs index 338177b..ed56cb0 100644 --- a/TelegramBotBase/Localizations/Localization.cs +++ b/TelegramBotBase/Localizations/Localization.cs @@ -4,7 +4,7 @@ using System.Text; namespace TelegramBotBase.Localizations { - public class Localization + public abstract class Localization { public Dictionary Values = new Dictionary(); From ef8ca3c988b4d9d65f0e253b5281fe88a1f8bd14 Mon Sep 17 00:00:00 2001 From: FlorianDahn Date: Sun, 1 Aug 2021 13:50:18 +0300 Subject: [PATCH 26/26] Adding basic example for different types of ConfirmAction --- TelegramBotBaseTest/Tests/Menu.cs | 9 +++ .../Tests/Notifications/Start.cs | 63 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 TelegramBotBaseTest/Tests/Notifications/Start.cs diff --git a/TelegramBotBaseTest/Tests/Menu.cs b/TelegramBotBaseTest/Tests/Menu.cs index 8f96053..6f14fec 100644 --- a/TelegramBotBaseTest/Tests/Menu.cs +++ b/TelegramBotBaseTest/Tests/Menu.cs @@ -200,6 +200,13 @@ namespace TelegramBotBaseTest.Tests break; + case "notifications": + + var not = new Notifications.Start(); + await NavigateTo(not); + + break; + default: message.Handled = false; @@ -247,6 +254,8 @@ namespace TelegramBotBaseTest.Tests btn.AddButtonRow(new ButtonBase("#18 Dynamic ButtonGrid (DataSources)", new CallbackData("a", "dynamicbuttongrid").Serialize())); + btn.AddButtonRow(new ButtonBase("#19 Notifications", new CallbackData("a", "notifications").Serialize())); + await this.Device.Send("Choose your test:", btn); } diff --git a/TelegramBotBaseTest/Tests/Notifications/Start.cs b/TelegramBotBaseTest/Tests/Notifications/Start.cs new file mode 100644 index 0000000..9fb96f1 --- /dev/null +++ b/TelegramBotBaseTest/Tests/Notifications/Start.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using TelegramBotBase.Base; +using TelegramBotBase.Form; + +namespace TelegramBotBaseTest.Tests.Notifications +{ + public class Start : AutoCleanForm + { + bool sent = false; + + public Start() + { + this.DeleteMode = TelegramBotBase.Enums.eDeleteMode.OnLeavingForm; + } + + public override async Task Action(MessageResult message) + { + if (message.Handled) + return; + + switch (message.RawData) + { + case "alert": + + await message.ConfirmAction("This is an alert.", true); + + break; + case "back": + + var mn = new Menu(); + await NavigateTo(mn); + + break; + default: + + await message.ConfirmAction("This is feedback"); + + break; + + } + + } + + public override async Task Render(MessageResult message) + { + if (sent) + return; + + var bf = new ButtonForm(); + bf.AddButtonRow("Normal feeback", "normal"); + bf.AddButtonRow("Alert Box", "alert"); + bf.AddButtonRow("Back", "back"); + + await Device.Send("Choose your test", bf); + + sent = true; + } + + } +}