Merge pull request #13 from MajMcCloud/development

Development
This commit is contained in:
Florian Dahn 2021-08-13 15:33:12 +03:00 committed by GitHub
commit 7004ee5963
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 2171 additions and 408 deletions

View File

@ -106,6 +106,16 @@ Thanks !
* [IgnoreState](#ignorestate) * [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) - [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 ## Examples

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using TelegramBotBase.Controls.Hybrid;
using TelegramBotBase.Form; using TelegramBotBase.Form;
namespace TelegramBotBase.Args namespace TelegramBotBase.Args
@ -18,6 +19,8 @@ namespace TelegramBotBase.Args
public object Tag { get; set; } public object Tag { get; set; }
public ButtonRow Row { get; set; }
public ButtonClickedEventArgs() public ButtonClickedEventArgs()
{ {
@ -36,5 +39,11 @@ namespace TelegramBotBase.Args
this.Index = Index; this.Index = Index;
} }
public ButtonClickedEventArgs(ButtonBase button, int Index, ButtonRow row)
{
this.Button = button;
this.Index = Index;
this.Row = row;
}
} }
} }

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using TelegramBotBase.Controls.Hybrid;
using TelegramBotBase.Form; using TelegramBotBase.Form;
namespace TelegramBotBase.Args namespace TelegramBotBase.Args
@ -17,7 +18,7 @@ namespace TelegramBotBase.Args
/// <summary> /// <summary>
/// Contains all buttons within this row, excluding the checkbox. /// Contains all buttons within this row, excluding the checkbox.
/// </summary> /// </summary>
public List<ButtonBase> Row { get; set; } public ButtonRow Row { get; set; }
/// <summary> /// <summary>
@ -31,7 +32,7 @@ namespace TelegramBotBase.Args
} }
public CheckedChangedEventArgs(List<ButtonBase> row, int Index, bool Checked) public CheckedChangedEventArgs(ButtonRow row, int Index, bool Checked)
{ {
this.Row = row; this.Row = row;
this.Index = Index; this.Index = Index;

View File

@ -6,6 +6,7 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using TelegramBotBase.Args; using TelegramBotBase.Args;
using TelegramBotBase.Base; using TelegramBotBase.Base;
using TelegramBotBase.Form.Navigation;
using TelegramBotBase.Sessions; using TelegramBotBase.Sessions;
using static TelegramBotBase.Base.Async; using static TelegramBotBase.Base.Async;
@ -17,6 +18,8 @@ namespace TelegramBotBase.Form
public class FormBase : IDisposable public class FormBase : IDisposable
{ {
public NavigationController NavigationController { get; set; }
public DeviceSession Device { get; set; } public DeviceSession Device { get; set; }
public MessageClient Client { get; set; } public MessageClient Client { get; set; }

View File

@ -10,6 +10,7 @@ using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types.ReplyMarkups; using Telegram.Bot.Types.ReplyMarkups;
using TelegramBotBase.Args; using TelegramBotBase.Args;
using TelegramBotBase.Base; using TelegramBotBase.Base;
using TelegramBotBase.Datasources;
using TelegramBotBase.Enums; using TelegramBotBase.Enums;
using TelegramBotBase.Exceptions; using TelegramBotBase.Exceptions;
using TelegramBotBase.Form; using TelegramBotBase.Form;
@ -30,7 +31,26 @@ namespace TelegramBotBase.Controls.Hybrid
private readonly EventHandlerList Events = new EventHandlerList(); private readonly EventHandlerList Events = new EventHandlerList();
public ButtonForm ButtonsForm { get; set; } /// <summary>
///
/// </summary>
[Obsolete("This property is obsolete. Please use the DataSource property instead.")]
public ButtonForm ButtonsForm
{
get
{
return DataSource.ButtonForm;
}
set
{
DataSource = new ButtonFormDataSource(value);
}
}
/// <summary>
/// Data source of the items.
/// </summary>
public ButtonFormDataSource DataSource { get; set; }
public int? MessageId { get; set; } public int? MessageId { get; set; }
@ -89,12 +109,12 @@ namespace TelegramBotBase.Controls.Hybrid
/// <summary> /// <summary>
/// Layout of the buttons which should be displayed always on top. /// Layout of the buttons which should be displayed always on top.
/// </summary> /// </summary>
public List<ButtonBase> HeadLayoutButtonRow { get; set; } public ButtonRow HeadLayoutButtonRow { get; set; }
/// <summary> /// <summary>
/// Layout of columns which should be displayed below the header /// Layout of columns which should be displayed below the header
/// </summary> /// </summary>
public List<ButtonBase> SubHeadLayoutButtonRow { get; set; } public ButtonRow SubHeadLayoutButtonRow { get; set; }
/// <summary> /// <summary>
/// Defines which type of Button Keyboard should be rendered. /// Defines which type of Button Keyboard should be rendered.
@ -123,8 +143,7 @@ namespace TelegramBotBase.Controls.Hybrid
public ButtonGrid() public ButtonGrid()
{ {
this.ButtonsForm = new ButtonForm(); this.DataSource = new ButtonFormDataSource();
} }
@ -136,7 +155,7 @@ namespace TelegramBotBase.Controls.Hybrid
public ButtonGrid(ButtonForm form) public ButtonGrid(ButtonForm form)
{ {
this.ButtonsForm = form; this.DataSource = new ButtonFormDataSource(form);
} }
@ -188,22 +207,49 @@ namespace TelegramBotBase.Controls.Hybrid
if (!result.IsFirstHandler) if (!result.IsFirstHandler)
return; return;
if (result.MessageText == null) if (result.MessageText == null || result.MessageText == "")
return; return;
var button = HeadLayoutButtonRow?.FirstOrDefault(a => a.Text.Trim() == result.MessageText) var matches = new List<ButtonRow>();
?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Text.Trim() == result.MessageText) ButtonRow match = null;
?? ButtonsForm.ToList().FirstOrDefault(a => a.Text.Trim() == result.MessageText); int index = -1;
var index = ButtonsForm.FindRowByButton(button); if (HeadLayoutButtonRow?.Matches(result.MessageText) ?? false)
if (button != null)
{ {
await OnButtonClicked(new ButtonClickedEventArgs(button, index)); match = HeadLayoutButtonRow;
goto check;
}
//Remove button click message if (SubHeadLayoutButtonRow?.Matches(result.MessageText) ?? false)
if (this.DeleteReplyMessage) {
await Device.DeleteMessage(result.MessageId); 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; result.Handled = true;
return; return;
@ -261,22 +307,48 @@ namespace TelegramBotBase.Controls.Hybrid
if (!result.IsFirstHandler) if (!result.IsFirstHandler)
return; return;
await result.ConfirmAction(this.ConfirmationText ?? "");
//Find clicked button depending on Text or Value (depending on markup type) //Find clicked button depending on Text or Value (depending on markup type)
if (this.KeyboardType != eKeyboardType.InlineKeyBoard) if (this.KeyboardType != eKeyboardType.InlineKeyBoard)
return; return;
await result.ConfirmAction(this.ConfirmationText ?? "");
var button = HeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData) ButtonRow match = null;
?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData) int index = -1;
?? ButtonsForm.ToList().FirstOrDefault(a => a.Value == result.RawData);
var index = ButtonsForm.FindRowByButton(button); if (HeadLayoutButtonRow?.Matches(result.RawData, false) ?? false)
if (button != null)
{ {
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; result.Handled = true;
return; return;
@ -313,28 +385,28 @@ namespace TelegramBotBase.Controls.Hybrid
{ {
case eKeyboardType.InlineKeyBoard: 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; break;
case eKeyboardType.ReplyKeyboard: 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; break;
@ -351,22 +423,21 @@ namespace TelegramBotBase.Controls.Hybrid
this.RenderNecessary = false; 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 != "") //if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "")
{ //{
form = form.FilterDuplicate(this.SearchQuery, true); // form = form.FilterDuplicate(this.SearchQuery, true);
} //}
else //else
{ //{
form = form.Duplicate(); // form = form.Duplicate();
} //}
if (this.EnablePaging) if (this.EnablePaging)
{ {
form = GeneratePagingView(form); IntegratePagingView(form);
} }
if (this.HeadLayoutButtonRow != null && HeadLayoutButtonRow.Count > 0) if (this.HeadLayoutButtonRow != null && HeadLayoutButtonRow.Count > 0)
@ -386,26 +457,37 @@ namespace TelegramBotBase.Controls.Hybrid
} }
} }
Message m = null;
switch (this.KeyboardType) switch (this.KeyboardType)
{ {
//Reply Keyboard could only be updated with a new keyboard. //Reply Keyboard could only be updated with a new keyboard.
case eKeyboardType.ReplyKeyboard: case eKeyboardType.ReplyKeyboard:
if (this.MessageId != null)
if (form.Count == 0)
{ {
if (form.Count == 0) if (this.MessageId != null)
{ {
await this.Device.HideReplyKeyboard(); await this.Device.HideReplyKeyboard();
this.MessageId = null; this.MessageId = null;
return;
} }
if (this.DeletePreviousMessage) return;
await this.Device.DeleteMessage(this.MessageId.Value);
} }
if (form.Count == 0) //if (this.MessageId != null)
return; //{
// if (form.Count == 0)
// {
// await this.Device.HideReplyKeyboard();
// this.MessageId = null;
// return;
// }
//}
//if (form.Count == 0)
// return;
var rkm = (ReplyKeyboardMarkup)form; var rkm = (ReplyKeyboardMarkup)form;
@ -413,6 +495,10 @@ namespace TelegramBotBase.Controls.Hybrid
rkm.OneTimeKeyboard = this.OneTimeKeyboard; rkm.OneTimeKeyboard = this.OneTimeKeyboard;
m = await this.Device.Send(this.Title, rkm, disableNotification: true, parseMode: MessageParseMode, MarkdownV2AutoEscape: false); 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; break;
case eKeyboardType.InlineKeyBoard: 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 //No Items
if (this.ButtonsForm.Count == 0) if (dataForm.Rows == 0)
{ {
bf.AddButtonRow(new ButtonBase(NoItemsLabel, "$")); dataForm.AddButtonRow(new ButtonBase(NoItemsLabel, "$"));
} }
if (this.IsNavigationBarVisible) if (this.IsNavigationBarVisible)
{ {
//🔍 //🔍
List<ButtonBase> lst = new List<ButtonBase>(); ButtonRow row = new ButtonRow();
lst.Add(new ButtonBase(PreviousPageLabel, "$previous$")); row.Add(new ButtonBase(PreviousPageLabel, "$previous$"));
lst.Add(new ButtonBase(String.Format(Localizations.Default.Language["ButtonGrid_CurrentPage"], this.CurrentPageIndex + 1, this.PageCount), "$site$")); row.Add(new ButtonBase(String.Format(Localizations.Default.Language["ButtonGrid_CurrentPage"], this.CurrentPageIndex + 1, this.PageCount), "$site$"));
lst.Add(new ButtonBase(NextPageLabel, "$next$")); row.Add(new ButtonBase(NextPageLabel, "$next$"));
if (this.EnableSearch) 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 public bool PagingNecessary
@ -545,7 +615,7 @@ namespace TelegramBotBase.Controls.Hybrid
{ {
get get
{ {
return this.LayoutRows + ButtonsForm.Rows; return this.LayoutRows + DataSource.RowCount;
} }
} }
@ -572,24 +642,40 @@ namespace TelegramBotBase.Controls.Hybrid
} }
} }
/// <summary>
/// Returns the number of item rows per page.
/// </summary>
public int ItemRowsPerPage
{
get
{
return this.MaximumRow - this.LayoutRows;
}
}
/// <summary>
/// Return the number of pages.
/// </summary>
public int PageCount public int PageCount
{ {
get get
{ {
if (this.ButtonsForm.Count == 0) if (DataSource.RowCount == 0)
return 1; return 1;
var bf = this.ButtonsForm; //var bf = this.DataSource.PickAllItems(this.EnableSearch ? this.SearchQuery : null);
if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "") var max = this.DataSource.CalculateMax(this.EnableSearch ? this.SearchQuery : null);
{
bf = bf.FilterDuplicate(this.SearchQuery);
}
if (bf.Rows == 0) //if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "")
//{
// bf = bf.FilterDuplicate(this.SearchQuery);
//}
if (max == 0)
return 1; return 1;
return (int)Math.Ceiling((decimal)(bf.Rows / (decimal)(MaximumRow - this.LayoutRows))); return (int)Math.Ceiling((decimal)((decimal)max / (decimal)ItemRowsPerPage));
} }
} }

View File

@ -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<ButtonBase> __buttons = new List<ButtonBase>();
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<ButtonBase> GetEnumerator()
{
return __buttons.GetEnumerator();
}
public ButtonBase[] ToArray()
{
return __buttons.ToArray();
}
public List<ButtonBase> 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;
}
/// <summary>
/// Returns the button inside of the row which matches.
/// </summary>
/// <param name="text"></param>
/// <param name="useText"></param>
/// <returns></returns>
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<ButtonBase> list)
{
return new ButtonRow() { __buttons = list };
}
}
}

