Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

impr(android): Use RecyclerView for ListView and TableView #12029

Merged
merged 18 commits into from
Nov 18, 2020

Conversation

garymathews
Copy link
Contributor

@garymathews garymathews commented Sep 10, 2020

  • Re-writeTi.UI.ListView to use RecyclerView
  • Re-write Ti.UI.TableView to use RecyclerView
TEST CASES
Updated 03/12/21 to include more test cases.
SHOW

const win = Ti.UI.createWindow({
    backgroundColor: 'white'
});
const tableViewSection = Ti.UI.createListSection({
    headerTitle: 'TableView'
});
const listViewSection = Ti.UI.createListSection({
    headerTitle: 'ListView'
});
const listView = Ti.UI.createListView({
    separatorStyle: Ti.UI.TABLE_VIEW_SEPARATOR_STYLE_SINGLE_LINE,
    separatorColor: 'black',
    templates: {
        template: {
            properties: {
                layout: 'vertical'
            },
            childTemplates: [{
                    type: 'Ti.UI.Label',
                    bindId: 'title',
                    properties: {
                        top: 5,
                        left: 5,
                        right: 5,
                        font: {
                            fontSize: 18
                        },
                        color: 'black'
                    }
                },
                {
                    type: 'Ti.UI.Label',
                    bindId: 'description',
                    properties: {
                        bottom: 5,
                        left: 5,
                        right: 5,
                        font: {
                            fontSize: 14
                        },
                        color: 'black'
                    }
                },
            ]
        }
    },
    defaultItemTemplate: 'template',
    sections: [tableViewSection, listViewSection]
});
listView.addEventListener('itemclick', e => {
    const item = e.section.items[e.itemIndex];
    if (item.properties.test instanceof Function) {
        item.properties.test();
    }
});
[
    // TableView
    {
        section: tableViewSection,
        title: 'Basic Test #1',
        description: 'headerView and footerView validation.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });
            const headerView = Ti.UI.createView({
                backgroundColor: 'red',
                height: '80px'
            });
            const footerView = Ti.UI.createView({
                backgroundColor: 'blue',
                height: '80px'
            });
            const tableView = Ti.UI.createTableView({
                headerView,
                footerView,
                backgroundColor: 'white'
            });
            tableView.setData([{
                title: 'Row',
                color: 'white'
            }]);
            win.add(tableView);
            win.open();
        }
    },
    {
        section: tableViewSection,
        title: 'Basic Test #2',
        description: 'Create multiple table sections and insert same section twice.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });
            const sectionFruit = Ti.UI.createTableViewSection({
                headerTitle: 'Fruit'
            });
            sectionFruit.add(Ti.UI.createTableViewRow({
                title: 'Apples'
            }));
            sectionFruit.add(Ti.UI.createTableViewRow({
                title: 'Bananas'
            }));
            const sectionVeg = Ti.UI.createTableViewSection({
                headerTitle: 'Vegetables'
            });
            sectionVeg.add(Ti.UI.createTableViewRow({
                title: 'Carrots'
            }));
            sectionVeg.add(Ti.UI.createTableViewRow({
                title: 'Potatoes'
            }));
            sectionVeg.add(Ti.UI.createTableViewRow({
                title: 'Cabbage'
            }));
            const table = Ti.UI.createTableView({
                data: [sectionFruit, sectionVeg]
            });
            const sectionFish = Ti.UI.createTableViewSection({
                headerTitle: 'Fish'
            });
            sectionFish.add(Ti.UI.createTableViewRow({
                title: 'Cod'
            }));
            sectionFish.add(Ti.UI.createTableViewRow({
                title: 'Haddock'
            }));
            table.insertSectionAfter(0, sectionFish);
            table.insertSectionBefore(0, sectionFish);
            win.add(table);
            win.open();
        }
    },
    {
        section: tableViewSection,
        title: 'Basic Test #3',
        description: 'Add same row multiple times. Section header should be displayed and row index should be correct.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });
            const table = Ti.UI.createTableView({});
            const tableSection = Ti.UI.createTableViewSection({
                headerTitle: 'Header'
            });
            const tableRow = Ti.UI.createTableViewRow({
                title: 'Row'
            });
            tableSection.add(tableRow);
            tableSection.add(tableRow);
            table.setData([tableSection]);
            table.addEventListener('click', e => {
                alert(`index: ${e.index}`);
                tableRow.backgroundColor = 'red';
            });
            win.add(table);
            win.open();
        }
    },
    {
        section: tableViewSection,
        title: 'Basic Test #4',
        description: 'Toggle TableView backgroundColor, row text should contrast with background.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });
            const tableView = Ti.UI.createTableView({
                data: [{
                        title: 'Row #1'
                    },
                    {
                        title: 'Row #2'
                    },
                    {
                        title: 'Row #3'
                    }
                ]
            });
            const btn = Ti.UI.createButton({
                title: 'TOGGLE BACKGROUND',
                left: 5,
                bottom: 5
            });
            btn.addEventListener('click', e => {
                tableView.backgroundColor = tableView.backgroundColor == 'white' ? 'black' : 'white';
            });
            win.add([tableView, btn]);
            win.open();
        }
    },
    {
        section: tableViewSection,
        title: 'Basic Test #5',
        description: 'Test search functionality, including \'noresults\' event.',
        test() {
            const win = Titanium.UI.createWindow({
                backgroundColor: 'gray'
            });
            const search = Ti.UI.createSearchBar({
                hintText: 'Search...',
                showCancel: false
            });

            function createSection(rows, suffix) {
                const section = Ti.UI.createTableViewSection({
                    headerTitle: `Section ${suffix}`
                });

                for (let i = 1; i <= rows; i++) {
                    section.add(Ti.UI.createTableViewRow({
                        title: `Row #${i}`
                    }));
                }

                return section;
            }

            const tableView = Ti.UI.createTableView({
                data: [
                    createSection(10, 'A'),
                    createSection(10, 'B'),
                    createSection(10, 'C')
                ],
                search
            });

            tableView.addEventListener('noresults', e => {
                alert('NO RESULTS!');
            });

            win.add(tableView);
            win.open();
        }
    },
    {
        section: tableViewSection,
        title: 'TIMOB-28290',
        description: 'Test \'scroll\', \'scrolling\' and \'scrollend\' events.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });

            const rows = [];
            for (let i = 0; i < 100; i++) {
                rows.push({
                    title: `Row #${i}`
                });
            }

            const tableView = Ti.UI.createTableView({
                data: rows
            });

            function callback(e) {
                console.log(`${e.type} properties: ${JSON.stringify(Object.keys(e))}`);
            }

            tableView.addEventListener('scrolling', callback);
            tableView.addEventListener('scroll', callback);
            tableView.addEventListener('scrollend', callback);

            win.add(tableView);
            win.open();
        }
    },
    {
        section: tableViewSection,
        title: 'TIMOB-28048',
        description: 'Allow RefreshControl to activate without dragging content.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });
            const refreshControl = Ti.UI.createRefreshControl({
                tintColor: 'red'
            });
            const tableView = Ti.UI.createTableView({
                data: [
                    Ti.UI.createTableViewRow({
                        title: 'Row #1'
                    }),
                    Ti.UI.createTableViewRow({
                        title: 'Row #2'
                    }),
                    Ti.UI.createTableViewRow({
                        title: 'Row #3'
                    }),
                ],
                refreshControl
            });
            const label = Ti.UI.createLabel({
                bottom: '33%',
                text: 'Dragging here should activate refresh control',
                color: 'white'
            });
            refreshControl.addEventListener('refreshstart', e => {
                setTimeout(_ => {
                    const row = Ti.UI.createTableViewRow({
                        title: `Row #${tableView.sections[0].rowCount+1}`
                    });
                    tableView.appendRow(row);
                    refreshControl.endRefreshing();
                }, 1000);
            });
            win.add([tableView, label]);
            win.open();
        }
    },
    {
        section: tableViewSection,
        title: 'TIMOB-28162',
        description: 'TableViewRow does not scale to height of parent.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });
            const row_fill = Ti.UI.createTableViewRow({
                height: Ti.UI.FILL,
                title: 'View should fill TableView parent',
                backgroundColor: 'red'
            });
            const row_percent = Ti.UI.createTableViewRow({
                height: '100%',
                title: 'View should fill TableView parent',
                backgroundColor: 'blue'
            });
            const table = Ti.UI.createTableView({
                data: [row_fill, row_percent]
            });
            win.add(table);
            win.open();
        }
    },
    {
        section: tableViewSection,
        title: 'TIMOB-28163',
        description: 'TableViewRow ignores borderRadius.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });
            const row = Ti.UI.createTableViewRow({
                borderRadius: 20,
                height: 80,
                title: 'View should have a border radius',
                backgroundColor: 'red'
            });
            const table = Ti.UI.createTableView({
                data: [row]
            });
            win.add(table);
            win.open();
        }
    },
    {
        section: tableViewSection,
        title: 'TIMOB-28164',
        description: 'Pressing on a TableViewRow results in a incorrect row color.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });
            const row = Ti.UI.createTableViewRow({
                height: 80,
                title: 'Row should not dull upon press.\nRipple animation should be displayed on Android 5.0+.',
                backgroundColor: 'blue'
            });
            const table = Ti.UI.createTableView({
                data: [row]
            });
            win.add(table);
            win.open();
        }
    },
    {
        section: tableViewSection,
        title: 'TIMOB-28165',
        description: 'Pressing on a child view within TableViewRow does not active the row ripple effect.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });
            const row = Ti.UI.createTableViewRow({
                height: 80,
                backgroundColor: 'blue'
            });
            const label = Ti.UI.createLabel({
                text: 'Pressing this should activate ripple effect on Android 5.0+.',
                textAlign: Ti.UI.TEXT_ALIGNMENT_CENTER,
                backgroundColor: 'red',
                width: '80%',
                height: '80%'
            });
            row.add(label);
            const table = Ti.UI.createTableView({
                data: [row]
            });
            win.add(table);
            win.open();
        }
    },
    {
        section: tableViewSection,
        title: 'TIMOB-28166',
        description: 'Setting opacity on TableViewRow has no effect on child views.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });
            const row = Ti.UI.createTableViewRow({
                height: 80,
                opacity: 0.4,
                backgroundColor: 'blue'
            });
            const label = Ti.UI.createLabel({
                text: 'This view should be affected by row opacity',
                textAlign: Ti.UI.TEXT_ALIGNMENT_CENTER,
                backgroundColor: 'red',
                width: '80%',
                height: '80%'
            });
            row.add(label);
            const table = Ti.UI.createTableView({
                data: [row]
            });
            win.add(table);
            win.open();
        }
    },
    {
        section: tableViewSection,
        title: 'TIMOB-26887',
        description: 'headerTitle and footerTitle should be updatable after creation.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });
            const table = Ti.UI.createTableView({
                headerTitle: 'Header',
                footerTitle: 'Footer',
                data: [Ti.UI.createTableViewRow({
                    title: 'Row'
                })]
            });
            const label = Ti.UI.createLabel({
                bottom: '33%',
                text: 'TableView.headerTitle and TableView.footerTitle should continuously update.'
            });
            let count = 0;
            setInterval(_ => {
                count++;
                table.headerTitle = `Header (${count})`;
                table.footerTitle = `Footer (${count})`;
            }, 1000);
            win.add([table, label]);
            win.open();
        }
    },
    {
        section: tableViewSection,
        title: 'TIMOB-28223',
        description: 'Validate row icons display correctly.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });
            const table = Ti.UI.createTableView({
                data: [
                    Ti.UI.createTableViewRow({
                        title: 'Checkmark',
                        hasCheck: true
                    }),
                    Ti.UI.createTableViewRow({
                        title: 'Child (Down-Arrow)',
                        hasChild: true
                    }),
                    Ti.UI.createTableViewRow({
                        title: 'Detail (Chevron)',
                        hasDetail: true
                    })
                ]
            });
            const label = Ti.UI.createLabel({
                bottom: '33%',
                text: 'Three rows should display icons (checkmark, down-arrow, chevron) in that order.'
            });
            win.add([table, label]);
            win.open();
        }
    },
    {
        section: tableViewSection,
        title: 'TIMOB-28329',
        description: 'Validate touchFeedback and touchFeedbackColor support.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'white'
            });
            const tableView = Ti.UI.createTableView({
                backgroundColor: 'white',
                data: [{
                        title: 'Item #1',
                        color: 'white',
                        backgroundColor: 'gray',
                        borderRadius: 10,
                        height: 50
                    },
                    {
                        title: 'Item #2 - transparent',
                        color: 'black',
                        backgroundColor: 'transparent',
                        borderRadius: 10,
                        height: 50
                    },
                    {
                        title: 'Item #3 - undefined',
                        color: 'black',
                        borderRadius: 10,
                        height: 50
                    }
                ],
                touchFeedback: true,
                touchFeedbackColor: 'green'
            });
            const isColor = _ => `touchFeedbackColor: ${tableView.touchFeedbackColor == 'red' ? 'red' : 'blue'}`;
            const btn_color = Ti.UI.createButton({
                title: isColor(),
                bottom: 5,
                right: 5
            });
            btn_color.addEventListener('click', _ => {
                tableView.touchFeedbackColor = tableView.touchFeedbackColor == 'red' ? 'blue' : 'red';
                btn_color.title = isColor();
            });
            const isFeedback = _ => `touchFeedback: ${tableView.touchFeedback ? 'ON' : 'OFF'}`;
            const btn_feedback = Ti.UI.createButton({
                title: isFeedback(),
                bottom: 5,
                left: 5
            });
            btn_feedback.addEventListener('click', _ => {
                tableView.touchFeedback = !tableView.touchFeedback;
                btn_feedback.title = isFeedback();
            });
            win.add([tableView, btn_feedback, btn_color]);
            win.open();
        }
    },
    {
        section: tableViewSection,
        title: 'TIMOB-28383',
        description: 'Validate minRowHeight.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'white'
            });
            const rows = [];

            for (let i = 0; i < 3; i++) {
                const row = Ti.UI.createTableViewRow({
                    borderColor: 'red',
                    borderWidth: 2
                });

                row.add(Ti.UI.createLabel({
                    color: 'black',
                    text: `Row #${i}`,
                    left: 5
                }));
                rows.push(row);
            }

            const table = Ti.UI.createTableView({
                data: rows,
                minRowHeight: 100
            });

            win.add(table);
            win.open();
        }
    },
    {
        section: tableViewSection,
        title: 'TIMOB-28384',
        description: 'Validate leftImage and rightImage.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'white'
            });
            const rows = [];

            for (let i = 0; i < 10; i++) {
                const row = Ti.UI.createTableViewRow({
                    borderColor: 'red',
                    borderWidth: 2,
                    leftImage: '/images/icon_left.png',
                    rightImage: '/images/icon_right.png'
                });

                row.add(Ti.UI.createLabel({
                    color: 'black',
                    text: `Row #${i}`,
                    left: 5
                }));
                rows.push(row);
            }

            const table = Ti.UI.createTableView({
                data: rows,
                minRowHeight: 100
            });

            win.add(table);
            win.open();
        }
    },
    {
        section: tableViewSection,
        title: 'TIMOB-28388',
        description: 'Validate updateRow() and row.index',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'white'
            });

            function createRow(title, updated = false) {
                const row = Ti.UI.createTableViewRow();

                row.add(Ti.UI.createLabel({
                    text: title,
                    left: '10'
                }));

                if (updated) {
                    row.add(Ti.UI.createLabel({
                        text: 'UPDATED',
                        right: '10'
                    }));
                }

                return row;
            }

            const sections = [];
            for (let i = 0; i < 3; i++) {
                const section = Ti.UI.createTableViewSection({
                    headerTitle: `Section #${i}`
                });

                for (let x = 0; x < 3; x++) {
                    section.add(createRow(`Row #${x}`));
                }

                sections.push(section);
            }

            const table = Ti.UI.createTableView({
                data: sections,
                minRowHeight: 50
            });

            table.addEventListener('click', e => {
                console.log(`row.index: ${e.index}`);
                table.updateRow(e.index, createRow(e.row.children[0].text, true));
            });

            win.add(table);
            win.open();
        }
    },
    {
        section: tableViewSection,
        title: 'TIMOB-28389',
        description: 'Validate removal of child views.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'white'
            });

            function createRow(title) {
                const row = Ti.UI.createTableViewRow();

                row.add(Ti.UI.createLabel({
                    text: title,
                    left: '10'
                }));
                row.add(Ti.UI.createLabel({
                    text: 'REMOVE',
                    right: '10'
                }));

                return row;
            }


            const table = Ti.UI.createTableView({
                data: [
                    createRow('Row #1'),
                    createRow('Row #2'),
                    createRow('Row #3')
                ],
                minRowHeight: 50
            });

            table.addEventListener('click', e => {
                if (e.row.children[1]) {
                    e.row.remove(e.row.children[1]);
                }
            });

            win.add(table);
            win.open();
        }
    },
    // ListView
    {
        section: listViewSection,
        title: 'Basic Test #1',
        description: 'Basic test for template, `itemclick`, scrolling and removal of items.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });
            const listView = Ti.UI.createListView({
                backgroundColor: 'white',
                templates: {
                    template: {
                        properties: {
                            borderRadius: 16
                        },
                        childTemplates: [{
                            type: 'Ti.UI.Label',
                            bindId: 'label',
                            properties: {
                                color: 'white'
                            }
                        }]
                    }
                }
            });
            listView.sections = [
                Ti.UI.createListSection({
                    items: [{
                            template: 'template',
                            properties: {
                                backgroundColor: 'red',
                                height: 50
                            },
                            label: {
                                text: 'Red'
                            }
                        },
                        {
                            template: 'template',
                            properties: {
                                backgroundColor: 'green',
                                height: 50
                            },
                            label: {
                                text: 'Green'
                            }
                        },
                        {
                            template: 'template',
                            properties: {
                                backgroundColor: 'blue',
                                height: 50
                            },
                            label: {
                                text: 'Blue'
                            }
                        },
                        {
                            template: 'template',
                            properties: {
                                backgroundColor: 'yellow',
                                height: 50
                            },
                            label: {
                                text: 'Yellow'
                            }
                        },
                        {
                            template: 'template',
                            properties: {
                                backgroundColor: 'cyan',
                                height: 50
                            },
                            label: {
                                text: 'Cyan'
                            }
                        },
                        {
                            template: 'template',
                            properties: {
                                backgroundColor: 'pink',
                                height: 50
                            },
                            label: {
                                text: 'Pink'
                            }
                        },
                    ]
                })
            ];
            listView.addEventListener('itemclick', e => {
                console.log('itemclick: ' + JSON.stringify(e, null, 2));
                // Remove item.
                e.section.deleteItemsAt(e.itemIndex, 1);
            });
            win.add(listView);
            win.open();
        }
    },
    {
        section: listViewSection,
        title: 'Basic Test #2',
        description: 'Basic test for custom properties.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });
            const section = Ti.UI.createListSection({
                items: [{
                    properties: {
                        title: 'Row 1',
                        customNumber: 1
                    }
                }, ]
            });
            const listView = Ti.UI.createListView({
                sections: [section]
            });
            section.appendItems([{
                properties: {
                    title: 'Row 2',
                    customNumber: 2
                }
            }, ]);
            listView.addEventListener('itemclick', e => {
                const item = section.getItemAt(e.itemIndex);
                item.customString = 'Custom String.';
                section.updateItemAt(e.itemIndex, item);
                alert(`
					customNumber: ${section.getItemAt(e.itemIndex).properties.customNumber}
					customString: ${section.getItemAt(e.itemIndex).customString}
				`);
            });
            win.add(listView);
            win.open();
        }
    },
    {
        section: listViewSection,
        title: 'Basic Test #3',
        description: 'Basic test for template collision.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });
            const section = Ti.UI.createListSection({
                items: [{
                        properties: {
                            title: 'Item #1'
                        }
                    },
                    {
                        properties: {
                            title: 'Item #2'
                        }
                    },
                    {
                        properties: {
                            title: 'Item #3'
                        }
                    },
                ]
            });
            const listView = Ti.UI.createListView({
                sections: [section],
                templates: {
                    template: {
                        childTemplates: [{
                            type: 'Ti.UI.Label',
                            bindId: 'title',
                            properties: {
                                right: '5px',
                                color: 'white'
                            }
                        }]
                    }
                },
                defaultItemTemplate: 'template'
            });
            const label = Ti.UI.createLabel({
                bottom: '33%',
                text: 'Custom item rows with collision `bindId` `title` should show right-aligned.\nLog should also show "sections: 1".'
            });
            console.log(`sections: listView.sections.length`);
            win.add([listView, label]);
            win.open();
        }
    },
    {
        section: listViewSection,
        title: 'Basic Test #4',
        description: 'Test template property function and priority.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });
            const section = Ti.UI.createListSection({
                items: [{
                        template: Ti.UI.LIST_ITEM_TEMPLATE_DEFAULT,
                        properties: {
                            title: 'LIST_ITEM_TEMPLATE_DEFAULT, with height undefined.'
                        }
                    },
                    {
                        template: Ti.UI.LIST_ITEM_TEMPLATE_DEFAULT,
                        properties: {
                            title: 'LIST_ITEM_TEMPLATE_DEFAULT, with height set to 100',
                            height: 100
                        }
                    },
                    {
                        template: 'item',
                        label: {
                            text: 'TEMPLATE, with height set as 200 from template.'
                        }
                    },
                    {
                        template: 'item',
                        properties: {
                            height: 300
                        },
                        label: {
                            text: 'TEMPLATE, with height set as 300.',
                        }
                    }
                ]
            });
            const listView = Ti.UI.createListView({
                templates: {
                    item: {
                        properties: {
                            height: 200
                        },
                        childTemplates: [{
                            type: 'Ti.UI.Label',
                            bindId: 'label',
                            properties: {
                                width: Ti.UI.FILL,
                                height: Ti.UI.FILL
                            }
                        }]
                    }
                },
                sections: [section]
            });
            win.add(listView);
            win.open();
        }
    },
    {
        section: listViewSection,
        title: 'Basic Test #5',
        description: 'Test search functionality.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });

            function createSection(rows, suffix) {
                const section = Ti.UI.createListSection({
                    headerTitle: `Section ${suffix}`
                });
                const items = [];

                for (let i = 1; i <= rows; i++) {
                    items.push({
                        properties: {
                            title: `Row #${i}`,
                            searchableText: `Row #${i}`
                        }
                    });
                }
                section.items = items;

                return section;
            }

            const search = Ti.UI.createSearchBar({
                hintText: 'Search...',
                showCancel: false
            });
            const listView = Ti.UI.createListView({
                searchView: search,
                sections: [
                    createSection(10, 'A'),
                    createSection(10, 'B'),
                    createSection(10, 'C')
                ]
            });
            win.add(listView);
            win.open();
        }
    },
    {
        section: listViewSection,
        title: 'Basic Test #6',
        description: 'Basic test of item index.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'white'
            });
            const sections = [];

            for (let i = 0; i < 3; i++) {
                const items = [];

                for (let x = 0; x < 5; x++) {
                    items.push({
                        properties: {
                            title: `Item #${x}`
                        }
                    });
                }

                sections.push(Ti.UI.createListSection({
                    headerTitle: `Section #${i}`,
                    items
                }));
            }
            const listView = Ti.UI.createListView({
                sections
            });

            listView.addEventListener('itemclick', e => {
                const section = listView.sections[e.sectionIndex];
                const item = section.getItemAt(e.itemIndex);

                item.properties.title = `Item #${e.itemIndex} - UPDATED`;
                section.updateItemAt(e.itemIndex, item);
            });

            win.add(listView);
            win.open();
        }
    },
    {
        section: listViewSection,
        title: 'TIMOB-28293',
        description: 'Test list view marker event.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });

            const items = [];
            for (let i = 0; i < 100; i++) {
                items.push({
                    properties: {
                        title: `Item #${i}`
                    }
                });
            }

            const listView = Ti.UI.createListView({
                sections: [Ti.UI.createListSection({
                    items
                })]
            });

            // Add valid marker.
            listView.addMarker({
                sectionIndex: 0,
                itemIndex: 32
            });

            // Add invalid marker.
            listView.addMarker({
                sectionIndex: 1,
                itemIndex: 1
            });

            // Add invalid marker.
            listView.addMarker({
                sectionIndex: 0,
                itemIndex: -1
            });

            // Add invalid marker.
            listView.addMarker({
                sectionIndex: -1,
                itemIndex: 1
            });

            // Add invalid marker.
            listView.addMarker({
                sectionIndex: 0,
                itemIndex: 101
            });

            listView.addEventListener('marker', e => {
                alert(JSON.stringify({
                    sectionIndex: e.sectionIndex,
                    itemIndex: e.itemIndex
                }, null, 1));
            });

            win.add(listView);
            win.open();
        }
    },
    {
        section: listViewSection,
        title: 'TIMOB-28290',
        description: 'Test \'scrolling\', \'scrollstart\' and \'scrollend\' events.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });
            const listView = Ti.UI.createListView({
                sections: [Ti.UI.createListSection()]
            });

            const items = [];
            for (let i = 0; i < 100; i++) {
                items.push({
                    properties: {
                        title: `Item #${i}`
                    }
                });
            }
            listView.sections[0].items = items;

            function callback(e) {
                console.log(`${e.type} properties: ${JSON.stringify(Object.keys(e))}`);
            }

            listView.addEventListener('scrolling', callback);
            listView.addEventListener('scrollstart', callback);
            listView.addEventListener('scrollend', callback);

            win.add(listView);
            win.open();
        }
    },
    {
        section: listViewSection,
        title: 'TIMOB-18069',
        description: 'Implement ListSection.itemCount and ListSection.filteredItemCount',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });
            const section = Ti.UI.createListSection({
                items: [{
                        properties: {
                            title: 'Item #1',
                            searchableText: '1'
                        }
                    },
                    {
                        properties: {
                            title: 'Item #2',
                            searchableText: '2'
                        }
                    },
                    {
                        properties: {
                            title: 'Item #3',
                            searchableText: '3'
                        }
                    }
                ]
            });
            const listView = Ti.UI.createListView({
                sections: [section],
                searchText: '2'
            });
            win.addEventListener('open', e => {
                alert(`
					EXPECTED
					itemCount: 3
					filteredItemCount: 1

					ACTUAL
					itemCount: ${section.itemCount}
					filteredItemCount: ${section.filteredItemCount}
				`);
            });
            win.add(listView);
            win.open();
        }
    },
    {
        section: listViewSection,
        title: 'TIMOB-25991',
        description: 'Allow RefreshControl to activate without dragging content.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });
            const section = Ti.UI.createListSection({
                items: [{
                        properties: {
                            title: 'Item #1'
                        }
                    },
                    {
                        properties: {
                            title: 'Item #2'
                        }
                    },
                    {
                        properties: {
                            title: 'Item #3'
                        }
                    }
                ]
            });
            const refreshControl = Ti.UI.createRefreshControl({
                tintColor: 'red'
            });
            const listView = Ti.UI.createListView({
                sections: [section],
                refreshControl
            });
            const label = Ti.UI.createLabel({
                bottom: '33%',
                text: 'Dragging here should activate refresh control',
                color: 'white'
            });
            refreshControl.addEventListener('refreshstart', e => {
                setTimeout(_ => {
                    section.appendItems([{
                        properties: {
                            title: `Item #${section.itemCount+1}`
                        }
                    }]);
                    refreshControl.endRefreshing();
                }, 1000);
            });
            win.add([listView, label]);
            win.open();
        }
    },
    {
        section: listViewSection,
        title: 'TIMOB-28167',
        description: 'Pressing on a child view within ListViewItem does not active the row ripple effect.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });
            const listView = Ti.UI.createListView({
                templates: {
                    template: {
                        properties: {
                            backgroundColor: 'blue',
                            height: 80
                        },
                        childTemplates: [{
                            type: 'Ti.UI.Label',
                            bindId: 'label',
                            properties: {
                                text: 'Pressing this should activate ripple',
                                textAlign: Ti.UI.TEXT_ALIGNMENT_CENTER,
                                backgroundColor: 'red',
                                width: '80%',
                                height: '80%'
                            }
                        }]
                    }
                },
                defaultItemTemplate: 'template'
            });
            listView.sections = [
                Ti.UI.createListSection({
                    items: [{}]
                })
            ];
            win.add(listView);
            win.open();
        }
    },
    {
        section: listViewSection,
        title: 'TIMOB-28223',
        description: 'Validate item icons display correctly.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'gray'
            });
            const section = Ti.UI.createListSection({
                items: [{
                        properties: {
                            title: 'NONE',
                            accessoryType: Ti.UI.LIST_ACCESSORY_TYPE_NONE
                        }
                    },
                    {
                        properties: {
                            title: 'LIST_ACCESSORY_TYPE_CHECKMARK',
                            accessoryType: Ti.UI.LIST_ACCESSORY_TYPE_CHECKMARK
                        }
                    },
                    {
                        properties: {
                            title: 'LIST_ACCESSORY_TYPE_DETAIL',
                            accessoryType: Ti.UI.LIST_ACCESSORY_TYPE_DETAIL
                        }
                    },
                    {
                        properties: {
                            title: 'LIST_ACCESSORY_TYPE_DISCLOSURE',
                            accessoryType: Ti.UI.LIST_ACCESSORY_TYPE_DISCLOSURE
                        }
                    }
                ]
            });
            const listView = Ti.UI.createListView({
                sections: [section]
            });
            const label = Ti.UI.createLabel({
                bottom: '33%',
                text: 'Four rows should be displayed (no icon, checkmark, down-arrow, chevron) in that order.',
                color: 'white'
            });
            win.add([listView, label]);
            win.open();
        }
    },
    {
        section: listViewSection,
        title: 'TIMOB-28329',
        description: 'Validate touchFeedback and touchFeedbackColor support.',
        test() {
            const win = Ti.UI.createWindow({
                backgroundColor: 'white'
            });
            const section = Ti.UI.createListSection({
                items: [{
                        properties: {
                            backgroundColor: 'gray'
                        },
                        title: {
                            text: 'Item #1 - gray',
                            color: 'black'
                        }
                    },
                    {
                        properties: {
                            backgroundColor: 'transparent'
                        },
                        title: {
                            text: 'Item #2 - transparent',
                            color: 'black'
                        }
                    },
                    {
                        title: {
                            text: 'Item #3 - undefined',
                            color: 'black'
                        }
                    },
                ]
            });
            const listView = Ti.UI.createListView({
                sections: [section],
                templates: {
                    template: {
                        properties: {
                            height: 50,
                            borderRadius: 10
                        },
                        childTemplates: [{
                            type: 'Ti.UI.Label',
                            bindId: 'title',
                            properties: {
                                left: '5dp',
                                color: 'white'
                            }
                        }]
                    }
                },
                defaultItemTemplate: 'template',
                touchFeedback: true,
                touchFeedbackColor: 'green'
            });
            const isColor = _ => `touchFeedbackColor: ${listView.touchFeedbackColor == 'red' ? 'red' : 'blue'}`;
            const btn_color = Ti.UI.createButton({
                title: isColor(),
                bottom: 5,
                right: 5
            });
            btn_color.addEventListener('click', _ => {
                listView.touchFeedbackColor = listView.touchFeedbackColor == 'red' ? 'blue' : 'red';
                btn_color.title = isColor();
            });
            const isFeedback = _ => `touchFeedback: ${listView.touchFeedback ? 'ON' : 'OFF'}`;
            const btn_feedback = Ti.UI.createButton({
                title: isFeedback(),
                bottom: 5,
                left: 5
            });
            btn_feedback.addEventListener('click', _ => {
                listView.touchFeedback = !listView.touchFeedback;
                btn_feedback.title = isFeedback();
            });
            win.add([listView, btn_feedback, btn_color]);
            win.open();
        }
    }
].forEach(test => test.section.appendItems({
    title: {
        text: test.title
    },
    description: {
        text: test.description
    },
    properties: {
        test: test.test
    }
}));
win.add(listView);
win.open();

