diff --git a/README.md b/README.md
index 11e605d..67379a4 100644
--- a/README.md
+++ b/README.md
@@ -106,6 +106,16 @@ Thanks !
* [IgnoreState](#ignorestate)
+
+- [Navigation and NavigationController (v4.0.0)](#navigation-and-navigationcontroller)
+
+ * [As of Now](#as-of-now)
+
+ * [How to use](#how-to-use-)
+
+
+
+
- [Examples](#examples)
---
@@ -1073,6 +1083,81 @@ public class Registration : STForm
```
+## Navigation and NavigationController
+
+### As of now
+
+As from earlier topics on this readme you already know the default way for (cross) navigation 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.
+
+**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.
+
+
+```
+
+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);
+
+```
+
+
+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();
+
+```
+
+**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.*
+
+
+
+
## Examples
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/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/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 31028e9..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; }
@@ -47,6 +65,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.
///
@@ -57,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.
///
@@ -82,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.
@@ -129,7 +166,7 @@ namespace TelegramBotBase.Controls.Hybrid
public TaggedButtonGrid()
{
- this.ButtonsForm = new ButtonForm();
+ this.DataSource = new ButtonFormDataSource();
this.SelectedViewIndex = 0;
}
@@ -142,7 +179,7 @@ namespace TelegramBotBase.Controls.Hybrid
public TaggedButtonGrid(ButtonForm form)
{
- this.ButtonsForm = form;
+ this.DataSource = new ButtonFormDataSource(form);
}
@@ -194,30 +231,63 @@ 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;
+
+ 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.DeletePreviousMessage)
- await Device.DeleteMessage(result.MessageId);
+ if (match != null)
+ {
+ await OnButtonClicked(new ButtonClickedEventArgs(match.GetButtonMatch(result.MessageText), index, match));
result.Handled = true;
-
return;
}
@@ -282,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)
@@ -323,27 +401,60 @@ 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)
- ?? 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;
@@ -381,6 +492,18 @@ namespace TelegramBotBase.Controls.Hybrid
this.SelectedViewIndex = 0;
this.Updated();
+ break;
+
+ case "$checkall$":
+
+ this.CheckAllTags();
+
+ break;
+
+ case "$uncheckall$":
+
+ this.UncheckAllTags();
+
break;
}
@@ -395,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;
@@ -463,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)
{
@@ -481,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());
}
}
@@ -506,21 +629,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 +645,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:
@@ -556,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
@@ -617,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)
{
@@ -636,21 +748,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 +767,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:
@@ -744,7 +857,7 @@ namespace TelegramBotBase.Controls.Hybrid
{
get
{
- return this.LayoutRows + ButtonsForm.Rows;
+ return this.LayoutRows + DataSource.RowCount;
}
}
@@ -767,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));
}
}
@@ -840,9 +971,30 @@ namespace TelegramBotBase.Controls.Hybrid
break;
}
+ }
+ ///
+ /// 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();
}
}
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/AutoCleanForm.cs b/TelegramBotBase/Form/AutoCleanForm.cs
index 4a7c6c5..5617775 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,79 @@ namespace TelegramBotBase.Form
///
public async Task MessageCleanup()
{
- while (this.OldMessages.Count > 0)
+ var oldMessages = OldMessages.AsEnumerable();
+
+#if !NET472
+ 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);
+ });
}
-
- await Task.WhenAll(tasks);
-
- foreach(var m in msgs)
+ catch (AggregateException ex)
{
- Device.OnMessageDeleted(new MessageDeletedEventArgs(m));
- }
+ cts.Cancel();
- this.OldMessages.RemoveRange(0, msgs.Count());
- }
+ 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;
+ }
+#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/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/Form/Navigation/NavigationController.cs b/TelegramBotBase/Form/Navigation/NavigationController.cs
new file mode 100644
index 0000000..d1858b9
--- /dev/null
+++ b/TelegramBotBase/Form/Navigation/NavigationController.cs
@@ -0,0 +1,358 @@
+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 to the root 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());
+ }
+ }
+
+ ///
+ /// Pop's through all forms back to the root form.
+ ///
+ ///
+ public virtual async Task PopToRootAsync()
+ {
+ while (Index > 0)
+ {
+ await PopAsync();
+ }
+ }
+
+ ///
+ /// 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
+
+ }
+}
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/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..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();
@@ -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";
+
}
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());
diff --git a/TelegramBotBase/TelegramBotBase.csproj b/TelegramBotBase/TelegramBotBase.csproj
index 3f7a020..0b31660 100644
--- a/TelegramBotBase/TelegramBotBase.csproj
+++ b/TelegramBotBase/TelegramBotBase.csproj
@@ -7,7 +7,7 @@
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
@@ -62,27 +62,9 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
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." });
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
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/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;
}
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 57cf9fb..6f14fec 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,41 @@ 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;
+
+ case "dynamicbuttongrid":
+
+ var dg = new Datasources.List();
+
+ await NavigateTo(dg);
+
+ break;
+
+ case "notifications":
+
+ var not = new Notifications.Start();
+ await NavigateTo(not);
+
+ break;
+
+ default:
+
+ message.Handled = false;
+
break;
}
@@ -240,6 +250,12 @@ 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()));
+
+ 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/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);
+
+
+ }
+
+ }
+}
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;
+ }
+
+ }
+}
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