diff --git a/TelegramBaseTest/Tests/ButtonTestForm.cs b/TelegramBaseTest/Tests/ButtonTestForm.cs index 9cbb7d6..1df08b5 100644 --- a/TelegramBaseTest/Tests/ButtonTestForm.cs +++ b/TelegramBaseTest/Tests/ButtonTestForm.cs @@ -11,7 +11,12 @@ namespace TelegramBaseTest.Tests public class ButtonTestForm : AutoCleanForm { - public override async Task Opened() + public ButtonTestForm() + { + this.Opened += ButtonTestForm_Opened; + } + + private async Task ButtonTestForm_Opened(object sender, EventArgs e) { await this.Device.Send("Hello world! (Click 'back' to get back to Start)"); } @@ -83,6 +88,8 @@ namespace TelegramBaseTest.Tests btn.AddButtonRow(new ButtonBase("Button 3", new CallbackData("a", "button3").Serialize()), new ButtonBase("Button 4", new CallbackData("a", "button4").Serialize())); + btn.AddButtonRow(new ButtonBase("Google.com", "google", "https://www.google.com"), new ButtonBase("Telegram", "telegram", "https://telegram.org/")); + btn.AddButtonRow(new ButtonBase("Back", new CallbackData("a", "back").Serialize())); await this.Device.Send("Click a button", btn); diff --git a/TelegramBaseTest/Tests/ProgressTest.cs b/TelegramBaseTest/Tests/ProgressTest.cs index c9ce89a..f755d0e 100644 --- a/TelegramBaseTest/Tests/ProgressTest.cs +++ b/TelegramBaseTest/Tests/ProgressTest.cs @@ -14,10 +14,13 @@ namespace TelegramBaseTest.Tests public ProgressTest() { - this.DeleteMode = eDeleteMode.OnLeavingForm; + this.DeleteMode = TelegramBotBase.Enums.eDeleteMode.OnLeavingForm; + this.Opened += ProgressTest_Opened; + this.Closed += ProgressTest_Closed; } - public override async Task Opened() + + private async Task ProgressTest_Opened(object sender, EventArgs e) { await this.Device.Send("Welcome to ProgressTest"); } @@ -87,14 +90,14 @@ namespace TelegramBaseTest.Tests //Render Progress bar and show some "example" progress - await Bar.Render(); + await Bar.Render(message); this.Controls.Add(Bar); for (int i = 0; i <= 100; i++) { Bar.Value++; - await Bar.Render(); + await Bar.Render(message); Thread.Sleep(250); } @@ -117,17 +120,10 @@ namespace TelegramBaseTest.Tests await this.Device.Send("Choose your progress bar:", btn); } - public override async Task Closed() + private async Task ProgressTest_Closed(object sender, EventArgs e) { - foreach (var b in this.Controls) - { - await b.Cleanup(); - } - await this.Device.Send("Ciao from ProgressTest"); } - - } } diff --git a/TelegramBaseTest/Tests/Register/Steps/Step1.cs b/TelegramBaseTest/Tests/Register/Steps/Step1.cs index 488b277..d117ae4 100644 --- a/TelegramBaseTest/Tests/Register/Steps/Step1.cs +++ b/TelegramBaseTest/Tests/Register/Steps/Step1.cs @@ -12,11 +12,17 @@ namespace TelegramBaseTest.Tests.Register.Steps { public Data UserData { get; set; } - public async override Task Init(params object[] args) + public Step1() + { + this.Init += Step1_Init; + } + + private async Task Step1_Init(object sender, InitEventArgs e) { this.UserData = new Data(); } + public async override Task Load(MessageResult message) { if (message.Handled) diff --git a/TelegramBaseTest/Tests/SimpleForm.cs b/TelegramBaseTest/Tests/SimpleForm.cs index c28b7c4..fc2b2c8 100644 --- a/TelegramBaseTest/Tests/SimpleForm.cs +++ b/TelegramBaseTest/Tests/SimpleForm.cs @@ -13,16 +13,17 @@ namespace TelegramBaseTest.Tests public SimpleForm() { - this.DeleteSide = eSide.Both; - this.DeleteMode = eDeleteMode.OnLeavingForm; + this.DeleteSide = TelegramBotBase.Enums.eSide.Both; + this.DeleteMode = TelegramBotBase.Enums.eDeleteMode.OnLeavingForm; + + this.Opened += SimpleForm_Opened; } - public override async Task Opened() + private async Task SimpleForm_Opened(object sender, EventArgs e) { await this.Device.Send("Hello world! (send 'back' to get back to Start)\r\nOr\r\nhi, hello, maybe, bye and ciao"); } - public override async Task Load(MessageResult message) { //message.MessageText will work also, cause it is a string you could manage a lot different scenerios here diff --git a/TelegramBaseTest/Tests/Start.cs b/TelegramBaseTest/Tests/Start.cs index 221bdde..75349da 100644 --- a/TelegramBaseTest/Tests/Start.cs +++ b/TelegramBaseTest/Tests/Start.cs @@ -8,8 +8,12 @@ using TelegramBotBase.Form; namespace TelegramBaseTest.Tests { - public class Start : FormBase + public class Start : AutoCleanForm { + public Start() + { + this.DeleteMode = TelegramBotBase.Enums.eDeleteMode.OnLeavingForm; + } public override async Task Action(MessageResult message) { @@ -79,6 +83,36 @@ namespace TelegramBaseTest.Tests await this.NavigateTo(data); + break; + + case "calendar": + + message.Handled = true; + + var calendar = new Controls.CalendarPickerForm(); + + await this.NavigateTo(calendar); + + break; + + case "month": + + message.Handled = true; + + var month = new Controls.MonthPickerForm(); + + await this.NavigateTo(month); + + break; + + case "treeview": + + message.Handled = true; + + var tree = new Controls.TreeViewForms(); + + await this.NavigateTo(tree); + break; } @@ -99,6 +133,10 @@ namespace TelegramBaseTest.Tests btn.AddButtonRow(new ButtonBase("#6 Form2 Command", new CallbackData("a", "form2").Serialize())); btn.AddButtonRow(new ButtonBase("#7 Data Handling", new CallbackData("a", "data").Serialize())); + btn.AddButtonRow(new ButtonBase("#8 Calendar Picker", new CallbackData("a", "calendar").Serialize())); + btn.AddButtonRow(new ButtonBase("#9 Month Picker", new CallbackData("a", "month").Serialize())); + + btn.AddButtonRow(new ButtonBase("#10 TreeView", new CallbackData("a", "treeview").Serialize())); await this.Device.Send("Choose your test:", btn); } diff --git a/TelegramBaseTest/Tests/TestForm.cs b/TelegramBaseTest/Tests/TestForm.cs index 51d13e7..9dce3fe 100644 --- a/TelegramBaseTest/Tests/TestForm.cs +++ b/TelegramBaseTest/Tests/TestForm.cs @@ -15,13 +15,18 @@ namespace TelegramBaseTest.Tests String LastMessage { get; set; } + public TestForm() + { + this.Opened += TestForm_Opened; + this.Closed += TestForm_Closed; + } - public override async Task Opened() + private async Task TestForm_Opened(object sender, EventArgs e) { await this.Device.Send("Welcome to Form 1"); } - public override async Task Closed() + private async Task TestForm_Closed(object sender, EventArgs e) { await this.Device.Send("Ciao from Form 1"); } diff --git a/TelegramBaseTest/Tests/TestForm2.cs b/TelegramBaseTest/Tests/TestForm2.cs index 8374ba8..22a3174 100644 --- a/TelegramBaseTest/Tests/TestForm2.cs +++ b/TelegramBaseTest/Tests/TestForm2.cs @@ -16,17 +16,25 @@ namespace TelegramBaseTest.Tests { - public override async Task Opened() + public TestForm2() + { + this.Opened += TestForm2_Opened; + this.Closed += TestForm2_Closed; + } + + private async Task TestForm2_Opened(object sender, EventArgs e) { await this.Device.Send("Welcome to Form 2"); } - public override async Task Closed() + private async Task TestForm2_Closed(object sender, EventArgs e) { await this.Device.Send("Ciao from Form 2"); } + + public override async Task Action(MessageResult message) { var call = message.GetData(); diff --git a/TelegramBotBase/Base/Async.cs b/TelegramBotBase/Base/Async.cs new file mode 100644 index 0000000..ac190c7 --- /dev/null +++ b/TelegramBotBase/Base/Async.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TelegramBotBase.Base +{ + public static class Async + { + public delegate Task AsyncEventHandler(object sender, TEventArgs e) where TEventArgs : EventArgs; + + public static IEnumerable> GetHandlers( + this AsyncEventHandler handler) + where TEventArgs : EventArgs + => handler.GetInvocationList().Cast>(); + + public static Task InvokeAllAsync(this AsyncEventHandler handler, object sender, TEventArgs e) + where TEventArgs : EventArgs + => Task.WhenAll( + handler.GetHandlers() + .Select(handleAsync => handleAsync(sender, e))); + + } +} diff --git a/TelegramBotBase/Base/ControlBase.cs b/TelegramBotBase/Base/ControlBase.cs index 32ac97c..83e07c4 100644 --- a/TelegramBotBase/Base/ControlBase.cs +++ b/TelegramBotBase/Base/ControlBase.cs @@ -13,9 +13,26 @@ namespace TelegramBotBase.Base { public Sessions.DeviceSession Device { get; set; } - public virtual async Task Render() + public int ID { get; set; } + + /// + /// Defines if the control should be rendered and invoked with actions + /// + public bool Enabled { get; set; } = true; + + public virtual async Task Action(MessageResult result) { + + + } + + public virtual async Task Render(MessageResult result) + { + + + + } public virtual async Task Cleanup() diff --git a/TelegramBotBase/Base/FormBase.cs b/TelegramBotBase/Base/FormBase.cs index 49c71bb..56399af 100644 --- a/TelegramBotBase/Base/FormBase.cs +++ b/TelegramBotBase/Base/FormBase.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; using TelegramBotBase.Base; using TelegramBotBase.Sessions; +using static TelegramBotBase.Base.Async; namespace TelegramBotBase.Form { @@ -13,12 +15,11 @@ namespace TelegramBotBase.Form /// public class FormBase : IDisposable { + public DeviceSession Device { get; set; } public MessageClient Client { get; set; } - public bool CustomEventManagement { get; set; } = false; - /// /// has this formular already been disposed ? /// @@ -26,34 +27,119 @@ namespace TelegramBotBase.Form public List Controls { get; set; } + + private EventHandlerList Events = new EventHandlerList(); + + + private static object __evInit = new object(); + + private static object __evOpened = new object(); + + private static object __evClosed = new object(); + + public FormBase() { this.Controls = new List(); } - public FormBase(MessageClient Client): this() + public FormBase(MessageClient Client) : this() { this.Client = Client; } - /// - /// Will get called at the initialization (once per context) - /// - public virtual async Task Init(params object[] args) + + public async Task OnInit(InitEventArgs e) { - + if (this.Events[__evInit] == null) + return; + + var handler = this.Events[__evInit].GetInvocationList().Cast>(); + foreach(var h in handler) + { + await Async.InvokeAllAsync(h, this, e); + } + } + + ///// + ///// Will get called at the initialization (once per context) + ///// + public event AsyncEventHandler Init + { + add + { + this.Events.AddHandler(__evInit, value); + } + remove + { + this.Events.RemoveHandler(__evInit, value); + } + } + + + + public async Task OnOpened(EventArgs e) + { + if (this.Events[__evOpened] == null) + return; + + var handler = this.Events[__evOpened].GetInvocationList().Cast>(); + foreach (var h in handler) + { + await Async.InvokeAllAsync(h, this, e); + } } /// /// Gets invoked if gets navigated to this form /// /// - public virtual async Task Opened() + public event AsyncEventHandler Opened { - + add + { + this.Events.AddHandler(__evOpened, value); + } + remove + { + this.Events.RemoveHandler(__evOpened, value); + } } - public virtual async Task Closed() + public async Task OnClosed(EventArgs e) + { + if (this.Events[__evClosed] == null) + return; + + var handler = this.Events[__evClosed].GetInvocationList().Cast>(); + foreach (var h in handler) + { + await Async.InvokeAllAsync(h, this, e); + } + } + + /// + /// Form has been closed (left) + /// + /// + public event AsyncEventHandler Closed + { + add + { + this.Events.AddHandler(__evClosed, value); + } + remove + { + this.Events.RemoveHandler(__evClosed, value); + } + } + + + /// + /// Pre to form close, cleanup all controls + /// + /// + public async Task CloseControls() { foreach (var b in this.Controls) { @@ -76,6 +162,22 @@ namespace TelegramBotBase.Form } + /// + /// Gets invoked if the user clicked a button. + /// + /// + /// + public async Task ActionControls(MessageResult message) + { + foreach (var b in this.Controls) + { + if (!b.Enabled) + continue; + + await b.Action(message); + } + } + /// /// Gets invoked if the user has clicked a button. /// @@ -96,6 +198,22 @@ namespace TelegramBotBase.Form } + /// + /// Gets invoked at the end of the cycle to "Render" text, images, buttons, etc... + /// + /// + /// + public async Task RenderControls(MessageResult message) + { + foreach (var b in this.Controls) + { + if (!b.Enabled) + continue; + + await b.Render(message); + } + } + /// /// Gets invoked at the end of the cycle to "Render" text, images, buttons, etc... /// @@ -106,6 +224,8 @@ namespace TelegramBotBase.Form } + + /// /// Navigates to a new form /// @@ -125,11 +245,20 @@ namespace TelegramBotBase.Form newForm.Client = this.Client; newForm.Device = ds; - await newForm.Init(args); + await newForm.OnInit(new InitEventArgs(args)); - await this.Closed(); + await this.CloseControls(); - await newForm.Opened(); + await this.OnClosed(new EventArgs()); + + await newForm.OnOpened(new EventArgs()); + } + + public void AddControl(ControlBase control) + { + control.ID = this.Controls.Count + 1; + control.Device = this.Device; + this.Controls.Add(control); } /// diff --git a/TelegramBotBase/Base/InitEventArgs.cs b/TelegramBotBase/Base/InitEventArgs.cs new file mode 100644 index 0000000..f724695 --- /dev/null +++ b/TelegramBotBase/Base/InitEventArgs.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TelegramBotBase.Base +{ + public class InitEventArgs : EventArgs + { + public object[] Args { get; set; } + + public InitEventArgs(params object[] args) + { + this.Args = args; + } + } +} diff --git a/TelegramBotBase/BotBase.cs b/TelegramBotBase/BotBase.cs index 2300fe1..44e100b 100644 --- a/TelegramBotBase/BotBase.cs +++ b/TelegramBotBase/BotBase.cs @@ -258,16 +258,12 @@ namespace TelegramBotBase do { i++; - + //Reset navigation ds.FormSwitched = false; activeForm = ds.ActiveForm; - //If the form manages the events by itselfs, skip here - if (activeForm.CustomEventManagement) - return; - //Pre Loading Event await activeForm.PreLoad(e); @@ -337,10 +333,6 @@ namespace TelegramBotBase activeForm = ds.ActiveForm; - //If the form manages the events by itselfs, skip here - if (activeForm.CustomEventManagement) - return; - //Pre Loading Event await activeForm.PreLoad(e); @@ -350,6 +342,10 @@ namespace TelegramBotBase //Action Event if (!ds.FormSwitched) { + //Send Action event to controls + await activeForm.ActionControls(e); + + //Send Action event to form itself await activeForm.Action(e); if (!e.Handled) @@ -374,7 +370,11 @@ namespace TelegramBotBase //Render Event if (!ds.FormSwitched) + { + await activeForm.RenderControls(e); + await activeForm.Render(e); + } } while (ds.FormSwitched && i < NavigationMaximum); diff --git a/TelegramBotBase/Enums/Enums.cs b/TelegramBotBase/Enums/Enums.cs new file mode 100644 index 0000000..c6fb8a5 --- /dev/null +++ b/TelegramBotBase/Enums/Enums.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TelegramBotBase.Enums +{ + public enum eDeleteMode + { + /// + /// Don't delete any message. + /// + None = 0, + /// + /// Delete messages on every callback/action. + /// + OnEveryCall = 1, + /// + /// Delete on leaving this form. + /// + OnLeavingForm = 2 + } + + public enum eSide + { + /// + /// Delete only messages from this bot. + /// + BotOnly = 0, + /// + /// Delete only user messages. + /// + UserOnly = 1, + /// + /// Delete all messages in this context. + /// + Both = 2 + } + + public enum eMonthPickerMode + { + /// + /// Shows the calendar with day picker mode + /// + day = 0, + /// + /// Shows the calendar with month overview + /// + month = 1, + /// + /// Shows the calendar with year overview + /// + year = 2 + } +} diff --git a/TelegramBotBase/Form/AutoCleanForm.cs b/TelegramBotBase/Form/AutoCleanForm.cs index 099a057..55e5d23 100644 --- a/TelegramBotBase/Form/AutoCleanForm.cs +++ b/TelegramBotBase/Form/AutoCleanForm.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading.Tasks; using Telegram.Bot.Types; using TelegramBotBase.Base; +using TelegramBotBase.Enums; namespace TelegramBotBase.Form { @@ -19,37 +20,7 @@ namespace TelegramBotBase.Form public eSide DeleteSide { get; set; } - public enum eDeleteMode - { - /// - /// Don't delete any message. - /// - None = 0, - /// - /// Delete messages on every callback/action. - /// - OnEveryCall = 1, - /// - /// Delete on leaving this form. - /// - OnLeavingForm = 2 - } - - public enum eSide - { - /// - /// Delete only messages from this bot. - /// - BotOnly = 0, - /// - /// Delete only user messages. - /// - UserOnly = 1, - /// - /// Delete all messages in this context. - /// - Both = 2 - } + public AutoCleanForm() { @@ -57,9 +28,14 @@ namespace TelegramBotBase.Form this.DeleteMode = eDeleteMode.OnEveryCall; this.DeleteSide = eSide.BotOnly; + this.Init += AutoCleanForm_Init; + + this.Closed += AutoCleanForm_Closed; + } - public override async Task Init(params object[] args) + + private async Task AutoCleanForm_Init(object sender, InitEventArgs e) { if (this.Device == null) return; @@ -127,7 +103,7 @@ namespace TelegramBotBase.Form this.OldMessages.RemoveAt(this.OldMessages.Count - 1); } - public async override Task Closed() + private async Task AutoCleanForm_Closed(object sender, EventArgs e) { if (this.DeleteMode != eDeleteMode.OnLeavingForm) return; @@ -135,7 +111,6 @@ namespace TelegramBotBase.Form await MessageCleanup(); } - /// /// Cleans up all remembered messages. /// diff --git a/TelegramBotBase/Form/ButtonForm.cs b/TelegramBotBase/Form/ButtonForm.cs index 2a1cbf9..a52f5bf 100644 --- a/TelegramBotBase/Form/ButtonForm.cs +++ b/TelegramBotBase/Form/ButtonForm.cs @@ -77,6 +77,9 @@ namespace TelegramBotBase.Form public static implicit operator InlineKeyboardMarkup(ButtonForm form) { + if (form == null) + return null; + InlineKeyboardMarkup ikm = new InlineKeyboardMarkup(form.ToArray()); return ikm; diff --git a/TelegramBotBase/SessionBase.cs b/TelegramBotBase/SessionBase.cs index 7039718..9f9a64f 100644 --- a/TelegramBotBase/SessionBase.cs +++ b/TelegramBotBase/SessionBase.cs @@ -67,9 +67,9 @@ namespace TelegramBotBase DeviceSession ds = new Sessions.DeviceSession(deviceId, start); start.Device = ds; - await start.Init(); + await start.OnInit(new InitEventArgs()); - await start.Opened(); + await start.OnOpened(new EventArgs()); this[deviceId] = ds; return ds; diff --git a/TelegramBotBase/Sessions/DeviceSession.cs b/TelegramBotBase/Sessions/DeviceSession.cs index 5e2e040..c8100cc 100644 --- a/TelegramBotBase/Sessions/DeviceSession.cs +++ b/TelegramBotBase/Sessions/DeviceSession.cs @@ -127,11 +127,7 @@ namespace TelegramBotBase.Sessions if (this.ActiveForm == null) return null; - InlineKeyboardMarkup markup = null; - if (buttons != null) - { - markup = buttons; - } + InlineKeyboardMarkup markup = buttons; try { @@ -160,11 +156,7 @@ namespace TelegramBotBase.Sessions if (this.ActiveForm == null) return null; - InlineKeyboardMarkup markup = null; - if (buttons != null) - { - markup = buttons; - } + InlineKeyboardMarkup markup = buttons; try { @@ -194,11 +186,7 @@ namespace TelegramBotBase.Sessions if (this.ActiveForm == null) return null; - InlineKeyboardMarkup markup = null; - if (buttons != null) - { - markup = buttons; - } + InlineKeyboardMarkup markup = buttons; Message m = null; @@ -253,6 +241,39 @@ namespace TelegramBotBase.Sessions return m; } + /// + /// Sends a simple text message + /// + /// + /// + /// + /// + /// + public async Task Send(String text, ReplyKeyboardMarkup markup, int replyTo = 0, bool disableNotification = false) + { + if (this.ActiveForm == null) + return null; + + Message m = null; + + try + { + m = await (this.Client.TelegramClient.SendTextMessageAsync(this.DeviceId, text, replyToMessageId: replyTo, replyMarkup: markup, disableNotification: disableNotification)); + + OnMessageSent(new MessageSentEventArgs(m)); + } + catch (ApiRequestException ex) + { + return null; + } + catch + { + return null; + } + + return m; + } + /// /// Sends an image /// @@ -266,11 +287,7 @@ namespace TelegramBotBase.Sessions if (this.ActiveForm == null) return null; - InlineKeyboardMarkup markup = null; - if (buttons != null) - { - markup = buttons; - } + InlineKeyboardMarkup markup = buttons; Message m = null;