TIMOB-27077
TIMOB-28088

@build
Copy link
Contributor

build commented Sep 10, 2020

Warnings
⚠️

build/lib/test/test.js#L464 - build/lib/test/test.js line 464 – 'stripped' is defined but never used. Allowed unused args must match /^_.+/u. (no-unused-vars)

⚠️

build/lib/test/test.js#L814 - build/lib/test/test.js line 814 – Missing JSDoc parameter description for 'testResults'. (valid-jsdoc)

Messages
📖 👍 Hey!, You deleted more code than you added. That's awesome!
📖

💾 Here's the generated SDK zipfile.

📖 ✊ The commits in this PR match our conventions! Feel free to Rebase and Merge this PR when ready.
📖

✅ All tests are passing
Nice one! All 13709 tests are passing.
(There are 900 skipped tests not included in that total)

Generated by 🚫 dangerJS against 80a398a

@garymathews garymathews changed the title impr(android): Use RecyclerView for ListView and TableView impr(android)(9_3_X): Use RecyclerView for ListView and TableView Sep 11, 2020
@hansemannn
Copy link
Collaborator

Hi Gary! Let me know if I can help with sample code. We have 36+ complex list views in all kinds (system- and custom-templates) that I could share with you via repo.

@sgtcoolguy
Copy link
Contributor