View File

@ -10,6 +10,7 @@ using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types.ReplyMarkups; using Telegram.Bot.Types.ReplyMarkups;
using TelegramBotBase.Args; using TelegramBotBase.Args;
using TelegramBotBase.Base; using TelegramBotBase.Base;
using TelegramBotBase.Datasources;
using TelegramBotBase.Enums; using TelegramBotBase.Enums;
using TelegramBotBase.Exceptions; using TelegramBotBase.Exceptions;
using TelegramBotBase.Form; using TelegramBotBase.Form;
@ -32,7 +33,23 @@ namespace TelegramBotBase.Controls.Hybrid
private readonly EventHandlerList Events = new EventHandlerList(); 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);
}
}
/// <summary>
/// Data source of the items.
/// </summary>
public ButtonFormDataSource DataSource { get; set; }
List<int> CheckedRows { get; set; } = new List<int>(); List<int> CheckedRows { get; set; } = new List<int>();
@ -51,6 +68,11 @@ namespace TelegramBotBase.Controls.Hybrid
public bool DeletePreviousMessage { get; set; } = true; public bool DeletePreviousMessage { get; set; } = true;
/// <summary>
/// Removes the reply message from a user.
/// </summary>
public bool DeleteReplyMessage { get; set; } = true;
/// <summary> /// <summary>
/// Parsemode of the message. /// Parsemode of the message.
/// </summary> /// </summary>
@ -92,12 +114,12 @@ namespace TelegramBotBase.Controls.Hybrid
/// <summary> /// <summary>
/// Layout of the buttons which should be displayed always on top. /// Layout of the buttons which should be displayed always on top.
/// </summary> /// </summary>
public List<ButtonBase> HeadLayoutButtonRow { get; set; } public ButtonRow HeadLayoutButtonRow { get; set; }
/// <summary> /// <summary>
/// Layout of columns which should be displayed below the header /// Layout of columns which should be displayed below the header
/// </summary> /// </summary>
public List<ButtonBase> SubHeadLayoutButtonRow { get; set; } public ButtonRow SubHeadLayoutButtonRow { get; set; }
/// <summary> /// <summary>
/// Defines which type of Button Keyboard should be rendered. /// Defines which type of Button Keyboard should be rendered.
@ -126,7 +148,7 @@ namespace TelegramBotBase.Controls.Hybrid
public CheckedButtonList() public CheckedButtonList()
{ {
this.ButtonsForm = new ButtonForm(); this.DataSource = new ButtonFormDataSource();
} }
@ -139,7 +161,7 @@ namespace TelegramBotBase.Controls.Hybrid
public CheckedButtonList(ButtonForm form) public CheckedButtonList(ButtonForm form)
{ {
this.ButtonsForm = form; this.DataSource = new ButtonFormDataSource(form);
} }
public event AsyncEventHandler<ButtonClickedEventArgs> ButtonClicked public event AsyncEventHandler<ButtonClickedEventArgs> ButtonClicked
@ -198,16 +220,50 @@ namespace TelegramBotBase.Controls.Hybrid
if (!result.IsFirstHandler) if (!result.IsFirstHandler)
return; return;
if (result.MessageText == null) if (result.MessageText == null || result.MessageText == "")
return; return;
var button = HeadLayoutButtonRow?.FirstOrDefault(a => a.Text.Trim() == result.MessageText) var matches = new List<ButtonRow>();
?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Text.Trim() == result.MessageText) ButtonRow match = null;
?? ButtonsForm.ToList().FirstOrDefault(a => a.Text.Trim() == result.MessageText); 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) if (result.MessageText == PreviousPageLabel)
{ {
@ -226,7 +282,7 @@ namespace TelegramBotBase.Controls.Hybrid
else if (result.MessageText.EndsWith(CheckedIconLabel)) else if (result.MessageText.EndsWith(CheckedIconLabel))
{ {
var s = result.MessageText.Split(' ', '.'); var s = result.MessageText.Split(' ', '.');
index = int.Parse(s[0]); index = int.Parse(s[0]) - 1;
if (!this.CheckedRows.Contains(index)) if (!this.CheckedRows.Contains(index))
return; return;
@ -241,7 +297,7 @@ namespace TelegramBotBase.Controls.Hybrid
else if (result.MessageText.EndsWith(UncheckedIconLabel)) else if (result.MessageText.EndsWith(UncheckedIconLabel))
{ {
var s = result.MessageText.Split(' ', '.'); var s = result.MessageText.Split(' ', '.');
index = int.Parse(s[0]); index = int.Parse(s[0]) - 1;
if (this.CheckedRows.Contains(index)) if (this.CheckedRows.Contains(index))
return; return;
@ -285,14 +341,18 @@ namespace TelegramBotBase.Controls.Hybrid
} }
await OnButtonClicked(new ButtonClickedEventArgs(button, index)); await OnButtonClicked(new ButtonClickedEventArgs(match.GetButtonMatch(result.MessageText), index, match));
//Remove button click message
if (this.DeletePreviousMessage)
await Device.DeleteMessage(result.MessageId);
result.Handled = true; 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) public async override Task Action(MessageResult result, string value = null)
@ -309,20 +369,55 @@ namespace TelegramBotBase.Controls.Hybrid
await result.ConfirmAction(this.ConfirmationText ?? ""); await result.ConfirmAction(this.ConfirmationText ?? "");
var button = HeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData) ButtonRow match = null;
?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData) int index = -1;
?? ButtonsForm.ToList().FirstOrDefault(a => a.Value == result.RawData);
var index = ButtonsForm.FindRowByButton(button); if (HeadLayoutButtonRow?.Matches(result.RawData, false) ?? false)
if (button != null)
{ {
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; result.Handled = true;
return; return;
} }
//if (button != null)
//{
// await OnButtonClicked(new ButtonClickedEventArgs(button, index));
// result.Handled = true;
// return;
//}
switch (result.RawData) switch (result.RawData)
{ {
case "$previous$": case "$previous$":
@ -396,28 +491,28 @@ namespace TelegramBotBase.Controls.Hybrid
{ {
case eKeyboardType.InlineKeyBoard: 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; break;
case eKeyboardType.ReplyKeyboard: 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; break;
@ -436,7 +531,7 @@ namespace TelegramBotBase.Controls.Hybrid
Message m = null; Message m = null;
ButtonForm form = this.ButtonsForm; ButtonForm form = this.DataSource.PickItems(CurrentPageIndex * ItemRowsPerPage, ItemRowsPerPage, null);
//if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "") //if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "")
//{ //{
@ -444,12 +539,12 @@ namespace TelegramBotBase.Controls.Hybrid
//} //}
//else //else
//{ //{
form = form.Duplicate(); //form = form.Duplicate();
//} //}
if (this.EnablePaging) if (this.EnablePaging)
{ {
form = GeneratePagingView(form); IntegratePagingView(form);
} }
else else
{ {
@ -458,18 +553,18 @@ namespace TelegramBotBase.Controls.Hybrid
if (this.HeadLayoutButtonRow != null && HeadLayoutButtonRow.Count > 0) 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.SubHeadLayoutButtonRow != null && SubHeadLayoutButtonRow.Count > 0)
{ {
if (this.IsNavigationBarVisible) if (this.IsNavigationBarVisible)
{ {
form.InsertButtonRow(2, this.SubHeadLayoutButtonRow); form.InsertButtonRow(2, this.SubHeadLayoutButtonRow.ToArray());
} }
else 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. //Reply Keyboard could only be updated with a new keyboard.
case eKeyboardType.ReplyKeyboard: case eKeyboardType.ReplyKeyboard:
if (this.MessageId != null) if (form.Count == 0)
{ {
if (form.Count == 0) if (this.MessageId != null)
{ {
await this.Device.HideReplyKeyboard(); await this.Device.HideReplyKeyboard();
this.MessageId = null; this.MessageId = null;
return;
} }
if (this.DeletePreviousMessage) return;
await this.Device.DeleteMessage(this.MessageId.Value);
} }
if (form.Count == 0) //if (form.Count == 0)
return; // return;
var rkm = (ReplyKeyboardMarkup)form; var rkm = (ReplyKeyboardMarkup)form;
@ -500,6 +593,10 @@ namespace TelegramBotBase.Controls.Hybrid
rkm.OneTimeKeyboard = this.OneTimeKeyboard; rkm.OneTimeKeyboard = this.OneTimeKeyboard;
m = await this.Device.Send(this.Title, rkm, disableNotification: true, parseMode: MessageParseMode, MarkdownV2AutoEscape: false); 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; break;
case eKeyboardType.InlineKeyBoard: 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(); ButtonForm bf = new ButtonForm();
bf = PrepareCheckableLayout(dataForm); bf = PrepareCheckableLayout(dataForm);
//No Items
if (this.ButtonsForm.Count == 0)
{
bf.AddButtonRow(new ButtonBase(NoItemsLabel, "$"));
}
if (this.IsNavigationBarVisible) if (this.IsNavigationBarVisible)
{ {
//🔍 //🔍
List<ButtonBase> lst = new List<ButtonBase>(); ButtonRow row = new ButtonRow();
lst.Add(new ButtonBase(PreviousPageLabel, "$previous$")); row.Add(new ButtonBase(PreviousPageLabel, "$previous$"));
lst.Add(new ButtonBase(String.Format(Localizations.Default.Language["ButtonGrid_CurrentPage"], this.CurrentPageIndex + 1, this.PageCount), "$site$")); row.Add(new ButtonBase(String.Format(Localizations.Default.Language["ButtonGrid_CurrentPage"], this.CurrentPageIndex + 1, this.PageCount), "$site$"));
lst.Add(new ButtonBase(NextPageLabel, "$next$")); 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) private ButtonForm PrepareCheckableLayout(ButtonForm dataForm)
{ {
var bf = new ButtonForm(); 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; int it = (this.CurrentPageIndex * (this.MaximumRow - LayoutRows)) + i;
if (it > dataForm.Rows - 1) //if (it > dataForm.Rows - 1)
break; // break;
var r = dataForm[it]; var r = dataForm[i];
String s = CheckedRows.Contains(it) ? this.CheckedIconLabel : this.UncheckedIconLabel; String s = CheckedRows.Contains(it) ? this.CheckedIconLabel : this.UncheckedIconLabel;
//On reply keyboards we need a unique text. //On reply keyboards we need a unique text.
if (this.KeyboardType == eKeyboardType.ReplyKeyboard) if (this.KeyboardType == eKeyboardType.ReplyKeyboard)
{ {
s = $"{it}. " + s; s = $"{it + 1}. " + s;
} }
if (CheckedRows.Contains(it)) if (CheckedRows.Contains(it))
@ -652,7 +743,7 @@ namespace TelegramBotBase.Controls.Hybrid
{ {
get get
{ {
return this.LayoutRows + ButtonsForm.Rows; return this.LayoutRows + DataSource.RowCount;
} }
} }
@ -679,24 +770,37 @@ namespace TelegramBotBase.Controls.Hybrid
} }
} }
/// <summary>
/// Returns the number of item rows per page.
/// </summary>
public int ItemRowsPerPage
{
get
{
return this.MaximumRow - this.LayoutRows;
}
}
public int PageCount public int PageCount
{ {
get get
{ {
if (this.ButtonsForm.Count == 0) if (DataSource.RowCount == 0)
return 1; 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 != "") //if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "")
//{ //{
// bf = bf.FilterDuplicate(this.SearchQuery); // bf = bf.FilterDuplicate(this.SearchQuery);
//} //}
if (bf.Rows == 0) if (max == 0)
return 1; return 1;
return (int)Math.Ceiling((decimal)(bf.Rows / (decimal)(MaximumRow - this.LayoutRows))); return (int)Math.Ceiling((decimal)((decimal)max / (decimal)ItemRowsPerPage));
} }
} }

View File

@ -7,9 +7,11 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.Enums;
using Telegram.Bot.Types.InlineQueryResults;
using Telegram.Bot.Types.ReplyMarkups; using Telegram.Bot.Types.ReplyMarkups;
using TelegramBotBase.Args; using TelegramBotBase.Args;
using TelegramBotBase.Base; using TelegramBotBase.Base;
using TelegramBotBase.Datasources;
using TelegramBotBase.Enums; using TelegramBotBase.Enums;
using TelegramBotBase.Exceptions; using TelegramBotBase.Exceptions;
using TelegramBotBase.Form; using TelegramBotBase.Form;
@ -30,7 +32,23 @@ namespace TelegramBotBase.Controls.Hybrid
private readonly EventHandlerList Events = new EventHandlerList(); 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);
}
}
/// <summary>
/// Data source of the items.
/// </summary>
public ButtonFormDataSource DataSource { get; set; }
public int? MessageId { get; set; } public int? MessageId { get; set; }
@ -47,6 +65,11 @@ namespace TelegramBotBase.Controls.Hybrid
public bool DeletePreviousMessage { get; set; } = true; public bool DeletePreviousMessage { get; set; } = true;
/// <summary>
/// Removes the reply message from a user.
/// </summary>
public bool DeleteReplyMessage { get; set; } = true;
/// <summary> /// <summary>
/// Parsemode of the message. /// Parsemode of the message.
/// </summary> /// </summary>
@ -57,6 +80,11 @@ namespace TelegramBotBase.Controls.Hybrid
/// </summary> /// </summary>
public bool EnablePaging { get; set; } = false; public bool EnablePaging { get; set; } = false;
/// <summary>
/// Shows un-/check all tags options
/// </summary>
public bool EnableCheckAllTools { get; set; } = false;
/// <summary> /// <summary>
/// Enabled a search function. /// Enabled a search function.
/// </summary> /// </summary>
@ -82,15 +110,24 @@ namespace TelegramBotBase.Controls.Hybrid
public String BackLabel = Localizations.Default.Language["ButtonGrid_Back"]; public String BackLabel = Localizations.Default.Language["ButtonGrid_Back"];
public String CheckAllLabel = Localizations.Default.Language["ButtonGrid_CheckAll"];
public String UncheckAllLabel = Localizations.Default.Language["ButtonGrid_UncheckAll"];
/// <summary> /// <summary>
/// Layout of the buttons which should be displayed always on top. /// Layout of the buttons which should be displayed always on top.
/// </summary> /// </summary>
public List<ButtonBase> HeadLayoutButtonRow { get; set; } public ButtonRow HeadLayoutButtonRow { get; set; }
/// <summary> /// <summary>
/// Layout of columns which should be displayed below the header /// Layout of columns which should be displayed below the header
/// </summary> /// </summary>
public List<ButtonBase> SubHeadLayoutButtonRow { get; set; } public ButtonRow SubHeadLayoutButtonRow { get; set; }
/// <summary>
/// Layout of columns which should be displayed below the header
/// </summary>
private ButtonRow TagsSubHeadLayoutButtonRow { get; set; }
/// <summary> /// <summary>
/// List of Tags which will be allowed to filter by. /// List of Tags which will be allowed to filter by.
@ -129,7 +166,7 @@ namespace TelegramBotBase.Controls.Hybrid
public TaggedButtonGrid() public TaggedButtonGrid()
{ {
this.ButtonsForm = new ButtonForm(); this.DataSource = new ButtonFormDataSource();
this.SelectedViewIndex = 0; this.SelectedViewIndex = 0;
} }
@ -142,7 +179,7 @@ namespace TelegramBotBase.Controls.Hybrid
public TaggedButtonGrid(ButtonForm form) public TaggedButtonGrid(ButtonForm form)
{ {
this.ButtonsForm = form; this.DataSource = new ButtonFormDataSource(form);
} }
@ -194,30 +231,63 @@ namespace TelegramBotBase.Controls.Hybrid
if (!result.IsFirstHandler) if (!result.IsFirstHandler)
return; return;
if (result.MessageText == null) if (result.MessageText == null || result.MessageText == "")
return; return;
var button = HeadLayoutButtonRow?.FirstOrDefault(a => a.Text.Trim() == result.MessageText) var matches = new List<ButtonRow>();
?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Text.Trim() == result.MessageText) ButtonRow match = null;
?? ButtonsForm.ToList().FirstOrDefault(a => a.Text.Trim() == result.MessageText); 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) switch (this.SelectedViewIndex)
{ {
case 0: case 0:
if (button != null) //Remove button click message
{ if (this.DeleteReplyMessage)
await OnButtonClicked(new ButtonClickedEventArgs(button, index)); await Device.DeleteMessage(result.MessageId);
//Remove button click message if (match != null)
if (this.DeletePreviousMessage) {
await Device.DeleteMessage(result.MessageId); await OnButtonClicked(new ButtonClickedEventArgs(match.GetButtonMatch(result.MessageText), index, match));
result.Handled = true; result.Handled = true;
return; return;
} }
@ -282,17 +352,25 @@ namespace TelegramBotBase.Controls.Hybrid
break; break;
case 1: case 1:
//Remove button click message
if (this.DeleteReplyMessage)
await Device.DeleteMessage(result.MessageId);
if (result.MessageText == this.BackLabel) if (result.MessageText == this.BackLabel)
{ {
//Remove button click message
if (this.DeletePreviousMessage)
await Device.DeleteMessage(result.MessageId);
this.SelectedViewIndex = 0; this.SelectedViewIndex = 0;
this.Updated(); this.Updated();
result.Handled = true; result.Handled = true;
return; return;
} }
else if (result.MessageText == this.CheckAllLabel)
{
this.CheckAllTags();
}
else if (result.MessageText == this.UncheckAllLabel)
{
this.UncheckAllTags();
}
var i = result.MessageText.LastIndexOf(" "); var i = result.MessageText.LastIndexOf(" ");
if (i == -1) if (i == -1)
@ -323,27 +401,60 @@ namespace TelegramBotBase.Controls.Hybrid
public async override Task Action(MessageResult result, string value = null) 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) if (result.Handled)
return; return;
if (!result.IsFirstHandler) if (!result.IsFirstHandler)
return; return;
//Find clicked button depending on Text or Value (depending on markup type)
if (this.KeyboardType != eKeyboardType.InlineKeyBoard)
return;
await result.ConfirmAction(this.ConfirmationText ?? ""); await result.ConfirmAction(this.ConfirmationText ?? "");
var button = HeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData) ButtonRow match = null;
?? SubHeadLayoutButtonRow?.FirstOrDefault(a => a.Value == result.RawData) int index = -1;
?? ButtonsForm.ToList().FirstOrDefault(a => a.Value == result.RawData);
var index = ButtonsForm.FindRowByButton(button); if (HeadLayoutButtonRow?.Matches(result.RawData, false) ?? false)
if (button != null)
{ {
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; result.Handled = true;
return; return;
@ -381,6 +492,18 @@ namespace TelegramBotBase.Controls.Hybrid
this.SelectedViewIndex = 0; this.SelectedViewIndex = 0;
this.Updated(); this.Updated();
break;
case "$checkall$":
this.CheckAllTags();
break;
case "$uncheckall$":
this.UncheckAllTags();
break; break;
} }
@ -395,28 +518,28 @@ namespace TelegramBotBase.Controls.Hybrid
{ {
case eKeyboardType.InlineKeyBoard: 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; break;
case eKeyboardType.ReplyKeyboard: 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; break;
@ -463,16 +586,16 @@ namespace TelegramBotBase.Controls.Hybrid
{ {
Message m = null; 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 != "") //if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "")
{ //{
form = form.FilterDuplicate(this.SearchQuery, true); // form = form.FilterDuplicate(this.SearchQuery, true);
} //}
else //else
{ //{
form = form.Duplicate(); // form = form.Duplicate();
} //}
if (this.Tags != null && this.SelectedTags != null) if (this.Tags != null && this.SelectedTags != null)
{ {
@ -481,23 +604,23 @@ namespace TelegramBotBase.Controls.Hybrid
if (this.EnablePaging) if (this.EnablePaging)
{ {
form = GeneratePagingView(form); IntegratePagingView(form);
} }
if (this.HeadLayoutButtonRow != null && HeadLayoutButtonRow.Count > 0) 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.SubHeadLayoutButtonRow != null && SubHeadLayoutButtonRow.Count > 0)
{ {
if (this.IsNavigationBarVisible) if (this.IsNavigationBarVisible)
{ {
form.InsertButtonRow(2, this.SubHeadLayoutButtonRow); form.InsertButtonRow(2, this.SubHeadLayoutButtonRow.ToArray());
} }
else 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. //Reply Keyboard could only be updated with a new keyboard.
case eKeyboardType.ReplyKeyboard: case eKeyboardType.ReplyKeyboard:
if (this.MessageId != null) if (form.Count == 0)
{ {
if (form.Count == 0) if (this.MessageId != null)
{ {
await this.Device.HideReplyKeyboard(); await this.Device.HideReplyKeyboard();
this.MessageId = null; this.MessageId = null;
return;
} }
if (this.DeletePreviousMessage)
await this.Device.DeleteMessage(this.MessageId.Value);
}
if (form.Count == 0)
return; return;
}
var rkm = (ReplyKeyboardMarkup)form; var rkm = (ReplyKeyboardMarkup)form;
@ -528,6 +645,10 @@ namespace TelegramBotBase.Controls.Hybrid
rkm.OneTimeKeyboard = this.OneTimeKeyboard; rkm.OneTimeKeyboard = this.OneTimeKeyboard;
m = await this.Device.Send(this.Title, rkm, disableNotification: true, parseMode: MessageParseMode, MarkdownV2AutoEscape: false); 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; break;
case eKeyboardType.InlineKeyBoard: 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 //No Items
if (this.ButtonsForm.Count == 0) if (dataForm.Rows == 0)
{ {
bf.AddButtonRow(new ButtonBase(NoItemsLabel, "$")); dataForm.AddButtonRow(new ButtonBase(NoItemsLabel, "$"));
} }
if (this.IsNavigationBarVisible) if (this.IsNavigationBarVisible)
{ {
//🔍 //🔍
List<ButtonBase> lst = new List<ButtonBase>(); ButtonRow row = new ButtonRow();
lst.Add(new ButtonBase(PreviousPageLabel, "$previous$")); row.Add(new ButtonBase(PreviousPageLabel, "$previous$"));
lst.Add(new ButtonBase(String.Format(Localizations.Default.Language["ButtonGrid_CurrentPage"], this.CurrentPageIndex + 1, this.PageCount), "$site$")); 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) 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) 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 #endregion
@ -617,6 +723,12 @@ namespace TelegramBotBase.Controls.Hybrid
bf.AddButtonRow(this.BackLabel, "$back$"); 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) foreach (var t in this.Tags)
{ {
@ -636,21 +748,18 @@ namespace TelegramBotBase.Controls.Hybrid
//Reply Keyboard could only be updated with a new keyboard. //Reply Keyboard could only be updated with a new keyboard.
case eKeyboardType.ReplyKeyboard: case eKeyboardType.ReplyKeyboard:
if (this.MessageId != null) if (bf.Count == 0)
{ {
if (bf.Count == 0) if (this.MessageId != null)
{ {
await this.Device.HideReplyKeyboard(); await this.Device.HideReplyKeyboard();
this.MessageId = null; this.MessageId = null;
return;
} }
return;
if (this.DeletePreviousMessage)
await this.Device.DeleteMessage(this.MessageId.Value);
} }
if (bf.Count == 0) //if (bf.Count == 0)
return; // return;
var rkm = (ReplyKeyboardMarkup)bf; var rkm = (ReplyKeyboardMarkup)bf;
@ -658,6 +767,10 @@ namespace TelegramBotBase.Controls.Hybrid
rkm.OneTimeKeyboard = this.OneTimeKeyboard; rkm.OneTimeKeyboard = this.OneTimeKeyboard;
m = await this.Device.Send("Choose category", rkm, disableNotification: true, parseMode: MessageParseMode, MarkdownV2AutoEscape: false); 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; break;
case eKeyboardType.InlineKeyBoard: case eKeyboardType.InlineKeyBoard:
@ -744,7 +857,7 @@ namespace TelegramBotBase.Controls.Hybrid
{ {
get 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) if (this.SubHeadLayoutButtonRow != null && this.SubHeadLayoutButtonRow.Count > 0)
layoutRows++; layoutRows++;
if (EnableCheckAllTools && this.SelectedViewIndex == 1)
{
layoutRows++;
}
return layoutRows; return layoutRows;
} }
} }
/// <summary>
/// Returns the number of item rows per page.
/// </summary>
public int ItemRowsPerPage
{
get
{
return this.MaximumRow - this.LayoutRows;
}
}
public int PageCount public int PageCount
{ {
get get
{ {
if (this.ButtonsForm.Count == 0) if (DataSource.RowCount == 0)
return 1; return 1;
var bf = this.ButtonsForm; //var bf = this.DataSource.PickAllItems(this.EnableSearch ? this.SearchQuery : null);
if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "") var max = this.DataSource.CalculateMax(this.EnableSearch ? this.SearchQuery : null);
{
bf = bf.FilterDuplicate(this.SearchQuery);
}
if (bf.Rows == 0) //if (this.EnableSearch && this.SearchQuery != null && this.SearchQuery != "")
//{
// bf = bf.FilterDuplicate(this.SearchQuery);
//}
if (max == 0)
return 1; 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; break;
} }
}
/// <summary>
/// Checks all tags for filtering.
/// </summary>
public void CheckAllTags()
{
this.SelectedTags.Clear();
this.SelectedTags = this.Tags.Select(a => a).ToList();
this.Updated();
}
/// <summary>
/// Unchecks all tags for filtering.
/// </summary>
public void UncheckAllTags()
{
this.SelectedTags.Clear();
this.Updated();
} }
} }

View File

@ -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<ButtonRow>
{
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;
}
/// <summary>
/// Returns the amount of rows exisiting.
/// </summary>
/// <returns></returns>
public virtual int Count => ButtonForm.Count;
/// <summary>
/// Returns the amount of rows.
/// </summary>
public virtual int RowCount => ButtonForm.Rows;
/// <summary>
/// Returns the maximum amount of columns.
/// </summary>
public virtual int ColumnCount => ButtonForm.Cols;
/// <summary>
/// Returns the row with the specific index.
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public virtual ButtonRow ItemAt(int index)
{
return ButtonForm[index];
}
public virtual List<ButtonRow> ItemRange(int start, int count)
{
return ButtonForm.GetRange(start, count);
}
public virtual List<ButtonRow> 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<ButtonRow, int> FindRow(String text, bool useText = true)
{
return ButtonForm.FindRow(text, useText);
}
/// <summary>
/// Returns the maximum items of this data source.
/// </summary>
/// <param name="filter"></param>
/// <returns></returns>
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;
}
}
}

