from datetime import datetime, time, timedelta from aiogram import Router, F from aiogram.fsm.context import FSMContext from aiogram.types import Message, CallbackQuery, InlineKeyboardButton from aiogram.utils.keyboard import InlineKeyboardBuilder from logging import getLogger from typing import TYPE_CHECKING from ....model.descriptors import FieldDescriptor from ....model.settings import Settings from ..context import ContextData, CallbackCommand from ....utils.main import get_send_message, get_field_descriptor from .wrapper import wrap_editor if TYPE_CHECKING: from ....main import QBotApp logger = getLogger(__name__) router = Router() @router.callback_query(ContextData.filter(F.command == CallbackCommand.TIME_PICKER)) async def time_picker_callback( query: CallbackQuery, callback_data: ContextData, app: "QBotApp", **kwargs ): if not callback_data.data: return field_descriptor = get_field_descriptor(app, callback_data) state: FSMContext = kwargs["state"] state_data = await state.get_data() kwargs["state_data"] = state_data await time_picker( query.message, field_descriptor=field_descriptor, callback_data=callback_data, current_value=datetime.strptime(callback_data.data, "%Y-%m-%d %H-%M") if len(callback_data.data) > 10 else time.fromisoformat(callback_data.data.replace("-", ":")), **kwargs, ) async def time_picker( message: Message | CallbackQuery, field_descriptor: FieldDescriptor, callback_data: ContextData, current_value: datetime | time, state: FSMContext, edit_prompt: str | None = None, **kwargs, ): keyboard_builder = InlineKeyboardBuilder() if not current_value: current_value = time(0, 0) for i in range(12): keyboard_builder.row( InlineKeyboardButton( text=( "▶︎ {v:02d} ◀︎" if i == (current_value.hour % 12) else "{v:02d}" ).format(v=i if current_value.hour < 12 else i + 12), callback_data=ContextData( command=CallbackCommand.TIME_PICKER, context=callback_data.context, entity_name=callback_data.entity_name, entity_id=callback_data.entity_id, field_name=callback_data.field_name, form_params=callback_data.form_params, user_command=callback_data.user_command, data=current_value.replace( hour=i if current_value.hour < 12 else i + 12 ).strftime( "%Y-%m-%d %H-%M" if isinstance(current_value, datetime) else "%H-%M" ) if i != current_value.hour % 12 else None, ).pack(), ), InlineKeyboardButton( text=( "▶︎ {v:02d} ◀︎" if i == current_value.minute // 5 else "{v:02d}" ).format(v=i * 5), callback_data=ContextData( command=CallbackCommand.TIME_PICKER, context=callback_data.context, entity_name=callback_data.entity_name, entity_id=callback_data.entity_id, field_name=callback_data.field_name, form_params=callback_data.form_params, user_command=callback_data.user_command, data=current_value.replace(minute=i * 5).strftime( "%Y-%m-%d %H-%M" if isinstance(current_value, datetime) else "%H-%M" ) if i != current_value.minute // 5 else None, ).pack(), ), ) keyboard_builder.row( InlineKeyboardButton( text="AM/PM", callback_data=ContextData( command=CallbackCommand.TIME_PICKER, context=callback_data.context, entity_name=callback_data.entity_name, entity_id=callback_data.entity_id, field_name=callback_data.field_name, form_params=callback_data.form_params, user_command=callback_data.user_command, data=current_value.replace( hour=current_value.hour + 12 if current_value.hour < 12 else current_value.hour - 12 ).strftime( "%Y-%m-%d %H-%M" if isinstance(current_value, datetime) else "%H-%M" ), ).pack(), ), InlineKeyboardButton( text=await Settings.get(Settings.APP_STRINGS_DONE_BTN), callback_data=ContextData( command=CallbackCommand.FIELD_EDITOR_CALLBACK, context=callback_data.context, entity_name=callback_data.entity_name, entity_id=callback_data.entity_id, field_name=callback_data.field_name, form_params=callback_data.form_params, user_command=callback_data.user_command, data=current_value.strftime( "%Y-%m-%d %H-%M" if isinstance(current_value, datetime) else "%H-%M" ), ).pack(), ), ) state_data = kwargs["state_data"] await wrap_editor( keyboard_builder=keyboard_builder, field_descriptor=field_descriptor, callback_data=callback_data, state_data=state_data, ) await state.set_data(state_data) if edit_prompt: send_message = get_send_message(message) await send_message(text=edit_prompt, reply_markup=keyboard_builder.as_markup()) else: await message.edit_reply_markup(reply_markup=keyboard_builder.as_markup()) async def date_picker( message: Message | CallbackQuery, field_descriptor: FieldDescriptor, callback_data: ContextData, current_value: datetime, state: FSMContext, edit_prompt: str | None = None, **kwargs, ): if not current_value: start_date = datetime.now() else: start_date = current_value start_date = current_value.replace(day=1) previous_month = start_date - timedelta(days=1) next_month = start_date.replace(day=28) + timedelta(days=4) keyboard_builder = InlineKeyboardBuilder() keyboard_builder.row( InlineKeyboardButton( text="◀️", callback_data=ContextData( command=CallbackCommand.DATE_PICKER_MONTH, context=callback_data.context, entity_name=callback_data.entity_name, entity_id=callback_data.entity_id, field_name=callback_data.field_name, form_params=callback_data.form_params, user_command=callback_data.user_command, data=previous_month.strftime("%Y-%m-%d %H-%M"), ).pack(), ), InlineKeyboardButton( text=start_date.strftime("%b %Y"), callback_data=ContextData( command=CallbackCommand.DATE_PICKER_YEAR, context=callback_data.context, entity_name=callback_data.entity_name, entity_id=callback_data.entity_id, field_name=callback_data.field_name, form_params=callback_data.form_params, user_command=callback_data.user_command, data=start_date.strftime("%Y-%m-%d %H-%M"), ).pack(), ), InlineKeyboardButton( text="▶️", callback_data=ContextData( command=CallbackCommand.DATE_PICKER_MONTH, context=callback_data.context, entity_name=callback_data.entity_name, entity_id=callback_data.entity_id, field_name=callback_data.field_name, form_params=callback_data.form_params, user_command=callback_data.user_command, data=next_month.strftime("%Y-%m-%d %H-%M"), ).pack(), ), ) first_day = start_date - timedelta(days=start_date.weekday()) weeks = ( ( (start_date.replace(day=28) + timedelta(days=4)).replace(day=1) - first_day ).days - 1 ) // 7 + 1 for week in range(weeks): buttons = [] for day in range(7): current_day = first_day + timedelta(days=week * 7 + day) buttons.append( InlineKeyboardButton( text=current_day.strftime("%d"), callback_data=ContextData( command=CallbackCommand.FIELD_EDITOR_CALLBACK if field_descriptor.dt_type == "date" else CallbackCommand.TIME_PICKER, context=callback_data.context, entity_name=callback_data.entity_name, entity_id=callback_data.entity_id, field_name=callback_data.field_name, form_params=callback_data.form_params, user_command=callback_data.user_command, data=current_day.strftime("%Y-%m-%d %H-%M"), ).pack(), ) ) keyboard_builder.row(*buttons) state_data = kwargs["state_data"] await wrap_editor( keyboard_builder=keyboard_builder, field_descriptor=field_descriptor, callback_data=callback_data, state_data=state_data, ) await state.set_data(state_data) if edit_prompt: send_message = get_send_message(message) await send_message(text=edit_prompt, reply_markup=keyboard_builder.as_markup()) else: await message.edit_reply_markup(reply_markup=keyboard_builder.as_markup()) @router.callback_query( ContextData.filter(F.command == CallbackCommand.DATE_PICKER_YEAR) ) async def date_picker_year( query: CallbackQuery, callback_data: ContextData, app: "QBotApp", state: FSMContext, **kwargs, ): start_date = datetime.strptime(callback_data.data, "%Y-%m-%d %H-%M") state_data = await state.get_data() kwargs["state_data"] = state_data keyboard_builder = InlineKeyboardBuilder() keyboard_builder.row( InlineKeyboardButton( text="🔼", callback_data=ContextData( command=CallbackCommand.DATE_PICKER_YEAR, context=callback_data.context, entity_name=callback_data.entity_name, entity_id=callback_data.entity_id, field_name=callback_data.field_name, form_params=callback_data.form_params, user_command=callback_data.user_command, data=start_date.replace(year=start_date.year - 20).strftime( "%Y-%m-%d %H-%M" ), ).pack(), ) ) for r in range(4): buttons = [] for c in range(5): current_date = start_date.replace(year=start_date.year + r * 5 + c - 10) buttons.append( InlineKeyboardButton( text=current_date.strftime("%Y"), callback_data=ContextData( command=CallbackCommand.DATE_PICKER_MONTH, context=callback_data.context, entity_name=callback_data.entity_name, entity_id=callback_data.entity_id, field_name=callback_data.field_name, form_params=callback_data.form_params, user_command=callback_data.user_command, data=current_date.strftime("%Y-%m-%d %H-%M"), ).pack(), ) ) keyboard_builder.row(*buttons) keyboard_builder.row( InlineKeyboardButton( text="🔽", callback_data=ContextData( command=CallbackCommand.DATE_PICKER_YEAR, context=callback_data.context, entity_name=callback_data.entity_name, entity_id=callback_data.entity_id, field_name=callback_data.field_name, form_params=callback_data.form_params, user_command=callback_data.user_command, data=start_date.replace(year=start_date.year + 20).strftime( "%Y-%m-%d %H-%M" ), ).pack(), ) ) field_descriptor = get_field_descriptor(app, callback_data) await wrap_editor( keyboard_builder=keyboard_builder, field_descriptor=field_descriptor, callback_data=callback_data, state_data=state_data, ) await query.message.edit_reply_markup(reply_markup=keyboard_builder.as_markup()) @router.callback_query( ContextData.filter(F.command == CallbackCommand.DATE_PICKER_MONTH) ) async def date_picker_month( query: CallbackQuery, callback_data: ContextData, app: "QBotApp", **kwargs ): field_descriptor = get_field_descriptor(app, callback_data) state: FSMContext = kwargs["state"] state_data = await state.get_data() kwargs["state_data"] = state_data await date_picker( query.message, field_descriptor=field_descriptor, callback_data=callback_data, current_value=datetime.strptime(callback_data.data, "%Y-%m-%d %H-%M"), **kwargs, )