@garymathews Does this supersede #11556 ?

@garymathews garymathews added the backport master when applied, PRs with this label will get an auto-generated backport to master branch on merge label Sep 21, 2020
@build build added the docs label Oct 7, 2020
@garymathews garymathews changed the base branch from 9_3_X to master October 10, 2020 01:07
@garymathews garymathews removed backport master when applied, PRs with this label will get an auto-generated backport to master branch on merge work in progress 🚧 labels Oct 10, 2020
@garymathews garymathews force-pushed the TIMOB-28088 branch 2 times, most recently from 0d804b1 to d5852a0 Compare October 15, 2020 01:00
@garymathews garymathews changed the title impr(android)(9_3_X): Use RecyclerView for ListView and TableView impr(android): Use RecyclerView for ListView and TableView Oct 20, 2020
@tidev tidev deleted a comment from build Oct 21, 2020
@garymathews
Copy link
Contributor Author

@ssekhri Updated PR also included test cases for search functionality in PR test case.

@lokeshchdhry

This comment has been minimized.

@ssekhri
Copy link

ssekhri commented Nov 18, 2020

The changes look good. Tests for listview and tableview ran fine.

@lokeshchdhry
Copy link
Contributor

Looks good to me as well. We can merge it.

@hansemannn
Copy link
Collaborator