View File

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TelegramBotBase.Datasources
{
public class StaticDataSource<T> : Interfaces.IDataSource<T>
{
List<T> Data { get; set; }
public StaticDataSource()
{
}
public StaticDataSource(List<T> data)
{
this.Data = data;
}
public int Count
{
get
{
return Data.Count;
}
}
public T ItemAt(int index)
{
return Data[index];
}
public List<T> ItemRange(int start, int count)
{
return Data.Skip(start).Take(count).ToList();
}
public List<T> AllItems()
{
return Data;
}
}
}

View File

@ -1,8 +1,10 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Telegram.Bot.Exceptions;
using Telegram.Bot.Types; using Telegram.Bot.Types;
using TelegramBotBase.Args; using TelegramBotBase.Args;
using TelegramBotBase.Attributes; using TelegramBotBase.Attributes;
@ -127,25 +129,79 @@ namespace TelegramBotBase.Form
/// <returns></returns> /// <returns></returns>
public async Task MessageCleanup() public async Task MessageCleanup()
{ {
while (this.OldMessages.Count > 0) var oldMessages = OldMessages.AsEnumerable();
#if !NET472
while (oldMessages.Any())
{ {
var tasks = new List<Task>(); using var cts = new CancellationTokenSource();
var msgs = this.OldMessages.Take(Constants.Telegram.MessageDeletionsPerSecond); var deletedMessages = new ConcurrentBag<int>();
var parallelQuery = OldMessages.AsParallel()
foreach (var msg in msgs) .WithCancellation(cts.Token);
Task retryAfterTask = null;
try
{ {
tasks.Add(this.Device.DeleteMessage(msg)); parallelQuery.ForAll(i =>
{
Device.DeleteMessage(i).GetAwaiter().GetResult();
deletedMessages.Add(i);
});
}
catch (AggregateException ex)
{
cts.Cancel();
var retryAfterSeconds = ex.InnerExceptions
.Where(e => e is ApiRequestException apiEx && apiEx.ErrorCode == 429)
.Max(e =>(int?) ((ApiRequestException)e).Parameters.RetryAfter) ?? 0;
retryAfterTask = Task.Delay(retryAfterSeconds * 1000);
} }
await Task.WhenAll(tasks); deletedMessages.AsParallel().ForAll(i => Device.OnMessageDeleted(new MessageDeletedEventArgs(i)));
foreach(var m in msgs) oldMessages = oldMessages.Where(x => !deletedMessages.Contains(x));
{ if (retryAfterTask != null)
Device.OnMessageDeleted(new MessageDeletedEventArgs(m)); await retryAfterTask;
}
this.OldMessages.RemoveRange(0, msgs.Count());
} }
#else
while (oldMessages.Any())
{
using (var cts = new CancellationTokenSource())
{
var deletedMessages = new ConcurrentBag<int>();
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();
} }
} }
} }

