0

I am trying to build an app which has a shedule manager in Python and KivyMD. The first page on the code is where the schedule will be displayed, the other page is were you will add the information. After filling the form on the second page, the dashboard window will be updated and the registered activity information will be added to an MDList widget. The top left MDCard holds the average time of the activities, the top right MDCard holds the amount of activities registered.

The user will have the opportunity to delete a previously registered activity if he/she made a mistake on registration (Swipe MDList items to the right and click the trash-can icon). Of course after deletion the widget values need to be updated. My problem comes when trying to update the average time of the activities: To calculate the average all the amounts are saved into a list, what I want to do is to obtain the index of the item deleted in the MDList. So I can delete the amount accordingly and do an accurate calculation. (Actually I just selected the index to be deleted as 0, so the deleted item will always be the first item in the list.

enter image description here

Python File:

from kivymd.app import MDApp
from kivy.uix.screenmanager import ScreenManager, Screen
from kivymd.toast import toast
from kivymd.uix.menu import MDDropdownMenu
from kivy.clock import Clock
from kivy.config import Config
from kivy.core.window import Window
from kivy.utils import platform
from kivy.properties import StringProperty
from kivymd.uix.card import MDCardSwipe
from kivymd.uix.button import MDIconButton, MDTooltip
from kivymd.uix.behaviors.toggle_behavior import MDToggleButton
from kivymd.uix.button import MDRectangleFlatButton


if platform not in ('android', 'ios'):
    Config.set('graphics', 'resizable', '0')
    Window.size = (600, 700)


class TooltipMDIconButton(MDIconButton, MDTooltip):
    pass


class MyToggleButton(MDRectangleFlatButton, MDToggleButton):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.background_down = self.theme_cls.primary_light


class SwipeToDeleteItem(MDCardSwipe):
    text = StringProperty()
    secondary_text = StringProperty()

    def remove_item(self, instance):
        dash = MDApp.get_running_app().root.get_screen('dash')
        dash.ids.md_list.remove_widget(instance)
        if dash.number_items_mdlist > 0:
            dash.number_items_mdlist -= 1
        dash.ids.numero_actividades_dash.text = str(dash.number_items_mdlist)

        # MODIFY INDEX SO TO MATCH THE MDLIST INDEX
        index = instance.id
        dash.minutes_items_mdlist -= int(dash.duracion_actividades_list[0])  # Should replace '0' with 'index'
        if int(dash.number_items_mdlist) != 0:
            dash.ids.promedio_tiempo_actividades.text = str(round(int(dash.minutes_items_mdlist) /
                                                              int(dash.number_items_mdlist)))
        else:
            dash.ids.promedio_tiempo_actividades.text = '0'

        del dash.duracion_actividades_list[0]   # Should replace '0' with 'index'
        self.show_toast()

    def show_toast(self):
        toast("Actividad eliminada del cronograma")


# DASHBOARD ################################
class DashboardWindow(Screen):
    number_items_mdlist = 0
    minutes_items_mdlist = 0
    duracion_actividades_list = []

    def on_pre_enter(self, *args):
        Window.size = (Window.width + 1, Window.height + 1)
        Window.size = (Window.width - 1, Window.height - 1)


# ACTIVITIES WINDOW ################################
class IngActivWindow(Screen):
    menu = None

    def dropdown_activity(self):
        # Create the drop down menu
        actividades = ["Correo electrónico", "Hora Regreso a Oficina", "Hora Salida de Oficina",
                       "Llamada de cortesia", "Llamada de Presentación", "Llamada de Promoción",
                       "Seguimiento a Cotización", "Traslado", "Visita a Cliente", "Whatsapp"]
        menu_items = [{"text": f"{actividad}"} for actividad in actividades]
        self.menu = MDDropdownMenu(
            caller=self.ids.dropdown_Actividades,
            items=menu_items,
            width_mult=5,
        )
        self.menu.open()
        self.menu.bind(on_release=self.set_item)

    def set_item(self, instance_menu, instance_menu_item):
        def set_item(interval):
            self.ids.dropdown_Actividades.text = instance_menu_item.text
            instance_menu.dismiss()
        Clock.schedule_once(set_item, 0.5)

    def format_hour(self):
        if len(self.ids.hour_field.text) > 2:
            self.ids.hour_field.text = self.ids.hour_field.text[:-1]

        if self.ids.hour_field.text != '' and int(self.ids.hour_field.text) > 12:
            self.ids.hour_field.text = self.ids.hour_field.text[:-1]

        self.ids.hora_final.text = self.ids.hour_field.text + ':00 ____'

    def format_minute(self):
        if len(self.ids.minute_field.text) > 2:
            self.ids.minute_field.text = self.ids.minute_field.text[:-1]

        if self.ids.minute_field.text != '' and int(self.ids.minute_field.text) > 59:
            self.ids.minute_field.text = self.ids.minute_field.text[:-1]

        self.ids.hora_final.text = self.ids.hour_field.text + ':' + self.ids.minute_field.text + ' ____'

    def format_am_pm(self):
        if self.ids.toggle_am.state == 'down':
            self.ids.hora_final.text = self.ids.hora_final.text[:-5] + ' a.m.'
        elif self.ids.toggle_pm.state == 'down':
            self.ids.hora_final.text = self.ids.hora_final.text[:-5] + ' p.m.'

    def calculate_final_time(self):
        if self.ids.hour_field != '' and self.ids.minute_field != '' and self.ids.duracion.text != '':

            add_minutes = str(int(self.ids.minute_field.text) + int(self.ids.duracion.text))
            # Check if added minutes does not surpass 60
            # < 60 --> just add minute_field and duracion
            if int(add_minutes) < 60:
                self.ids.hora_final.text = self.ids.hour_field.text + ':' + add_minutes
                # Add a.m./p.m. to label depending on the toggle button selected
                if self.ids.toggle_am.state == 'down':
                    self.ids.hora_final.text = self.ids.hora_final.text + ' a.m.'
                elif self.ids.toggle_pm.state == 'down':
                    self.ids.hora_final.text = self.ids.hora_final.text + ' p.m.'

            # if >= 60 -->
            else:
                hours_to_add = int(add_minutes) / 60
                minutes_to_add = int(add_minutes) % 60
                add_hours = int(self.ids.hour_field.text) + int(hours_to_add)
                if len(str(minutes_to_add)) == 1:
                    minutes_to_add = '0' + str(minutes_to_add)

                # Si la suma de las horas es mayor a 12, cambiar a formato de 12 horas
                if int(add_hours) == 12:
                    if self.ids.toggle_am.state == 'down':
                        self.ids.hora_final.text = str(add_hours) + ':' + str(minutes_to_add) + ' p.m.'
                    elif self.ids.toggle_pm.state == 'down':
                        self.ids.hora_final.text = str(add_hours) + ':' + str(minutes_to_add) + ' a.m.'

                elif int(add_hours) > 11:
                    if int(self.ids.hour_field.text) == 12:
                        if self.ids.toggle_am.state == 'down':
                            add_hours -= 12
                            self.ids.hora_final.text = str(add_hours) + ':' + str(minutes_to_add) + ' a.m.'
                        elif self.ids.toggle_pm.state == 'down':
                            add_hours -= 12
                            self.ids.hora_final.text = str(add_hours) + ':' + str(minutes_to_add) + ' p.m.'
                    else:
                        if self.ids.toggle_am.state == 'down':
                            add_hours -= 12
                            self.ids.hora_final.text = str(add_hours) + ':' + str(minutes_to_add) + ' p.m.'
                        elif self.ids.toggle_pm.state == 'down':
                            add_hours -= 12
                            self.ids.hora_final.text = str(add_hours) + ':' + str(minutes_to_add) + ' a.m.'
                else:
                    self.ids.hora_final.text = str(add_hours) + ':' + str(minutes_to_add)
                    # Add a.m./p.m. to label depending on the toggle button selected
                    if self.ids.toggle_am.state == 'down':
                        self.ids.hora_final.text = self.ids.hora_final.text + ' a.m.'
                    elif self.ids.toggle_pm.state == 'down':
                        self.ids.hora_final.text = self.ids.hora_final.text + ' p.m.'

        if self.ids.duracion.text == '' or self.ids.duracion.text == 0:
            self.ids.hora_final.text = str(self.ids.hour_field.text) + ':' + str(self.ids.minute_field.text)
            if self.ids.toggle_am.state == 'down':
                self.ids.hora_final.text = self.ids.hora_final.text + ' a.m.'
            elif self.ids.toggle_pm.state == 'down':
                self.ids.hora_final.text = self.ids.hora_final.text + ' p.m.'

    def complete_registration(self):
        dash = MDApp.get_running_app().root.get_screen('dash')
        activity = MDApp.get_running_app().root.get_screen('ingActiv')
        if activity.ids.toggle_am.state == 'down':
            dash.ids.md_list.add_widget(
                SwipeToDeleteItem(text=f"{activity.ids.dropdown_Actividades.text}",
                                  secondary_text=f"{activity.ids.hour_field.text}:{activity.ids.minute_field.text} a.m."
                                                 f" - {activity.ids.hora_final.text}"))

        elif activity.ids.toggle_pm.state == 'down':
            dash.ids.md_list.add_widget(
                SwipeToDeleteItem(text=f"{activity.ids.dropdown_Actividades.text}",
                                  secondary_text=f"{activity.ids.hour_field.text}:{activity.ids.minute_field.text} p.m."
                                                 f" - {activity.ids.hora_final.text}"))
        dash.number_items_mdlist += 1
        dash.minutes_items_mdlist += int(activity.ids.duracion.text)
        dash.duracion_actividades_list.append(activity.ids.duracion.text)

        dash.ids.numero_actividades_dash.text = str(dash.number_items_mdlist)
        dash.ids.promedio_tiempo_actividades.text = str(round(int(dash.minutes_items_mdlist) /
                                                              int(dash.number_items_mdlist)))
        self.show_toast_activities()

    def show_toast_activities(self):
        toast("Actividad registrada con éxito")


# WINDOW MANAGER ################################
class WindowManager(ScreenManager):
    pass


# MAIN CLASS ################################
class ReproducibleExample(MDApp):
    enie = 'ñ'
    acento_A = 'Á'
    acento_a = 'á'
    acento_E = 'É'
    acento_e = 'é'
    acento_I = 'Í'
    acento_i = 'í'
    acento_O = 'Ó'
    acento_o = 'ó'
    acento_U = 'Ú'
    acento_u = 'ú'
    interrog_inic = '¿'

    def build(self):
        self.theme_cls.primary_palette = "Teal"
        return WindowManager()


if __name__ == "__main__":
    ReproducibleExample().run()

KV File:

<WindowManager>:
    id: screen_manager

    DashboardWindow:
        id: dash
        name:'dash'
    IngActivWindow:
        id: ingActiv
        name: 'ingActiv'

<DashboardWindow>:
    name: "dash"

    BoxLayout:
        orientation: 'vertical'

        MDCard:
            size_hint: (0.75, 0.8)
            pos_hint: {'center_x': 0.5, 'center_y': 0.44}
            orientation: 'vertical'
            padding: '15dp'
            spacing: '10dp'
            md_bg_color: [1, 1, 1, 0.75]
            radius: [16, ]
            MDRaisedButton:
                text: 'Next Page'
                on_release:
                    root.manager.current = 'ingActiv'
            MDLabel:
                text: 'Datos de Actividades y Cronograma'
                font_style: 'H5'
                valign: 'middle'
                size_hint: 1, 0.05

            MDBoxLayout:
                orientation: 'horizontal'
                padding: "10dp", 0, "10dp", "10dp"
                spacing: "10dp"
                size_hint: 1, 0.30
                MDCard:
                    size_hint: 0.9, 0.9
                    orientation: 'vertical'
                    padding: '15dp'
                    spacing: '15dp'
                    md_bg_color: [0.811, 0, 0.058, 0.7]
                    radius: [16, ]
                    MDLabel:
                        id: promedio_tiempo_actividades
                        text: "0"
                        theme_text_color: "Custom"
                        text_color: 1, 1, 1, 1
                        font_style: 'H1'
                        halign: 'center'
                        font_size: (root.width**2 + root.height**2) / 11**4
                    MDLabel:
                        text: "Tiempo promedio de actividades"
                        theme_text_color: "Custom"
                        text_color: 1, 1, 1, 1
                        font_style: 'Subtitle2'
                        halign: 'center'
                MDCard:
                    size_hint: 0.9, 0.9
                    orientation: 'vertical'
                    padding: '15dp'
                    spacing: '15dp'
                    md_bg_color: [0.121, .227, 0.576, 0.7]
                    radius: [16, ]
                    MDLabel:
                        id: numero_actividades_dash
                        text: "0"
                        theme_text_color: "Custom"
                        text_color: 1, 1, 1, 1
                        font_style: 'H1'
                        halign: 'center'
                        font_size: (root.width**2 + root.height**2) / 11**4
                    MDLabel:
                        text: 'N' + app.acento_u + 'mero de actividades realizadas'
                        theme_text_color: "Custom"
                        text_color: 1, 1, 1, 1
                        font_style: 'Subtitle2'
                        halign: 'center'

            MDBoxLayout:
                orientation: 'horizontal'
                padding: "10dp", 0, "10dp", "10dp"
                spacing: "10dp"
                size_hint: 1, 0.65

                MDCard:
                    orientation: 'vertical'
                    padding: '15dp'
                    spacing: '10dp'
                    md_bg_color: [0.419, .725, 0.941, 0.7]
                    radius: [16, ]
                    MDBoxLayout:
                        orientation: 'horizontal'
                        size_hint_y: 0.15
                        MDLabel:
                            text: "Cronograma del dia con Hora de Inicio y Final, Cliente, Actividad, y Resultado"
                            theme_text_color: "Custom"
                            text_color: 1, 1, 1, 1
                            font_style: 'Subtitle2'
                            halign: 'center'
                        TooltipMDIconButton:
                            icon: 'plus'
                            tooltip_text: 'Agregar actividad'
                            pos_hint: {"center_x": 0.5, "center_y": 0.5}
                            font_color: 0,0,0,1
                            on_press:
                                root.manager.current = 'ingActiv'
                                root.manager.transition.direction =  'left'
                    MDBoxLayout:
                        orientation: "vertical"
                        spacing: "10dp"
                        size_hint_y: 0.85
                        ScrollView:
                            scroll_timeout: 100
                            MDList:
                                id: md_list
                                padding: 0


### EXTRAS:  SWIPE AND DELETE ITEM ####################
<SwipeToDeleteItem>:
    size_hint_y: None
    height: content.height

    MDCardSwipeLayerBox:
        padding: "8dp"
        MDIconButton:
            icon: "trash-can"
            pos_hint: {"center_y": .5}
            on_release: root.remove_item(root)

    MDCardSwipeFrontBox:
        TwoLineListItem:
            id: content
            text: root.text
            secondary_text: root.secondary_text
            _no_ripple_effect: True
            max_opened_x: "50dp"

<IngActivWindow>:


##################### ACTIVIDADES REALIZADAS ############################################################

<IngActivWindow>:
    name: 'ingActiv'

    FloatLayout:
        cols:1

        MDCard:
            size_hint: 0.80,0.66
            pos_hint: {"center_x": 0.5, "center_y": 0.45}
            radius: [36, ]
            padding: '10dp'
            md_bg_color: [1, 1, 1, 0.85]

            ScrollView:
                MDGridLayout:
                    id: widget_container
                    cols:1
                    size_hint_y: None
                    height: self.minimum_height
                    padding: '15dp', '15dp', '15dp', '15dp'
                    spacing: '20dp'
                    adaptive_height: True

                    MDRaisedButton:
                        text: 'Next Page'
                        on_release:
                            root.manager.current = 'dash'
                    MDLabel:
                        id: tiempoActiv
                        text: "Hora y Tipo de Actividad"
                        pos_hint: {"center_x":0.5, "center_y": 0.5}
                        bold: 'True'
                        font_style: 'Button'
                        font_size: '18sp'
                    MDBoxLayout:
                        orientation: 'horizontal'
                        padding: "15dp", "10dp", "15dp", "5dp"
                        spacing: "10dp"
                        adaptive_height: True
                        size_hint: 1, None

                        MDTextField:
                            id: hour_field
                            hint_text: 'Hora'
                            helper_text: 'Max: 12'
                            helper_text_mode: 'on_focus'
                            input_filter: 'int'
                            write_tab: False
                            pos_hint: {"center_x":0.5, "center_y": 0.5}
                            size_hint_x: 0.2
                            on_text: root.format_hour()
                        MDTextField:
                            id: minute_field
                            hint_text: 'Minuto'
                            helper_text: 'Max: 59'
                            helper_text_mode: 'on_focus'
                            input_filter: 'int'
                            write_tab: False
                            pos_hint: {"center_x":0.5, "center_y": 0.5}
                            size_hint_x: 0.2
                            on_text: root.format_minute()
                        MyToggleButton:
                            id: toggle_am
                            text: "A.M."
                            group: "hora"
                            pos_hint: {"center_x":0.5, "center_y": 0.5}
                            on_press: root.format_am_pm()
                        MyToggleButton:
                            id: toggle_pm
                            text: "P.M."
                            group: "hora"
                            pos_hint: {"center_x":0.5, "center_y": 0.5}
                            on_press: root.format_am_pm()

                    # DURACIÓN DE ACTIVIDAD
                    MDBoxLayout:
                        orientation: 'horizontal'
                        padding: "15dp", "5dp", "15dp", "15dp"
                        spacing: "15dp"
                        adaptive_height: True
                        size_hint: 1, None

                        MDTextField:
                            pos_hint: {"x":0, "center_y": 0.5}
                            id: duracion
                            hint_text: 'Duraci' + app.acento_o + 'n de actividad'
                            helper_text: 'Duracion en minutos'
                            helper_text_mode: 'on_focus'
                            write_tab: False
                            input_filter: 'int'
                            size_hint_x: 0.45
                            on_text: root.calculate_final_time()
                        MDBoxLayout:
                            orientation: 'horizontal'
                            size_hint_x: 0.45
                            spacing: '15dp'
                            MDLabel:
                                text: 'Hora de termino:'
                                font_size: '12sp'
                                theme_text_color: "Custom"
                                text_color: 0, 0, 0, 0.50
                                halign: 'right'
                            MDLabel:
                                id: hora_final
                                text: ''
                                font_size: '16sp'
                                theme_text_color: "Custom"
                                text_color: 0, 0, 0, 0.50

                    # TIPO DE ACTIVIDAD
                    MDBoxLayout:
                        orientation: 'horizontal'
                        padding: "15dp", "5dp", "15dp", "5dp"
                        spacing: "5dp"
                        adaptive_height: True
                        size_hint: 1, None

                        MDTextField:
                            id: dropdown_Actividades
                            write_tab: False
                            size_hint: 1, None
                            pos_hint: {"x": 0, "center_y": 0.5}
                            icon_right: 'arrow-down-drop-circle-outline'
                            hint_text: 'Seleccionar Tipo de Actividad'
                            write_tab: False
                            valign: 'bottom'
                            enabled: False
                            on_focus: if self.focus: root.dropdown_activity()

                    MDSeparator:
                        height: "2dp"
                        MDRaisedButton:
                            text: "Registrar Actividad"
                            size_hint: None, None
                            pos_hint: {'center_x': 0.5, 'center_y': 0.5}
                            font_size: '19sp'
                            on_release:
                                root.complete_registration()

I hope I was clear with the explanation. Bottomline, how can I obtain the index of an MDList?

Thanks a lot in advance.

Diego Gc
  • 175
  • 2
  • 15

1 Answers1

1

You can get the index of an item in an MDList by using the index() method of the children list of the MDList. However, that will not be the index of the corresponding element in your duracion_actividades_list, since widgets are inserted to the beginning of the children list and you are appending values to the duration list. Here is a way to calculate the correct index:

index = dash.number_items_mdlist - 1 - dash.ids.md_list.children.index(instance)

Possible simplifications might be to mimic the add_widget() behavior by inserting durations into the start of the list. Or keep the durations in a dictionary where the key is the SwipeToDeleteItem instance. Or just keep the duration as a property of the SwipeToDeleteItem.

John Anderson
  • 35,991
  • 4
  • 13
  • 36
  • Thank you very much @John Anderson as always your answers are really helpful. Moreover, you helped me realize I could've simplified code by just adding the durations at the beginning of the list. Better to avoid any further confusions. Thanks again! – Diego Gc Feb 05 '21 at 15:42
  • Pls what if I want to print the text of that index I've search online about the kivymd mdlist documentation but they you add how the gui kit will interact with python through data they handel – Lord_daniel Mar 21 '22 at 11:11