hansemannn commented Nov 18, 2020

There is still a UI regression that I mentioned earlier last week, where the chevron-icons have changed in size and color (left: 9.2.2, right: this PR):

Screenshot_1605739488

Previous color: #858585
New color: #777777

@build
Copy link
Contributor

build commented Nov 18, 2020

The backport to 9_3_X failed:

The process 'git' failed with exit code 128

Check the run for full details
To backport manually, run these commands in your terminal:

# Fetch latest updates from GitHub
git fetch
# Check out the target branch
git checkout 9_3_X
# Make sure it's up to date
git pull
# Check out your branch
git checkout -b backport-12029-to-9_3_X
# Apply the commits from the PR
curl -s https://github.com/appcelerator/titanium_mobile/commit/a31fe83815c71d89aca48e74d110dc8f72908388.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/52e919ead515b8bdeb546117b6fea23c41731c53.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/2cccc2fa41983d3704f1217377a7450da3248724.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/de6ee316abb6893e82e5eac3d8a1edbfc2440aaa.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/52989953c7ded3983139db1c3480a203ff76a106.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/44cf3d1545f52a0d2e907fba7560277429281ea6.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/84c6621b85a5a77b7ed1829ad35c6e9c26f7973a.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/dbb74dd3dde114a2552dac4bf61b71da88b0cc1a.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/31e8d82149405b9eb079a793c11fcaee53be5453.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/9a6b40ce142577f6ea9992ecd3297bc94936419e.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/5f1c04d6a2deb3783884b28406e15daad53d3889.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/9b1efe7f836a33aededa25696cffb6489fb78924.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/1b2a19d69edb0732a126396b252e41cab1a8f1b4.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/5069965e5be35362431cbcc068faf001df475de5.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/a0f312573473d51f9d6640efb31c0d2f04021586.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/9743ddd0f217ecc61bd34ee4e826e95b97c1ff83.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/80a398ab13041ffcf87c0f6a15b1f06ba28de33c.patch | git am -3 --ignore-whitespace
# Push it to GitHub
git push --set-upstream origin backport-12029-to-9_3_X