View File

@ -5,6 +5,7 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Telegram.Bot.Types.ReplyMarkups; using Telegram.Bot.Types.ReplyMarkups;
using TelegramBotBase.Base; using TelegramBotBase.Base;
using TelegramBotBase.Controls.Hybrid;
namespace TelegramBotBase.Form namespace TelegramBotBase.Form
{ {
@ -13,7 +14,7 @@ namespace TelegramBotBase.Form
/// </summary> /// </summary>
public class ButtonForm public class ButtonForm
{ {
List<List<ButtonBase>> Buttons = new List<List<ButtonBase>>(); List<ButtonRow> Buttons = new List<ButtonRow>();
public IReplyMarkup Markup { get; set; } public IReplyMarkup Markup { get; set; }
@ -43,7 +44,7 @@ namespace TelegramBotBase.Form
} }
public List<ButtonBase> this[int row] public ButtonRow this[int row]
{ {
get get
{ {
@ -66,9 +67,14 @@ namespace TelegramBotBase.Form
Buttons.Add(new List<ButtonBase>() { new ButtonBase(Text, Value, Url) }); Buttons.Add(new List<ButtonBase>() { new ButtonBase(Text, Value, Url) });
} }
public void AddButtonRow(IEnumerable<ButtonBase> 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) public void AddButtonRow(params ButtonBase[] row)
@ -76,16 +82,26 @@ namespace TelegramBotBase.Form
AddButtonRow(row.ToList()); AddButtonRow(row.ToList());
} }
public void AddButtonRows(IEnumerable<ButtonRow> rows)
{
Buttons.AddRange(rows);
}
public void InsertButtonRow(int index, IEnumerable<ButtonBase> row) public void InsertButtonRow(int index, IEnumerable<ButtonBase> row)
{ {
Buttons.Insert(index, row.ToList()); 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<T>(IEnumerable<T> items, int itemsPerRow = 2) public static T[][] SplitTo<T>(IEnumerable<T> items, int itemsPerRow = 2)
{ {
T[][] splitted = default(T[][]); T[][] splitted = default(T[][]);
@ -132,34 +148,62 @@ namespace TelegramBotBase.Form
} }
} }
/// <summary>
/// Returns a range of rows from the buttons.
/// </summary>
/// <param name="start"></param>
/// <param name="count"></param>
/// <returns></returns>
public List<ButtonRow> GetRange(int start, int count)
{
return Buttons.Skip(start).Take(count).ToList();
}
public List<ButtonBase> ToList() public List<ButtonBase> ToList()
{ {
return this.Buttons.DefaultIfEmpty(new List<ButtonBase>()).Aggregate((a, b) => a.Union(b).ToList()); return this.Buttons.DefaultIfEmpty(new List<ButtonBase>()).Select(a => a.ToList()).Aggregate((a, b) => a.Union(b).ToList());
} }
public InlineKeyboardButton[][] ToInlineButtonArray() 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; return ikb;
} }
public KeyboardButton[][] ToReplyButtonArray() 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; return ikb;
} }
public List<ButtonRow> ToArray()
{
return Buttons;
}
public int FindRowByButton(ButtonBase button) 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) if (row == null)
return -1; return -1;
return this.Buttons.IndexOf(row); return this.Buttons.IndexOf(row);
} }
public Tuple<ButtonRow, int> 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<ButtonRow, int>(r, i);
}
/// <summary> /// <summary>
/// Returns the first Button with the given value. /// Returns the first Button with the given value.
/// </summary> /// </summary>
@ -204,7 +248,7 @@ namespace TelegramBotBase.Form
foreach (var b in Buttons) foreach (var b in Buttons)
{ {
var lst = new List<ButtonBase>(); var lst = new ButtonRow();
foreach (var b2 in b) foreach (var b2 in b)
{ {
lst.Add(b2); lst.Add(b2);
@ -229,7 +273,7 @@ namespace TelegramBotBase.Form
foreach (var b in Buttons) foreach (var b in Buttons)
{ {
var lst = new List<ButtonBase>(); var lst = new ButtonRow();
foreach (var b2 in b) foreach (var b2 in b)
{ {
if (b2.Text.IndexOf(filter, StringComparison.InvariantCultureIgnoreCase) == -1) 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. //Copy full row, when at least one match has found.
if (ByRow) if (ByRow)
{ {
lst.AddRange(b); lst = b;
break; break;
} }
else else
@ -268,7 +312,7 @@ namespace TelegramBotBase.Form
foreach (var b in Buttons) foreach (var b in Buttons)
{ {
var lst = new List<ButtonBase>(); var lst = new ButtonRow();
foreach (var b2 in b) foreach (var b2 in b)
{ {
if (!(b2 is TagButtonBase tb)) if (!(b2 is TagButtonBase tb))
@ -280,7 +324,7 @@ namespace TelegramBotBase.Form
//Copy full row, when at least one match has found. //Copy full row, when at least one match has found.
if (ByRow) if (ByRow)
{ {
lst.AddRange(b); lst = b;
break; break;
} }
else else

View File

@ -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<FormBase> History { get; set; }
[SaveState]
public int Index { get; set; }
/// <summary>
/// Will replace the controller when poping a form to the root form.
/// </summary>
[SaveState]
public bool ForceCleanupOnLastPop { get; set; }
public NavigationController()
{
History = new List<FormBase>();
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);
}
/// <summary>
/// Remove the current active form on the stack.
/// </summary>
/// <returns></returns>
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());
}
}
/// <summary>
/// Pop's through all forms back to the root form.
/// </summary>
/// <returns></returns>
public virtual async Task PopToRootAsync()
{
while (Index > 0)
{
await PopAsync();
}
}
/// <summary>
/// Pushing the given form to the stack and renders it.
/// </summary>
/// <param name="form"></param>
/// <returns></returns>
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());
}
/// <summary>
/// Pops the current form and pushes a new one.
/// Will help to remove forms so you can not navigate back to them.
/// </summary>
/// <param name="form"></param>
/// <param name="args"></param>
/// <returns></returns>
public virtual async Task PushAndReplaceAsync(FormBase form, params object[] args)
{
await PopAsync();
await PushAsync(form, args);
}
/// <summary>
/// Returns the current form from the stack.
/// </summary>
public FormBase CurrentForm
{
get
{
if (this.History.Count == 0)
return null;
return this.History[Index];
}
}
public List<FormBase> 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<String, object>;
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<String, object>();
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
}
}

View File

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Text;
using TelegramBotBase.Controls.Hybrid;
namespace TelegramBotBase.Interfaces
{
public interface IDataSource<T>
{
/// <summary>
/// Returns the amount of items within this source.
/// </summary>
/// <returns></returns>
int Count { get; }
/// <summary>
/// Returns the item at the specific index.
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
T ItemAt(int index);
/// <summary>
/// Get all items from this source within this range.
/// </summary>
/// <param name="start"></param>
/// <param name="count"></param>
/// <returns></returns>
List<T> ItemRange(int start, int count);
/// <summary>
/// Gets a list of all items of this datasource.
/// </summary>
/// <returns></returns>
List<T> AllItems();
}
}

View File

@ -16,6 +16,8 @@ namespace TelegramBotBase.Localizations
Values["ButtonGrid_CurrentPage"] = "Page {0} of {1}"; 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_SearchFeature"] = "💡 Send a message to filter the list. Click the 🔍 to reset the filter.";
Values["ButtonGrid_Back"] = "Back"; Values["ButtonGrid_Back"] = "Back";
Values["ButtonGrid_CheckAll"] = "Check all";
Values["ButtonGrid_UncheckAll"] = "Uncheck all";
Values["CalendarPicker_Title"] = "Pick date"; Values["CalendarPicker_Title"] = "Pick date";
Values["CalendarPicker_PreviousPage"] = "◀️"; Values["CalendarPicker_PreviousPage"] = "◀️";
Values["CalendarPicker_NextPage"] = "▶️"; Values["CalendarPicker_NextPage"] = "▶️";

View File

@ -8,7 +8,32 @@ namespace TelegramBotBase.Localizations
{ {
public German() : base() 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";
} }

View File

@ -4,7 +4,7 @@ using System.Text;
namespace TelegramBotBase.Localizations namespace TelegramBotBase.Localizations
{ {
public class Localization public abstract class Localization
{ {
public Dictionary<String, String> Values = new Dictionary<string, string>(); public Dictionary<String, String> Values = new Dictionary<string, string>();
@ -18,30 +18,7 @@ namespace TelegramBotBase.Localizations
public Localization() 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";
} }

View File

@ -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 //Is Subclass of IStateForm
var iform = form as IStateForm; var iform = form as IStateForm;
if (iform != null) if (iform != null)
@ -217,14 +224,6 @@ namespace TelegramBotBase
iform.LoadState(ls); iform.LoadState(ls);
} }
form.Client = Client;
var device = new DeviceSession(s.DeviceId, form);
device.ChatTitle = s.ChatTitle;
this.SessionList.Add(s.DeviceId, device);
try try
{ {
await form.OnInit(new InitEventArgs()); await form.OnInit(new InitEventArgs());

View File

@ -7,7 +7,7 @@
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> <PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageProjectUrl>https://github.com/MajMcCloud/TelegramBotFramework</PackageProjectUrl> <PackageProjectUrl>https://github.com/MajMcCloud/TelegramBotFramework</PackageProjectUrl>
<RepositoryUrl>https://github.com/MajMcCloud/TelegramBotFramework</RepositoryUrl> <RepositoryUrl>https://github.com/MajMcCloud/TelegramBotFramework</RepositoryUrl>
<PackageReleaseNotes>- moving from .Net Framework 4.7.2 to .Net Standard 2.1 for the Library and .Net Core 3.1 for the test project!</PackageReleaseNotes> <PackageReleaseNotes>- moving from .Net Framework 4.7.2 to .Net Standard 2.1 for the Library and .Net Core 3.1 for the test project!</PackageReleaseNotes>
<Configurations>Debug;Release;</Configurations> <Configurations>Debug;Release;</Configurations>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
<RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild> <RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild>
@ -62,27 +62,9 @@
<None Remove="Archive\**" /> <None Remove="Archive\**" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Remove="cpack.ps1" />
<None Remove="cpack_bak.ps1" />
<None Remove="cpush.ps1" />
<None Remove="nuget.exe" />
<None Remove="TelegramBotBase.1.5.0.nupkg" />
<None Remove="TelegramBotBase.1.5.0.zip" />
<None Remove="TelegramBotBase.1.5.1.nupkg" />
<None Remove="TelegramBotBase.1.5.1.zip" />
<None Remove="TelegramBotBase.1.5.2.nupkg" />
<None Remove="TelegramBotBase.1.5.2.zip" />
<None Remove="TelegramBotBase.2.0.0.nupkg" />
<None Remove="TelegramBotBase.2.1.0.nupkg" />
<None Remove="TelegramBotBase.2.1.0.zip" />
<None Remove="TelegramBotBase.3.0.0.nupkg" />
<None Remove="TelegramBotBase.3.0.0.zip" />
<None Remove="TelegramBotBase.nuspec" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="System.ComponentModel" Version="4.3.0" /> <PackageReference Include="System.ComponentModel" Version="4.3.0" />
<PackageReference Include="System.Drawing.Common" Version="5.0.2" /> <PackageReference Include="System.Drawing.Common" Version="5.0.2" />
<PackageReference Include="Telegram.Bot" Version="16.0.2" /> <PackageReference Include="Telegram.Bot" Version="16.0.2" />

View File

@ -7,7 +7,7 @@ using Telegram.Bot.Types;
using TelegramBotBase; using TelegramBotBase;
using TelegramBotBase.Form; using TelegramBotBase.Form;
using TelegramBotBaseTest.Tests; using TelegramBotBaseTest.Tests;
using TelegramBotBase.Commands;
namespace TelegramBotBaseTest namespace TelegramBotBaseTest
{ {
class Program class Program
@ -17,7 +17,9 @@ namespace TelegramBotBaseTest
BotBase<Start> bb = new BotBase<Start>(APIKey); BotBase<Start> bb = new BotBase<Start>(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 = "form1", Description = "Opens test form 1" });
bb.BotCommands.Add(new BotCommand() { Command = "form2", Description = "Opens test form 2" }); 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." }); bb.BotCommands.Add(new BotCommand() { Command = "params", Description = "Returns all send parameters as a message." });

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp3.0;net472</TargetFrameworks> <TargetFrameworks>netcoreapp3.1;net5</TargetFrameworks>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
</PropertyGroup> </PropertyGroup>

View File

@ -33,6 +33,7 @@ namespace TelegramBotBaseTest.Tests.Controls
m_Buttons.HeadLayoutButtonRow = new List<ButtonBase>() { new ButtonBase("Back", "back") }; m_Buttons.HeadLayoutButtonRow = new List<ButtonBase>() { new ButtonBase("Back", "back") };
var countries = CultureInfo.GetCultures(CultureTypes.SpecificCultures); var countries = CultureInfo.GetCultures(CultureTypes.SpecificCultures);
ButtonForm bf = new ButtonForm(); ButtonForm bf = new ButtonForm();
@ -45,7 +46,9 @@ namespace TelegramBotBaseTest.Tests.Controls
m_Buttons.Tags = countries.Select(a => a.Parent.EnglishName).Distinct().OrderBy(a => a).ToList(); 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.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; m_Buttons.ButtonClicked += Bg_ButtonClicked;
@ -57,11 +60,13 @@ namespace TelegramBotBaseTest.Tests.Controls
if (e.Button == null) if (e.Button == null)
return; return;
if (e.Button.Value == "back") switch (e.Button.Value)
{ {
var start = new Menu();
await this.NavigateTo(start); case "back":
return; var start = new Menu();
await this.NavigateTo(start);
return;
} }

View File

@ -35,12 +35,12 @@ namespace TelegramBotBaseTest.Tests.Controls
ButtonForm bf = new ButtonForm(); 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.ButtonClicked += Bg_ButtonClicked;
m_Buttons.CheckedChanged += M_Buttons_CheckedChanged; m_Buttons.CheckedChanged += M_Buttons_CheckedChanged;
@ -58,29 +58,32 @@ namespace TelegramBotBaseTest.Tests.Controls
if (e.Button == null) if (e.Button == null)
return; return;
if (e.Button.Value == "back") switch (e.Button.Value)
{ {
var start = new Menu(); case "back":
await this.NavigateTo(start);
} var start = new Menu();
else if (e.Button.Value == "switch") await NavigateTo(start);
{ break;
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 "switch":
else 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;
} }

View File

@ -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<String> Countries = new List<string>() { "Country 1", "Country 2", "Country 3" };
public CustomDataSource()
{
loadData();
}
/// <summary>
/// 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.
/// </summary>
private void loadData()
{
//Exists data source? Read it
if (File.Exists(AppContext.BaseDirectory + "countries.json"))
{
try
{
var List = Newtonsoft.Json.JsonConvert.DeserializeObject<List<String>>(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<ButtonRow> ItemRange(int start, int count)
{
var items = Countries.Skip(start).Take(count);
List<ButtonRow> lst = new List<ButtonRow>();
foreach (var c in items)
{
lst.Add(Render(c));
}
return lst;
}
public override List<ButtonRow> AllItems()
{
List<ButtonRow> lst = new List<ButtonRow>();
foreach (var c in Countries)
{
lst.Add(Render(c));
}
return lst;
}
public override ButtonForm PickItems(int start, int count, string filter = null)
{
List<ButtonRow> 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<ButtonRow> 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;
}
}
}
}

View File

@ -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)
{
}
}
}

View File

@ -41,7 +41,9 @@ namespace TelegramBotBaseTest.Tests
if (call == null) if (call == null)
return; return;
switch(call.Value) message.Handled = true;
switch (call.Value)
{ {
case "text": case "text":
@ -93,8 +95,6 @@ namespace TelegramBotBaseTest.Tests
case "data": case "data":
message.Handled = true;
var data = new DataForm(); var data = new DataForm();
await this.NavigateTo(data); await this.NavigateTo(data);
@ -103,8 +103,6 @@ namespace TelegramBotBaseTest.Tests
case "calendar": case "calendar":
message.Handled = true;
var calendar = new Controls.CalendarPickerForm(); var calendar = new Controls.CalendarPickerForm();
await this.NavigateTo(calendar); await this.NavigateTo(calendar);
@ -113,8 +111,6 @@ namespace TelegramBotBaseTest.Tests
case "month": case "month":
message.Handled = true;
var month = new Controls.MonthPickerForm(); var month = new Controls.MonthPickerForm();
await this.NavigateTo(month); await this.NavigateTo(month);
@ -123,8 +119,6 @@ namespace TelegramBotBaseTest.Tests
case "treeview": case "treeview":
message.Handled = true;
var tree = new Controls.TreeViewForms(); var tree = new Controls.TreeViewForms();
await this.NavigateTo(tree); await this.NavigateTo(tree);
@ -133,8 +127,6 @@ namespace TelegramBotBaseTest.Tests
case "togglebuttons": case "togglebuttons":
message.Handled = true;
var tb = new Controls.ToggleButtons(); var tb = new Controls.ToggleButtons();
await this.NavigateTo(tb); await this.NavigateTo(tb);
@ -143,8 +135,6 @@ namespace TelegramBotBaseTest.Tests
case "multitogglebuttons": case "multitogglebuttons":
message.Handled = true;
var mtb = new Controls.MultiToggleButtons(); var mtb = new Controls.MultiToggleButtons();
await this.NavigateTo(mtb); await this.NavigateTo(mtb);
@ -153,8 +143,6 @@ namespace TelegramBotBaseTest.Tests
case "buttongrid": case "buttongrid":
message.Handled = true;
var bg = new Controls.ButtonGridForm(); var bg = new Controls.ButtonGridForm();
await this.NavigateTo(bg); await this.NavigateTo(bg);
@ -163,8 +151,6 @@ namespace TelegramBotBaseTest.Tests
case "buttongridfilter": case "buttongridfilter":
message.Handled = true;
var bg2 = new Controls.ButtonGridPagingForm(); var bg2 = new Controls.ButtonGridPagingForm();
await this.NavigateTo(bg2); await this.NavigateTo(bg2);
@ -173,8 +159,6 @@ namespace TelegramBotBaseTest.Tests
case "buttongridtags": case "buttongridtags":
message.Handled = true;
var bg3 = new Controls.ButtonGridTagForm(); var bg3 = new Controls.ButtonGridTagForm();
await this.NavigateTo(bg3); await this.NavigateTo(bg3);
@ -183,8 +167,6 @@ namespace TelegramBotBaseTest.Tests
case "multiview": case "multiview":
message.Handled = true;
var mvf = new MultiViewForm(); var mvf = new MultiViewForm();
await NavigateTo(mvf); await NavigateTo(mvf);
@ -194,13 +176,41 @@ namespace TelegramBotBaseTest.Tests
case "checkedbuttonlist": case "checkedbuttonlist":
message.Handled = true;
var cbl = new CheckedButtonListForm(); var cbl = new CheckedButtonListForm();
await NavigateTo(cbl); 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; break;
} }
@ -240,6 +250,12 @@ namespace TelegramBotBaseTest.Tests
btn.AddButtonRow(new ButtonBase("#16 CheckedButtonList", new CallbackData("a", "checkedbuttonlist").Serialize())); 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); await this.Device.Send("Choose your test:", btn);
} }

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net452" /> <package id="Newtonsoft.Json" version="12.0.2" targetFramework="net5" />
<package id="System.Net.Requests" version="4.3.0" targetFramework="net452" /> <package id="System.Net.Requests" version="4.3.0" targetFramework="net5" />
<package id="Telegram.Bot" version="14.12.0" targetFramework="net452" /> <package id="Telegram.Bot" version="14.12.0" targetFramework="net5" />
</packages> </packages>