Then, create a pull request where the base branch is 9_3_X and the compare/head branch is backport-12029-to-9_3_X.

@build
Copy link
Contributor

build commented Nov 18, 2020

The backport to 9_3_X failed:

The process 'git' failed with exit code 128

Check the run for full details
To backport manually, run these commands in your terminal:

# Fetch latest updates from GitHub
git fetch
# Check out the target branch
git checkout 9_3_X
# Make sure it's up to date
git pull
# Check out your branch
git checkout -b backport-12029-to-9_3_X
# Apply the commits from the PR
curl -s https://github.com/appcelerator/titanium_mobile/commit/a31fe83815c71d89aca48e74d110dc8f72908388.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/52e919ead515b8bdeb546117b6fea23c41731c53.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/2cccc2fa41983d3704f1217377a7450da3248724.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/de6ee316abb6893e82e5eac3d8a1edbfc2440aaa.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/52989953c7ded3983139db1c3480a203ff76a106.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/44cf3d1545f52a0d2e907fba7560277429281ea6.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/84c6621b85a5a77b7ed1829ad35c6e9c26f7973a.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/dbb74dd3dde114a2552dac4bf61b71da88b0cc1a.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/31e8d82149405b9eb079a793c11fcaee53be5453.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/9a6b40ce142577f6ea9992ecd3297bc94936419e.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/5f1c04d6a2deb3783884b28406e15daad53d3889.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/9b1efe7f836a33aededa25696cffb6489fb78924.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/1b2a19d69edb0732a126396b252e41cab1a8f1b4.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/5069965e5be35362431cbcc068faf001df475de5.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/a0f312573473d51f9d6640efb31c0d2f04021586.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/9743ddd0f217ecc61bd34ee4e826e95b97c1ff83.patch | git am -3 --ignore-whitespace
curl -s https://github.com/appcelerator/titanium_mobile/commit/80a398ab13041ffcf87c0f6a15b1f06ba28de33c.patch | git am -3 --ignore-whitespace
# Push it to GitHub
git push --set-upstream origin backport-12029-to-9_3_X

Then, create a pull request where the base branch is 9_3_X and the compare/head branch is backport-12029-to-9_3_X.

@sgtcoolguy
Copy link
Contributor

The backport to 9_3_X failed, but I have manually merged the master branch to 9_3_X, so these changes should be ported over.

@hansemannn
Copy link
Collaborator

Quick feedback after more testing: The overall app is still very stable and this PR handles all of our list- and table-view scenarios properly. The only "glitch" we noticed is that the selection / ripple is very light on dark mode. It's #9c9c9d (right now) vs #4d4d50 (used for example in the Play Store app).

@hansemannn
Copy link
Collaborator

And this one is a regression we cannot workaround: https://jira.appcelerator.org/browse/AC-6644

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants