diff --git a/CHANGELOG.TXT b/CHANGELOG.TXT new file mode 100644 index 0000000..702f8ea --- /dev/null +++ b/CHANGELOG.TXT @@ -0,0 +1,12 @@ +v2.0.2 +- added translucency to the navigation bar on iOS + +v2.0.1 +- added a little separation between the arrows and the text in the stations list +- fine tuned the positioning, centering items when they are supposed to be centered +- changed text from "sosește" to "sosire", in order to match "plecare" + +v2.0.0 +Rewritten! +- separate UI for Android and iOS +- uses WebView to get data on device instead of relying on server \ No newline at end of file diff --git a/assets/lines/atc.json b/assets/lines/atc.json new file mode 100644 index 0000000..14c3b18 --- /dev/null +++ b/assets/lines/atc.json @@ -0,0 +1 @@ +{"short_name":"ATC","operator":"Astra Trans Carpatic","data_export":"20171212","valabil":{"de_la":"20171210","pana_la":"20181208"},"versiune":"1","trenuri":[{"rang":"IR","numar":"15510","numar_intern":15510},{"rang":"IR","numar":"15511","numar_intern":15511},{"rang":"IR","numar":"15512","numar_intern":15512},{"rang":"IR","numar":"15513*","numar_intern":15513},{"rang":"IR","numar":"15513","numar_intern":15513},{"rang":"IR","numar":"15514","numar_intern":15514},{"rang":"IR","numar":"15514","numar_intern":15514},{"rang":"IR","numar":"15516","numar_intern":15516},{"rang":"IR","numar":"15520","numar_intern":15520},{"rang":"IR","numar":"15521*","numar_intern":15521},{"rang":"IR","numar":"15521","numar_intern":15521},{"rang":"IR","numar":"15522","numar_intern":15522},{"rang":"IR","numar":"15522","numar_intern":15522},{"rang":"IR","numar":"15523","numar_intern":15523},{"rang":"IR","numar":"15523","numar_intern":15523},{"rang":"IR","numar":"15527","numar_intern":15527},{"rang":"IR","numar":"15528","numar_intern":15528},{"rang":"IR","numar":"15529","numar_intern":15529},{"rang":"IR","numar":"15531","numar_intern":15531},{"rang":"IR","numar":"15532","numar_intern":15532},{"rang":"IR","numar":"15533","numar_intern":15533},{"rang":"IR","numar":"15533e","numar_intern":15533},{"rang":"IR","numar":"15534","numar_intern":15534},{"rang":"IR","numar":"15535","numar_intern":15535},{"rang":"IR","numar":"15536","numar_intern":15536},{"rang":"IR","numar":"15537-2","numar_intern":15537},{"rang":"IR","numar":"15538","numar_intern":15538},{"rang":"IR","numar":"15540","numar_intern":15540},{"rang":"IR","numar":"15541","numar_intern":15541},{"rang":"IR","numar":"15541","numar_intern":15541},{"rang":"IR","numar":"15542","numar_intern":15542},{"rang":"IR","numar":"15542","numar_intern":15542},{"rang":"IR","numar":"15543","numar_intern":15543},{"rang":"IR","numar":"15544","numar_intern":15544},{"rang":"IR","numar":"15545","numar_intern":15545},{"rang":"IR","numar":"15546*","numar_intern":15546},{"rang":"IR","numar":"15546","numar_intern":15546},{"rang":"IR","numar":"15547","numar_intern":15547},{"rang":"IR","numar":"15548","numar_intern":15548},{"rang":"IR","numar":"15549*","numar_intern":15549},{"rang":"IR","numar":"15549b","numar_intern":15549},{"rang":"IR","numar":"15551","numar_intern":15551},{"rang":"IR","numar":"15552","numar_intern":15552},{"rang":"IR","numar":"15553","numar_intern":15553},{"rang":"IR","numar":"15581","numar_intern":15581},{"rang":"IR","numar":"15582","numar_intern":15582},{"rang":"IR","numar":"15582***","numar_intern":15582},{"rang":"IR","numar":"15583","numar_intern":15583},{"rang":"IR","numar":"15590","numar_intern":15590},{"rang":"IR","numar":"15591","numar_intern":15591},{"rang":"IR","numar":"15592","numar_intern":15592},{"rang":"IR","numar":"15593","numar_intern":15593},{"rang":"R","numar":"*P18801","numar_intern":null},{"rang":"IR","numar":"*15546","numar_intern":null},{"rang":"R","numar":"**P18801","numar_intern":null},{"rang":"IR","numar":"*15591","numar_intern":null},{"rang":"R","numar":"**P18800","numar_intern":null},{"rang":"IR","numar":"15593","numar_intern":15593},{"rang":"IR","numar":"15594","numar_intern":15594},{"rang":"IR","numar":"15595-2","numar_intern":15595},{"rang":"R","numar":"18800","numar_intern":18800},{"rang":"R","numar":"18801","numar_intern":18801},{"rang":"IR","numar":"18826","numar_intern":18826},{"rang":"IR","numar":"18851","numar_intern":18851},{"rang":"IR","numar":"18852","numar_intern":18852}]} \ No newline at end of file diff --git a/assets/lines/cfr.json b/assets/lines/cfr.json new file mode 100644 index 0000000..4853d4c --- /dev/null +++ b/assets/lines/cfr.json @@ -0,0 +1 @@ +{"short_name":"CFR","operator":"SNTFC \"CFR Călători\" S.A.","data_export":"20181212","valabil":{"de_la":"20181209","pana_la":"20191214"},"versiune":"1","trenuri":[{"rang":"IR","numar":"72-1","numar_intern":72},{"rang":"IR","numar":"73-2","numar_intern":73},{"rang":"IR","numar":"74-1","numar_intern":74},{"rang":"IR","numar":"75-2","numar_intern":75},{"rang":"IR","numar":"78-1","numar_intern":78},{"rang":"IR","numar":"79-2","numar_intern":79},{"rang":"IR","numar":"143-2","numar_intern":143},{"rang":"IR","numar":"144-1","numar_intern":144},{"rang":"IR","numar":"346-1","numar_intern":346},{"rang":"IR","numar":"347-2","numar_intern":347},{"rang":"R","numar":"364-1","numar_intern":364},{"rang":"R","numar":"365-2","numar_intern":365},{"rang":"IR","numar":"366-1","numar_intern":366},{"rang":"IR","numar":"367-2","numar_intern":367},{"rang":"R","numar":"368-1","numar_intern":368},{"rang":"R","numar":"369-2","numar_intern":369},{"rang":"IR","numar":"380-1","numar_intern":380},{"rang":"IR","numar":"381","numar_intern":381},{"rang":"IR","numar":"401","numar_intern":401},{"rang":"IR","numar":"402-1","numar_intern":402},{"rang":"IR","numar":"406-1","numar_intern":406},{"rang":"IR","numar":"407-2","numar_intern":407},{"rang":"IR","numar":"460","numar_intern":460},{"rang":"IR","numar":"461","numar_intern":461},{"rang":"IR","numar":"472-1","numar_intern":472},{"rang":"IR","numar":"473-2","numar_intern":473},{"rang":"IR","numar":"1003","numar_intern":1003},{"rang":"IR","numar":"1004","numar_intern":1004},{"rang":"R","numar":"1061","numar_intern":1061},{"rang":"R","numar":"1062","numar_intern":1062},{"rang":"R","numar":"1063","numar_intern":1063},{"rang":"R","numar":"1064","numar_intern":1064},{"rang":"R","numar":"1090","numar_intern":1090},{"rang":"R","numar":"1091","numar_intern":1091},{"rang":"IR","numar":"1094","numar_intern":1094},{"rang":"IR","numar":"1095","numar_intern":1095},{"rang":"R","numar":"1384-1","numar_intern":1384},{"rang":"R","numar":"1385","numar_intern":1385},{"rang":"IR","numar":"1521","numar_intern":1521},{"rang":"IR","numar":"1522","numar_intern":1522},{"rang":"IR","numar":"1528","numar_intern":1528},{"rang":"IR","numar":"1529","numar_intern":1529},{"rang":"IR","numar":"1531","numar_intern":1531},{"rang":"IR","numar":"1532","numar_intern":1532},{"rang":"IR","numar":"1533","numar_intern":1533},{"rang":"IR","numar":"1534","numar_intern":1534},{"rang":"IR","numar":"1535","numar_intern":1535},{"rang":"IR","numar":"1536-1","numar_intern":1536},{"rang":"IR","numar":"1537","numar_intern":1537},{"rang":"IR","numar":"1538","numar_intern":1538},{"rang":"IR","numar":"1539","numar_intern":1539},{"rang":"IR","numar":"1543-2","numar_intern":1543},{"rang":"IR","numar":"1544","numar_intern":1544},{"rang":"IR","numar":"1545-2","numar_intern":1545},{"rang":"IR","numar":"1546","numar_intern":1546},{"rang":"IR","numar":"1547-2","numar_intern":1547},{"rang":"IR","numar":"1548","numar_intern":1548},{"rang":"IR","numar":"1550-1","numar_intern":1550},{"rang":"IR","numar":"1551","numar_intern":1551},{"rang":"IR","numar":"1552","numar_intern":1552},{"rang":"IR","numar":"1553","numar_intern":1553},{"rang":"IR","numar":"1555","numar_intern":1555},{"rang":"IR","numar":"1556","numar_intern":1556},{"rang":"IR","numar":"1557","numar_intern":1557},{"rang":"IR","numar":"1558","numar_intern":1558},{"rang":"IR","numar":"1559","numar_intern":1559},{"rang":"IR","numar":"1570","numar_intern":1570},{"rang":"IR","numar":"1571","numar_intern":1571},{"rang":"IR","numar":"1573","numar_intern":1573},{"rang":"IR","numar":"1574","numar_intern":1574},{"rang":"IR","numar":"1575","numar_intern":1575},{"rang":"IR","numar":"1576","numar_intern":1576},{"rang":"IR","numar":"1578","numar_intern":1578},{"rang":"IR","numar":"1579","numar_intern":1579},{"rang":"IR","numar":"1580","numar_intern":1580},{"rang":"IR","numar":"1581","numar_intern":1581},{"rang":"IR","numar":"1582","numar_intern":1582},{"rang":"IR","numar":"1583","numar_intern":1583},{"rang":"IR","numar":"1584","numar_intern":1584},{"rang":"IR","numar":"1585","numar_intern":1585},{"rang":"IR","numar":"1586","numar_intern":1586},{"rang":"IR","numar":"1587","numar_intern":1587},{"rang":"IR","numar":"1588","numar_intern":1588},{"rang":"IR","numar":"1589","numar_intern":1589},{"rang":"IR","numar":"1590","numar_intern":1590},{"rang":"IR","numar":"1591","numar_intern":1591},{"rang":"IR","numar":"1592","numar_intern":1592},{"rang":"IR","numar":"1593","numar_intern":1593},{"rang":"IR","numar":"1594","numar_intern":1594},{"rang":"IR","numar":"1595","numar_intern":1595},{"rang":"IR","numar":"1596","numar_intern":1596},{"rang":"IR","numar":"1597","numar_intern":1597},{"rang":"IR","numar":"1623","numar_intern":1623},{"rang":"IR","numar":"1624","numar_intern":1624},{"rang":"IR","numar":"1630","numar_intern":1630},{"rang":"IR","numar":"1631","numar_intern":1631},{"rang":"IR","numar":"1632","numar_intern":1632},{"rang":"IR","numar":"1633","numar_intern":1633},{"rang":"IR","numar":"1634","numar_intern":1634},{"rang":"IR","numar":"1636","numar_intern":1636},{"rang":"IR","numar":"1638","numar_intern":1638},{"rang":"IR","numar":"1639","numar_intern":1639},{"rang":"IR","numar":"1641","numar_intern":1641},{"rang":"IR","numar":"1642","numar_intern":1642},{"rang":"IR","numar":"1643","numar_intern":1643},{"rang":"IR","numar":"1644","numar_intern":1644},{"rang":"IR","numar":"1645","numar_intern":1645},{"rang":"IR","numar":"1646","numar_intern":1646},{"rang":"IR","numar":"1648","numar_intern":1648},{"rang":"IR","numar":"1649","numar_intern":1649},{"rang":"IR","numar":"1653","numar_intern":1653},{"rang":"IR","numar":"1654","numar_intern":1654},{"rang":"IR","numar":"1655","numar_intern":1655},{"rang":"IR","numar":"1656","numar_intern":1656},{"rang":"IR","numar":"1657","numar_intern":1657},{"rang":"IR","numar":"1658","numar_intern":1658},{"rang":"IR","numar":"1660","numar_intern":1660},{"rang":"IR","numar":"1661","numar_intern":1661},{"rang":"IR","numar":"1662","numar_intern":1662},{"rang":"IR","numar":"1663","numar_intern":1663},{"rang":"IR","numar":"1664","numar_intern":1664},{"rang":"IR","numar":"1665","numar_intern":1665},{"rang":"IR","numar":"1667","numar_intern":1667},{"rang":"IR","numar":"1668","numar_intern":1668},{"rang":"IR","numar":"1681","numar_intern":1681},{"rang":"IR","numar":"1682","numar_intern":1682},{"rang":"IR","numar":"1683","numar_intern":1683},{"rang":"IR","numar":"1684","numar_intern":1684},{"rang":"IR","numar":"1691","numar_intern":1691},{"rang":"IR","numar":"1692","numar_intern":1692},{"rang":"IR","numar":"1693","numar_intern":1693},{"rang":"IR","numar":"1695","numar_intern":1695},{"rang":"IR","numar":"1696","numar_intern":1696},{"rang":"IR","numar":"1698","numar_intern":1698},{"rang":"IR","numar":"1699","numar_intern":1699},{"rang":"IR","numar":"1720","numar_intern":1720},{"rang":"IR","numar":"1721-2","numar_intern":1721},{"rang":"IR","numar":"1722","numar_intern":1722},{"rang":"IR","numar":"1723","numar_intern":1723},{"rang":"IR","numar":"1724","numar_intern":1724},{"rang":"IR","numar":"1725-2","numar_intern":1725},{"rang":"IR","numar":"1726","numar_intern":1726},{"rang":"IR","numar":"1727","numar_intern":1727},{"rang":"IR","numar":"1728","numar_intern":1728},{"rang":"IR","numar":"1729","numar_intern":1729},{"rang":"IR","numar":"1731-2","numar_intern":1731},{"rang":"IR","numar":"1732","numar_intern":1732},{"rang":"IR","numar":"1736-1","numar_intern":1736},{"rang":"IR","numar":"1737","numar_intern":1737},{"rang":"IR","numar":"1738","numar_intern":1738},{"rang":"IR","numar":"1739","numar_intern":1739},{"rang":"IR","numar":"1741","numar_intern":1741},{"rang":"IR","numar":"1742","numar_intern":1742},{"rang":"IR","numar":"1743","numar_intern":1743},{"rang":"IR","numar":"1744","numar_intern":1744},{"rang":"IR","numar":"1745","numar_intern":1745},{"rang":"IR","numar":"1746","numar_intern":1746},{"rang":"IR","numar":"1748","numar_intern":1748},{"rang":"IR","numar":"1749","numar_intern":1749},{"rang":"IR","numar":"1750","numar_intern":1750},{"rang":"IR","numar":"1751","numar_intern":1751},{"rang":"IR","numar":"1752","numar_intern":1752},{"rang":"IR","numar":"1753","numar_intern":1753},{"rang":"IR","numar":"1754","numar_intern":1754},{"rang":"IR","numar":"1755","numar_intern":1755},{"rang":"IR","numar":"1756","numar_intern":1756},{"rang":"IR","numar":"1757","numar_intern":1757},{"rang":"IR","numar":"1765","numar_intern":1765},{"rang":"IR","numar":"1766-1","numar_intern":1766},{"rang":"IR","numar":"1770","numar_intern":1770},{"rang":"IR","numar":"1771","numar_intern":1771},{"rang":"IR","numar":"1772","numar_intern":1772},{"rang":"IR","numar":"1773","numar_intern":1773},{"rang":"IR","numar":"1774","numar_intern":1774},{"rang":"IR","numar":"1775","numar_intern":1775},{"rang":"IR","numar":"1780","numar_intern":1780},{"rang":"IR","numar":"1781","numar_intern":1781},{"rang":"IR","numar":"1782","numar_intern":1782},{"rang":"IR","numar":"1783","numar_intern":1783},{"rang":"IR","numar":"1784","numar_intern":1784},{"rang":"IR","numar":"1785","numar_intern":1785},{"rang":"IR","numar":"1786","numar_intern":1786},{"rang":"IR","numar":"1787","numar_intern":1787},{"rang":"IR","numar":"1788","numar_intern":1788},{"rang":"IR","numar":"1789","numar_intern":1789},{"rang":"IR","numar":"1790","numar_intern":1790},{"rang":"IR","numar":"1791","numar_intern":1791},{"rang":"IR","numar":"1792","numar_intern":1792},{"rang":"IR","numar":"1793","numar_intern":1793},{"rang":"IR","numar":"1794","numar_intern":1794},{"rang":"IR","numar":"1795","numar_intern":1795},{"rang":"IR","numar":"1811","numar_intern":1811},{"rang":"IR","numar":"1812-1","numar_intern":1812},{"rang":"IR","numar":"1818-1","numar_intern":1818},{"rang":"IR","numar":"1819","numar_intern":1819},{"rang":"IR","numar":"1821","numar_intern":1821},{"rang":"IR","numar":"1822","numar_intern":1822},{"rang":"IR","numar":"1823","numar_intern":1823},{"rang":"IR","numar":"1824","numar_intern":1824},{"rang":"IR","numar":"1825","numar_intern":1825},{"rang":"IR","numar":"1826","numar_intern":1826},{"rang":"IR","numar":"1831-2","numar_intern":1831},{"rang":"IR","numar":"1832","numar_intern":1832},{"rang":"IR","numar":"1833","numar_intern":1833},{"rang":"IR","numar":"1834-1","numar_intern":1834},{"rang":"IR","numar":"1835","numar_intern":1835},{"rang":"IR","numar":"1836","numar_intern":1836},{"rang":"IR","numar":"1837","numar_intern":1837},{"rang":"IR","numar":"1838-1","numar_intern":1838},{"rang":"IR","numar":"1851","numar_intern":1851},{"rang":"IR","numar":"1852-1","numar_intern":1852},{"rang":"IR","numar":"1856","numar_intern":1856},{"rang":"IR","numar":"1857","numar_intern":1857},{"rang":"IR","numar":"1858","numar_intern":1858},{"rang":"IR","numar":"1859","numar_intern":1859},{"rang":"IR","numar":"1861-2","numar_intern":1861},{"rang":"IR","numar":"1862","numar_intern":1862},{"rang":"IR","numar":"1863-2","numar_intern":1863},{"rang":"IR","numar":"1866","numar_intern":1866},{"rang":"IR","numar":"1880","numar_intern":1880},{"rang":"IR","numar":"1881","numar_intern":1881},{"rang":"IR","numar":"1882","numar_intern":1882},{"rang":"IR","numar":"1883","numar_intern":1883},{"rang":"IR","numar":"1884","numar_intern":1884},{"rang":"IR","numar":"1885","numar_intern":1885},{"rang":"IR","numar":"1886","numar_intern":1886},{"rang":"IR","numar":"1887","numar_intern":1887},{"rang":"IR","numar":"1888","numar_intern":1888},{"rang":"IR","numar":"1889","numar_intern":1889},{"rang":"IR","numar":"1890","numar_intern":1890},{"rang":"IR","numar":"1891","numar_intern":1891},{"rang":"IR","numar":"1892","numar_intern":1892},{"rang":"IR","numar":"1893","numar_intern":1893},{"rang":"IR","numar":"1894","numar_intern":1894},{"rang":"IR","numar":"1895","numar_intern":1895},{"rang":"IR","numar":"1896","numar_intern":1896},{"rang":"IR","numar":"1897","numar_intern":1897},{"rang":"IR","numar":"1921-2","numar_intern":1921},{"rang":"IR","numar":"1922","numar_intern":1922},{"rang":"IR","numar":"1923-2","numar_intern":1923},{"rang":"IR","numar":"1924","numar_intern":1924},{"rang":"IR","numar":"1925","numar_intern":1925},{"rang":"IR","numar":"1926","numar_intern":1926},{"rang":"IR","numar":"1928","numar_intern":1928},{"rang":"IR","numar":"1929-2","numar_intern":1929},{"rang":"IR","numar":"1931-2","numar_intern":1931},{"rang":"IR","numar":"1932","numar_intern":1932},{"rang":"IR","numar":"1936","numar_intern":1936},{"rang":"IR","numar":"1937-2","numar_intern":1937},{"rang":"IR","numar":"1941-2","numar_intern":1941},{"rang":"IR","numar":"1942","numar_intern":1942},{"rang":"IR","numar":"1944-1","numar_intern":1944},{"rang":"IR","numar":"1945-2","numar_intern":1945},{"rang":"IR","numar":"1948","numar_intern":1948},{"rang":"IR","numar":"1949","numar_intern":1949},{"rang":"IR","numar":"1952","numar_intern":1952},{"rang":"IR","numar":"1953-2","numar_intern":1953},{"rang":"IR","numar":"1961-2","numar_intern":1961},{"rang":"IR","numar":"1962","numar_intern":1962},{"rang":"IR","numar":"1963-2","numar_intern":1963},{"rang":"IR","numar":"1964","numar_intern":1964},{"rang":"IR","numar":"1968","numar_intern":1968},{"rang":"IR","numar":"1969","numar_intern":1969},{"rang":"IR","numar":"1970","numar_intern":1970},{"rang":"IR","numar":"1971-2","numar_intern":1971},{"rang":"IR","numar":"1983","numar_intern":1983},{"rang":"IR","numar":"1984","numar_intern":1984},{"rang":"IR","numar":"1986","numar_intern":1986},{"rang":"IR","numar":"1987","numar_intern":1987},{"rang":"IR","numar":"1988","numar_intern":1988},{"rang":"IR","numar":"1989","numar_intern":1989},{"rang":"IR","numar":"1991-2","numar_intern":1991},{"rang":"IR","numar":"1992","numar_intern":1992},{"rang":"IR","numar":"1993-2","numar_intern":1993},{"rang":"IR","numar":"1994","numar_intern":1994},{"rang":"IR","numar":"1995-2","numar_intern":1995},{"rang":"IR","numar":"1996","numar_intern":1996},{"rang":"R","numar":"2023","numar_intern":2023},{"rang":"R","numar":"2024","numar_intern":2024},{"rang":"R","numar":"2027","numar_intern":2027},{"rang":"R","numar":"2028-1","numar_intern":2028},{"rang":"R","numar":"2029","numar_intern":2029},{"rang":"R","numar":"2031","numar_intern":2031},{"rang":"R","numar":"2033","numar_intern":2033},{"rang":"R","numar":"2038","numar_intern":2038},{"rang":"R","numar":"2039-2","numar_intern":2039},{"rang":"R","numar":"2041","numar_intern":2041},{"rang":"R","numar":"2042","numar_intern":2042},{"rang":"R","numar":"2043","numar_intern":2043},{"rang":"R","numar":"2044","numar_intern":2044},{"rang":"R","numar":"2045","numar_intern":2045},{"rang":"R","numar":"2046","numar_intern":2046},{"rang":"R","numar":"2047","numar_intern":2047},{"rang":"R","numar":"2048","numar_intern":2048},{"rang":"R","numar":"2050","numar_intern":2050},{"rang":"R","numar":"2052","numar_intern":2052},{"rang":"R","numar":"2061-2","numar_intern":2061},{"rang":"R","numar":"2063","numar_intern":2063},{"rang":"R","numar":"2064","numar_intern":2064},{"rang":"R","numar":"2068","numar_intern":2068},{"rang":"R","numar":"2069","numar_intern":2069},{"rang":"R","numar":"2070","numar_intern":2070},{"rang":"R","numar":"2071","numar_intern":2071},{"rang":"R","numar":"2072","numar_intern":2072},{"rang":"R","numar":"2074","numar_intern":2074},{"rang":"R","numar":"2075","numar_intern":2075},{"rang":"R","numar":"2076","numar_intern":2076},{"rang":"R","numar":"2079","numar_intern":2079},{"rang":"R","numar":"2080","numar_intern":2080},{"rang":"R","numar":"2081","numar_intern":2081},{"rang":"R","numar":"2082","numar_intern":2082},{"rang":"R","numar":"2083","numar_intern":2083},{"rang":"R","numar":"2084","numar_intern":2084},{"rang":"R","numar":"2085","numar_intern":2085},{"rang":"R","numar":"2086","numar_intern":2086},{"rang":"R","numar":"2087","numar_intern":2087},{"rang":"R","numar":"2088","numar_intern":2088},{"rang":"R","numar":"2089","numar_intern":2089},{"rang":"R","numar":"2091","numar_intern":2091},{"rang":"R","numar":"2092","numar_intern":2092},{"rang":"R","numar":"2093","numar_intern":2093},{"rang":"R","numar":"2094","numar_intern":2094},{"rang":"R","numar":"2095","numar_intern":2095},{"rang":"R","numar":"2101","numar_intern":2101},{"rang":"R","numar":"2102","numar_intern":2102},{"rang":"R","numar":"2103","numar_intern":2103},{"rang":"R","numar":"2104","numar_intern":2104},{"rang":"R","numar":"2105","numar_intern":2105},{"rang":"R","numar":"2181","numar_intern":2181},{"rang":"R","numar":"2182","numar_intern":2182},{"rang":"R","numar":"2183","numar_intern":2183},{"rang":"R","numar":"2184","numar_intern":2184},{"rang":"R","numar":"2185","numar_intern":2185},{"rang":"R","numar":"2186","numar_intern":2186},{"rang":"R","numar":"2188","numar_intern":2188},{"rang":"R","numar":"2201","numar_intern":2201},{"rang":"R","numar":"2202","numar_intern":2202},{"rang":"R","numar":"2203","numar_intern":2203},{"rang":"R","numar":"2204","numar_intern":2204},{"rang":"R","numar":"2205","numar_intern":2205},{"rang":"R","numar":"2206","numar_intern":2206},{"rang":"R","numar":"2207","numar_intern":2207},{"rang":"R","numar":"2208","numar_intern":2208},{"rang":"R","numar":"2209","numar_intern":2209},{"rang":"R","numar":"2210","numar_intern":2210},{"rang":"R","numar":"2211","numar_intern":2211},{"rang":"R","numar":"2212","numar_intern":2212},{"rang":"R","numar":"2213","numar_intern":2213},{"rang":"R","numar":"2214","numar_intern":2214},{"rang":"R","numar":"2215","numar_intern":2215},{"rang":"R","numar":"2216","numar_intern":2216},{"rang":"R","numar":"2230","numar_intern":2230},{"rang":"R","numar":"2231","numar_intern":2231},{"rang":"R","numar":"2232","numar_intern":2232},{"rang":"R","numar":"2233","numar_intern":2233},{"rang":"R","numar":"2234","numar_intern":2234},{"rang":"R","numar":"2235","numar_intern":2235},{"rang":"R","numar":"2302","numar_intern":2302},{"rang":"R","numar":"2307","numar_intern":2307},{"rang":"R","numar":"2311","numar_intern":2311},{"rang":"R","numar":"2312","numar_intern":2312},{"rang":"R","numar":"2372","numar_intern":2372},{"rang":"R","numar":"2374","numar_intern":2374},{"rang":"R","numar":"2381","numar_intern":2381},{"rang":"R","numar":"2383","numar_intern":2383},{"rang":"R","numar":"2384","numar_intern":2384},{"rang":"R","numar":"2385","numar_intern":2385},{"rang":"R","numar":"2386","numar_intern":2386},{"rang":"R","numar":"2411","numar_intern":2411},{"rang":"R","numar":"2412-1","numar_intern":2412},{"rang":"R","numar":"2415","numar_intern":2415},{"rang":"R","numar":"2416-1","numar_intern":2416},{"rang":"R","numar":"2420","numar_intern":2420},{"rang":"R","numar":"2421","numar_intern":2421},{"rang":"R","numar":"2422","numar_intern":2422},{"rang":"R","numar":"2423","numar_intern":2423},{"rang":"R","numar":"2434","numar_intern":2434},{"rang":"R","numar":"2437","numar_intern":2437},{"rang":"R","numar":"2438","numar_intern":2438},{"rang":"R","numar":"2441","numar_intern":2441},{"rang":"R","numar":"2442-1","numar_intern":2442},{"rang":"R","numar":"2443","numar_intern":2443},{"rang":"R","numar":"2444-1","numar_intern":2444},{"rang":"R","numar":"2461","numar_intern":2461},{"rang":"R","numar":"2464","numar_intern":2464},{"rang":"R","numar":"2474","numar_intern":2474},{"rang":"R","numar":"2481","numar_intern":2481},{"rang":"R","numar":"2482","numar_intern":2482},{"rang":"R","numar":"2496","numar_intern":2496},{"rang":"R","numar":"2501","numar_intern":2501},{"rang":"R","numar":"2502","numar_intern":2502},{"rang":"R","numar":"2503","numar_intern":2503},{"rang":"R","numar":"2506","numar_intern":2506},{"rang":"R","numar":"2511","numar_intern":2511},{"rang":"R","numar":"2512","numar_intern":2512},{"rang":"R","numar":"2513","numar_intern":2513},{"rang":"R","numar":"2514","numar_intern":2514},{"rang":"R","numar":"2515","numar_intern":2515},{"rang":"R","numar":"2516","numar_intern":2516},{"rang":"R","numar":"2517","numar_intern":2517},{"rang":"R","numar":"2518","numar_intern":2518},{"rang":"R","numar":"2560-1","numar_intern":2560},{"rang":"R","numar":"2561","numar_intern":2561},{"rang":"R","numar":"2562-1","numar_intern":2562},{"rang":"R","numar":"2563","numar_intern":2563},{"rang":"R","numar":"2564-1","numar_intern":2564},{"rang":"R","numar":"2565","numar_intern":2565},{"rang":"R","numar":"2566-1","numar_intern":2566},{"rang":"R","numar":"2567","numar_intern":2567},{"rang":"R","numar":"2568-1","numar_intern":2568},{"rang":"R","numar":"2569","numar_intern":2569},{"rang":"R","numar":"2570-1","numar_intern":2570},{"rang":"R","numar":"2571","numar_intern":2571},{"rang":"R","numar":"2572-1","numar_intern":2572},{"rang":"R","numar":"2591","numar_intern":2591},{"rang":"R","numar":"2592","numar_intern":2592},{"rang":"R","numar":"2593","numar_intern":2593},{"rang":"R","numar":"2594","numar_intern":2594},{"rang":"R","numar":"2595","numar_intern":2595},{"rang":"R","numar":"2601","numar_intern":2601},{"rang":"R","numar":"2602","numar_intern":2602},{"rang":"R","numar":"2603","numar_intern":2603},{"rang":"R","numar":"2604","numar_intern":2604},{"rang":"R","numar":"2605","numar_intern":2605},{"rang":"R","numar":"2606","numar_intern":2606},{"rang":"R","numar":"2608","numar_intern":2608},{"rang":"R","numar":"2610","numar_intern":2610},{"rang":"R","numar":"2611","numar_intern":2611},{"rang":"R","numar":"2613","numar_intern":2613},{"rang":"R","numar":"2700","numar_intern":2700},{"rang":"R","numar":"2701","numar_intern":2701},{"rang":"R","numar":"2702","numar_intern":2702},{"rang":"R","numar":"2703","numar_intern":2703},{"rang":"R","numar":"2704","numar_intern":2704},{"rang":"R","numar":"2705","numar_intern":2705},{"rang":"R","numar":"2707","numar_intern":2707},{"rang":"R","numar":"2708","numar_intern":2708},{"rang":"R","numar":"2709","numar_intern":2709},{"rang":"R","numar":"2710","numar_intern":2710},{"rang":"R","numar":"2711","numar_intern":2711},{"rang":"R","numar":"2713","numar_intern":2713},{"rang":"R","numar":"2724","numar_intern":2724},{"rang":"R","numar":"2850","numar_intern":2850},{"rang":"R","numar":"2851","numar_intern":2851},{"rang":"R","numar":"2852","numar_intern":2852},{"rang":"R","numar":"2853","numar_intern":2853},{"rang":"R","numar":"2854","numar_intern":2854},{"rang":"R","numar":"2855","numar_intern":2855},{"rang":"R","numar":"2856","numar_intern":2856},{"rang":"R","numar":"2857","numar_intern":2857},{"rang":"R","numar":"2860","numar_intern":2860},{"rang":"R","numar":"2870","numar_intern":2870},{"rang":"R","numar":"2871","numar_intern":2871},{"rang":"R","numar":"2872","numar_intern":2872},{"rang":"R","numar":"2873","numar_intern":2873},{"rang":"R","numar":"3001","numar_intern":3001},{"rang":"R","numar":"3002","numar_intern":3002},{"rang":"R","numar":"3003","numar_intern":3003},{"rang":"R","numar":"3004","numar_intern":3004},{"rang":"R","numar":"3005","numar_intern":3005},{"rang":"R","numar":"3006","numar_intern":3006},{"rang":"R","numar":"3007","numar_intern":3007},{"rang":"R","numar":"3008","numar_intern":3008},{"rang":"R","numar":"3009","numar_intern":3009},{"rang":"R","numar":"3010","numar_intern":3010},{"rang":"R","numar":"3011","numar_intern":3011},{"rang":"R","numar":"3012","numar_intern":3012},{"rang":"R","numar":"3017","numar_intern":3017},{"rang":"R","numar":"3019","numar_intern":3019},{"rang":"R","numar":"3023","numar_intern":3023},{"rang":"R","numar":"3024","numar_intern":3024},{"rang":"R","numar":"3071","numar_intern":3071},{"rang":"R","numar":"3072","numar_intern":3072},{"rang":"R","numar":"3073","numar_intern":3073},{"rang":"R","numar":"3074","numar_intern":3074},{"rang":"R","numar":"3075","numar_intern":3075},{"rang":"R","numar":"3076","numar_intern":3076},{"rang":"R","numar":"3081","numar_intern":3081},{"rang":"R","numar":"3082","numar_intern":3082},{"rang":"R","numar":"3083","numar_intern":3083},{"rang":"R","numar":"3084","numar_intern":3084},{"rang":"R","numar":"3085","numar_intern":3085},{"rang":"R","numar":"3086","numar_intern":3086},{"rang":"R","numar":"3087","numar_intern":3087},{"rang":"R","numar":"3088","numar_intern":3088},{"rang":"R","numar":"3089","numar_intern":3089},{"rang":"R","numar":"3090","numar_intern":3090},{"rang":"R","numar":"3091","numar_intern":3091},{"rang":"R","numar":"3111","numar_intern":3111},{"rang":"R","numar":"3112","numar_intern":3112},{"rang":"R","numar":"3113","numar_intern":3113},{"rang":"R","numar":"3114","numar_intern":3114},{"rang":"R","numar":"3115","numar_intern":3115},{"rang":"R","numar":"3116","numar_intern":3116},{"rang":"R","numar":"3117","numar_intern":3117},{"rang":"R","numar":"3118","numar_intern":3118},{"rang":"R","numar":"3122","numar_intern":3122},{"rang":"R","numar":"3500","numar_intern":3500},{"rang":"R","numar":"3501","numar_intern":3501},{"rang":"R","numar":"3503","numar_intern":3503},{"rang":"R","numar":"3511","numar_intern":3511},{"rang":"R","numar":"3512","numar_intern":3512},{"rang":"R","numar":"3513","numar_intern":3513},{"rang":"R","numar":"3514","numar_intern":3514},{"rang":"R","numar":"3528","numar_intern":3528},{"rang":"R","numar":"3530","numar_intern":3530},{"rang":"R","numar":"3531","numar_intern":3531},{"rang":"R","numar":"3532","numar_intern":3532},{"rang":"R","numar":"3533","numar_intern":3533},{"rang":"R","numar":"3534","numar_intern":3534},{"rang":"R","numar":"3535","numar_intern":3535},{"rang":"R","numar":"3536","numar_intern":3536},{"rang":"R","numar":"3541","numar_intern":3541},{"rang":"R","numar":"3542","numar_intern":3542},{"rang":"R","numar":"3543","numar_intern":3543},{"rang":"R","numar":"3544","numar_intern":3544},{"rang":"R","numar":"3545","numar_intern":3545},{"rang":"R","numar":"3546","numar_intern":3546},{"rang":"R","numar":"3547","numar_intern":3547},{"rang":"R","numar":"3548","numar_intern":3548},{"rang":"R","numar":"3549","numar_intern":3549},{"rang":"R","numar":"3550","numar_intern":3550},{"rang":"R","numar":"3612","numar_intern":3612},{"rang":"R","numar":"3620","numar_intern":3620},{"rang":"R","numar":"3622","numar_intern":3622},{"rang":"R","numar":"3623","numar_intern":3623},{"rang":"R","numar":"3624","numar_intern":3624},{"rang":"R","numar":"3627","numar_intern":3627},{"rang":"R","numar":"3628","numar_intern":3628},{"rang":"R","numar":"3629","numar_intern":3629},{"rang":"R","numar":"3630","numar_intern":3630},{"rang":"R","numar":"3633","numar_intern":3633},{"rang":"R","numar":"3635","numar_intern":3635},{"rang":"R","numar":"3637","numar_intern":3637},{"rang":"R","numar":"3640","numar_intern":3640},{"rang":"R","numar":"3641","numar_intern":3641},{"rang":"R","numar":"3642","numar_intern":3642},{"rang":"R","numar":"3643","numar_intern":3643},{"rang":"R","numar":"3644","numar_intern":3644},{"rang":"R","numar":"3645","numar_intern":3645},{"rang":"R","numar":"3646","numar_intern":3646},{"rang":"R","numar":"3728","numar_intern":3728},{"rang":"R","numar":"3745","numar_intern":3745},{"rang":"R","numar":"3746","numar_intern":3746},{"rang":"R","numar":"3747","numar_intern":3747},{"rang":"R","numar":"4041","numar_intern":4041},{"rang":"R","numar":"4042","numar_intern":4042},{"rang":"R","numar":"4043","numar_intern":4043},{"rang":"R","numar":"4044","numar_intern":4044},{"rang":"R","numar":"4045","numar_intern":4045},{"rang":"R","numar":"4046","numar_intern":4046},{"rang":"R","numar":"4047","numar_intern":4047},{"rang":"R","numar":"4048","numar_intern":4048},{"rang":"R","numar":"4049","numar_intern":4049},{"rang":"R","numar":"4050","numar_intern":4050},{"rang":"R","numar":"4070","numar_intern":4070},{"rang":"R","numar":"4071","numar_intern":4071},{"rang":"R","numar":"4072","numar_intern":4072},{"rang":"R","numar":"4073","numar_intern":4073},{"rang":"R","numar":"4074","numar_intern":4074},{"rang":"R","numar":"4076","numar_intern":4076},{"rang":"R","numar":"4082","numar_intern":4082},{"rang":"R","numar":"4083","numar_intern":4083},{"rang":"R","numar":"4084","numar_intern":4084},{"rang":"R","numar":"4085-2","numar_intern":4085},{"rang":"R","numar":"4091","numar_intern":4091},{"rang":"R","numar":"4092","numar_intern":4092},{"rang":"R","numar":"4094","numar_intern":4094},{"rang":"R","numar":"4095-2","numar_intern":4095},{"rang":"R","numar":"4101","numar_intern":4101},{"rang":"R","numar":"4102","numar_intern":4102},{"rang":"R","numar":"4103","numar_intern":4103},{"rang":"R","numar":"4104","numar_intern":4104},{"rang":"R","numar":"4105","numar_intern":4105},{"rang":"R","numar":"4106","numar_intern":4106},{"rang":"R","numar":"4107","numar_intern":4107},{"rang":"R","numar":"4108","numar_intern":4108},{"rang":"R","numar":"4109","numar_intern":4109},{"rang":"R","numar":"4110","numar_intern":4110},{"rang":"R","numar":"4111-2","numar_intern":4111},{"rang":"R","numar":"4133-2","numar_intern":4133},{"rang":"R","numar":"4134","numar_intern":4134},{"rang":"R","numar":"4135-2","numar_intern":4135},{"rang":"R","numar":"4136","numar_intern":4136},{"rang":"R","numar":"4141","numar_intern":4141},{"rang":"R","numar":"4142-1","numar_intern":4142},{"rang":"R","numar":"4143","numar_intern":4143},{"rang":"R","numar":"4144-1","numar_intern":4144},{"rang":"R","numar":"4145-2","numar_intern":4145},{"rang":"R","numar":"4146-1","numar_intern":4146},{"rang":"R","numar":"4205","numar_intern":4205},{"rang":"R","numar":"4206-1","numar_intern":4206},{"rang":"R","numar":"4301","numar_intern":4301},{"rang":"R","numar":"4302","numar_intern":4302},{"rang":"R","numar":"4303","numar_intern":4303},{"rang":"R","numar":"4305","numar_intern":4305},{"rang":"R","numar":"4306","numar_intern":4306},{"rang":"R","numar":"4307","numar_intern":4307},{"rang":"R","numar":"4308","numar_intern":4308},{"rang":"R","numar":"4310","numar_intern":4310},{"rang":"R","numar":"4311","numar_intern":4311},{"rang":"R","numar":"4312","numar_intern":4312},{"rang":"R","numar":"4313","numar_intern":4313},{"rang":"R","numar":"4314","numar_intern":4314},{"rang":"R","numar":"4315","numar_intern":4315},{"rang":"R","numar":"4316","numar_intern":4316},{"rang":"R","numar":"4317","numar_intern":4317},{"rang":"R","numar":"4318","numar_intern":4318},{"rang":"R","numar":"4321","numar_intern":4321},{"rang":"R","numar":"4322","numar_intern":4322},{"rang":"R","numar":"4324","numar_intern":4324},{"rang":"R","numar":"4330","numar_intern":4330},{"rang":"R","numar":"4331","numar_intern":4331},{"rang":"R","numar":"4332","numar_intern":4332},{"rang":"R","numar":"4333","numar_intern":4333},{"rang":"R","numar":"4334","numar_intern":4334},{"rang":"R","numar":"4335","numar_intern":4335},{"rang":"R","numar":"4336","numar_intern":4336},{"rang":"R","numar":"4337","numar_intern":4337},{"rang":"R","numar":"4338","numar_intern":4338},{"rang":"R","numar":"4339","numar_intern":4339},{"rang":"R","numar":"4340","numar_intern":4340},{"rang":"R","numar":"4342","numar_intern":4342},{"rang":"R","numar":"4361","numar_intern":4361},{"rang":"R","numar":"4362","numar_intern":4362},{"rang":"R","numar":"4363","numar_intern":4363},{"rang":"R","numar":"4364","numar_intern":4364},{"rang":"R","numar":"4365","numar_intern":4365},{"rang":"R","numar":"4366","numar_intern":4366},{"rang":"R","numar":"4367","numar_intern":4367},{"rang":"R","numar":"4371","numar_intern":4371},{"rang":"R","numar":"4372","numar_intern":4372},{"rang":"R","numar":"4375","numar_intern":4375},{"rang":"R","numar":"4376","numar_intern":4376},{"rang":"R","numar":"4402","numar_intern":4402},{"rang":"R","numar":"4403","numar_intern":4403},{"rang":"R","numar":"4404","numar_intern":4404},{"rang":"R","numar":"4405","numar_intern":4405},{"rang":"R","numar":"4406","numar_intern":4406},{"rang":"R","numar":"4407","numar_intern":4407},{"rang":"R","numar":"4408","numar_intern":4408},{"rang":"R","numar":"4409","numar_intern":4409},{"rang":"R","numar":"4410","numar_intern":4410},{"rang":"R","numar":"4411","numar_intern":4411},{"rang":"R","numar":"4412","numar_intern":4412},{"rang":"R","numar":"4413","numar_intern":4413},{"rang":"R","numar":"4415","numar_intern":4415},{"rang":"R","numar":"4422","numar_intern":4422},{"rang":"R","numar":"4423","numar_intern":4423},{"rang":"R","numar":"4441","numar_intern":4441},{"rang":"R","numar":"4442","numar_intern":4442},{"rang":"R","numar":"4451","numar_intern":4451},{"rang":"R","numar":"4452","numar_intern":4452},{"rang":"R","numar":"4453","numar_intern":4453},{"rang":"R","numar":"4454","numar_intern":4454},{"rang":"R","numar":"4455","numar_intern":4455},{"rang":"R","numar":"4456","numar_intern":4456},{"rang":"R","numar":"4483","numar_intern":4483},{"rang":"R","numar":"4484","numar_intern":4484},{"rang":"R","numar":"4485","numar_intern":4485},{"rang":"R","numar":"4486","numar_intern":4486},{"rang":"R","numar":"4490","numar_intern":4490},{"rang":"R","numar":"4501","numar_intern":4501},{"rang":"R","numar":"4502","numar_intern":4502},{"rang":"R","numar":"4503","numar_intern":4503},{"rang":"R","numar":"4504","numar_intern":4504},{"rang":"R","numar":"4505","numar_intern":4505},{"rang":"R","numar":"4506","numar_intern":4506},{"rang":"R","numar":"4507","numar_intern":4507},{"rang":"R","numar":"4508","numar_intern":4508},{"rang":"R","numar":"4509","numar_intern":4509},{"rang":"R","numar":"4510","numar_intern":4510},{"rang":"R","numar":"4511","numar_intern":4511},{"rang":"R","numar":"4512","numar_intern":4512},{"rang":"R","numar":"4513","numar_intern":4513},{"rang":"R","numar":"4515","numar_intern":4515},{"rang":"R","numar":"4516","numar_intern":4516},{"rang":"R","numar":"4517","numar_intern":4517},{"rang":"R","numar":"4518","numar_intern":4518},{"rang":"R","numar":"4519","numar_intern":4519},{"rang":"R","numar":"4520","numar_intern":4520},{"rang":"R","numar":"4521","numar_intern":4521},{"rang":"R","numar":"4522","numar_intern":4522},{"rang":"R","numar":"4523","numar_intern":4523},{"rang":"R","numar":"4524","numar_intern":4524},{"rang":"R","numar":"4525","numar_intern":4525},{"rang":"R","numar":"4526","numar_intern":4526},{"rang":"R","numar":"4527","numar_intern":4527},{"rang":"R","numar":"4528","numar_intern":4528},{"rang":"R","numar":"4530","numar_intern":4530},{"rang":"R","numar":"4531","numar_intern":4531},{"rang":"R","numar":"4532","numar_intern":4532},{"rang":"R","numar":"4533","numar_intern":4533},{"rang":"R","numar":"4534","numar_intern":4534},{"rang":"R","numar":"4535","numar_intern":4535},{"rang":"R","numar":"4536","numar_intern":4536},{"rang":"R","numar":"4537","numar_intern":4537},{"rang":"R","numar":"4540","numar_intern":4540},{"rang":"R","numar":"4541","numar_intern":4541},{"rang":"R","numar":"4542","numar_intern":4542},{"rang":"R","numar":"4543","numar_intern":4543},{"rang":"R","numar":"4544","numar_intern":4544},{"rang":"R","numar":"4545","numar_intern":4545},{"rang":"R","numar":"4546","numar_intern":4546},{"rang":"R","numar":"4547","numar_intern":4547},{"rang":"R","numar":"4548","numar_intern":4548},{"rang":"R","numar":"4549","numar_intern":4549},{"rang":"R","numar":"4550","numar_intern":4550},{"rang":"R","numar":"4580","numar_intern":4580},{"rang":"R","numar":"4581","numar_intern":4581},{"rang":"R","numar":"4582","numar_intern":4582},{"rang":"R","numar":"4583","numar_intern":4583},{"rang":"R","numar":"4584","numar_intern":4584},{"rang":"R","numar":"4585","numar_intern":4585},{"rang":"R","numar":"4586","numar_intern":4586},{"rang":"R","numar":"4650","numar_intern":4650},{"rang":"R","numar":"4651","numar_intern":4651},{"rang":"R","numar":"4652","numar_intern":4652},{"rang":"R","numar":"4653","numar_intern":4653},{"rang":"R","numar":"4654","numar_intern":4654},{"rang":"R","numar":"4656","numar_intern":4656},{"rang":"R","numar":"4701","numar_intern":4701},{"rang":"R","numar":"4702","numar_intern":4702},{"rang":"R","numar":"4703","numar_intern":4703},{"rang":"R","numar":"4704","numar_intern":4704},{"rang":"R","numar":"4705","numar_intern":4705},{"rang":"R","numar":"5001","numar_intern":5001},{"rang":"R","numar":"5002","numar_intern":5002},{"rang":"R","numar":"5003","numar_intern":5003},{"rang":"R","numar":"5005","numar_intern":5005},{"rang":"R","numar":"5006","numar_intern":5006},{"rang":"R","numar":"5007","numar_intern":5007},{"rang":"R","numar":"5008","numar_intern":5008},{"rang":"R","numar":"5010","numar_intern":5010},{"rang":"R","numar":"5021","numar_intern":5021},{"rang":"R","numar":"5022","numar_intern":5022},{"rang":"R","numar":"5023","numar_intern":5023},{"rang":"R","numar":"5024","numar_intern":5024},{"rang":"R","numar":"5025","numar_intern":5025},{"rang":"R","numar":"5026","numar_intern":5026},{"rang":"R","numar":"5027","numar_intern":5027},{"rang":"R","numar":"5051","numar_intern":5051},{"rang":"R","numar":"5052","numar_intern":5052},{"rang":"R","numar":"5053","numar_intern":5053},{"rang":"R","numar":"5054","numar_intern":5054},{"rang":"R","numar":"5055","numar_intern":5055},{"rang":"R","numar":"5056","numar_intern":5056},{"rang":"R","numar":"5057","numar_intern":5057},{"rang":"R","numar":"5071","numar_intern":5071},{"rang":"R","numar":"5072","numar_intern":5072},{"rang":"R","numar":"5073","numar_intern":5073},{"rang":"R","numar":"5074","numar_intern":5074},{"rang":"R","numar":"5076","numar_intern":5076},{"rang":"R","numar":"5101","numar_intern":5101},{"rang":"R","numar":"5103","numar_intern":5103},{"rang":"R","numar":"5104","numar_intern":5104},{"rang":"R","numar":"5105-2","numar_intern":5105},{"rang":"R","numar":"5106","numar_intern":5106},{"rang":"R","numar":"5107","numar_intern":5107},{"rang":"R","numar":"5108","numar_intern":5108},{"rang":"R","numar":"5109","numar_intern":5109},{"rang":"R","numar":"5110","numar_intern":5110},{"rang":"R","numar":"5111","numar_intern":5111},{"rang":"R","numar":"5112","numar_intern":5112},{"rang":"R","numar":"5201","numar_intern":5201},{"rang":"R","numar":"5202","numar_intern":5202},{"rang":"R","numar":"5203","numar_intern":5203},{"rang":"R","numar":"5204-1","numar_intern":5204},{"rang":"R","numar":"5205","numar_intern":5205},{"rang":"R","numar":"5206","numar_intern":5206},{"rang":"R","numar":"5207","numar_intern":5207},{"rang":"R","numar":"5208-1","numar_intern":5208},{"rang":"R","numar":"5209","numar_intern":5209},{"rang":"R","numar":"5210","numar_intern":5210},{"rang":"R","numar":"5212-1","numar_intern":5212},{"rang":"R","numar":"5213","numar_intern":5213},{"rang":"R","numar":"5214-1","numar_intern":5214},{"rang":"R","numar":"5216","numar_intern":5216},{"rang":"R","numar":"5301","numar_intern":5301},{"rang":"R","numar":"5302","numar_intern":5302},{"rang":"R","numar":"5303","numar_intern":5303},{"rang":"R","numar":"5304","numar_intern":5304},{"rang":"R","numar":"5306","numar_intern":5306},{"rang":"R","numar":"5311","numar_intern":5311},{"rang":"R","numar":"5312","numar_intern":5312},{"rang":"R","numar":"5401","numar_intern":5401},{"rang":"R","numar":"5402","numar_intern":5402},{"rang":"R","numar":"5403","numar_intern":5403},{"rang":"R","numar":"5404-1","numar_intern":5404},{"rang":"R","numar":"5405","numar_intern":5405},{"rang":"R","numar":"5406-1","numar_intern":5406},{"rang":"R","numar":"5411","numar_intern":5411},{"rang":"R","numar":"5412","numar_intern":5412},{"rang":"R","numar":"5413","numar_intern":5413},{"rang":"R","numar":"5414","numar_intern":5414},{"rang":"R","numar":"5420-1","numar_intern":5420},{"rang":"R","numar":"5421","numar_intern":5421},{"rang":"R","numar":"5422-1","numar_intern":5422},{"rang":"R","numar":"5423","numar_intern":5423},{"rang":"R","numar":"5424-1","numar_intern":5424},{"rang":"R","numar":"5426-1","numar_intern":5426},{"rang":"R","numar":"5431","numar_intern":5431},{"rang":"R","numar":"5432","numar_intern":5432},{"rang":"R","numar":"5433","numar_intern":5433},{"rang":"R","numar":"5442","numar_intern":5442},{"rang":"R","numar":"5443","numar_intern":5443},{"rang":"R","numar":"5444","numar_intern":5444},{"rang":"R","numar":"5460","numar_intern":5460},{"rang":"R","numar":"5461","numar_intern":5461},{"rang":"R","numar":"5462","numar_intern":5462},{"rang":"R","numar":"5463","numar_intern":5463},{"rang":"R","numar":"5464","numar_intern":5464},{"rang":"R","numar":"5465","numar_intern":5465},{"rang":"R","numar":"5466","numar_intern":5466},{"rang":"R","numar":"5467","numar_intern":5467},{"rang":"R","numar":"5468","numar_intern":5468},{"rang":"R","numar":"5471","numar_intern":5471},{"rang":"R","numar":"5472","numar_intern":5472},{"rang":"R","numar":"5473","numar_intern":5473},{"rang":"R","numar":"5474","numar_intern":5474},{"rang":"R","numar":"5481","numar_intern":5481},{"rang":"R","numar":"5482","numar_intern":5482},{"rang":"R","numar":"5501","numar_intern":5501},{"rang":"R","numar":"5502","numar_intern":5502},{"rang":"R","numar":"5503","numar_intern":5503},{"rang":"R","numar":"5504","numar_intern":5504},{"rang":"R","numar":"5505","numar_intern":5505},{"rang":"R","numar":"5506","numar_intern":5506},{"rang":"R","numar":"5508","numar_intern":5508},{"rang":"R","numar":"5520","numar_intern":5520},{"rang":"R","numar":"5521","numar_intern":5521},{"rang":"R","numar":"5522","numar_intern":5522},{"rang":"R","numar":"5523","numar_intern":5523},{"rang":"R","numar":"5524","numar_intern":5524},{"rang":"R","numar":"5525","numar_intern":5525},{"rang":"R","numar":"5526","numar_intern":5526},{"rang":"R","numar":"5527","numar_intern":5527},{"rang":"R","numar":"5560","numar_intern":5560},{"rang":"R","numar":"5561-2","numar_intern":5561},{"rang":"R","numar":"5562","numar_intern":5562},{"rang":"R","numar":"5563-2","numar_intern":5563},{"rang":"R","numar":"5564","numar_intern":5564},{"rang":"R","numar":"5565-2","numar_intern":5565},{"rang":"R","numar":"5566","numar_intern":5566},{"rang":"R","numar":"5567-2","numar_intern":5567},{"rang":"R","numar":"5568","numar_intern":5568},{"rang":"R","numar":"5570","numar_intern":5570},{"rang":"R","numar":"5581","numar_intern":5581},{"rang":"R","numar":"5582","numar_intern":5582},{"rang":"R","numar":"5583","numar_intern":5583},{"rang":"R","numar":"5584","numar_intern":5584},{"rang":"R","numar":"5601","numar_intern":5601},{"rang":"R","numar":"5602","numar_intern":5602},{"rang":"R","numar":"5603","numar_intern":5603},{"rang":"R","numar":"5604","numar_intern":5604},{"rang":"R","numar":"5605","numar_intern":5605},{"rang":"R","numar":"5606","numar_intern":5606},{"rang":"R","numar":"5608","numar_intern":5608},{"rang":"R","numar":"5610","numar_intern":5610},{"rang":"R","numar":"5611","numar_intern":5611},{"rang":"R","numar":"5612","numar_intern":5612},{"rang":"R","numar":"5613","numar_intern":5613},{"rang":"R","numar":"5620","numar_intern":5620},{"rang":"R","numar":"5621","numar_intern":5621},{"rang":"R","numar":"5622","numar_intern":5622},{"rang":"R","numar":"5623","numar_intern":5623},{"rang":"R","numar":"5624","numar_intern":5624},{"rang":"R","numar":"5625","numar_intern":5625},{"rang":"R","numar":"5626","numar_intern":5626},{"rang":"R","numar":"5627","numar_intern":5627},{"rang":"R","numar":"5628","numar_intern":5628},{"rang":"R","numar":"5630","numar_intern":5630},{"rang":"R","numar":"5631","numar_intern":5631},{"rang":"R","numar":"5632","numar_intern":5632},{"rang":"R","numar":"5633","numar_intern":5633},{"rang":"R","numar":"5634","numar_intern":5634},{"rang":"R","numar":"5635","numar_intern":5635},{"rang":"R","numar":"5636","numar_intern":5636},{"rang":"R","numar":"5637","numar_intern":5637},{"rang":"R","numar":"5638","numar_intern":5638},{"rang":"R","numar":"5701","numar_intern":5701},{"rang":"R","numar":"5702","numar_intern":5702},{"rang":"R","numar":"5703","numar_intern":5703},{"rang":"R","numar":"5704","numar_intern":5704},{"rang":"R","numar":"5705","numar_intern":5705},{"rang":"R","numar":"5706","numar_intern":5706},{"rang":"R","numar":"5710","numar_intern":5710},{"rang":"R","numar":"5711","numar_intern":5711},{"rang":"R","numar":"5712","numar_intern":5712},{"rang":"R","numar":"5720","numar_intern":5720},{"rang":"R","numar":"5721","numar_intern":5721},{"rang":"R","numar":"5722","numar_intern":5722},{"rang":"R","numar":"5723","numar_intern":5723},{"rang":"R","numar":"5730","numar_intern":5730},{"rang":"R","numar":"5731","numar_intern":5731},{"rang":"R","numar":"5732","numar_intern":5732},{"rang":"R","numar":"5733","numar_intern":5733},{"rang":"R","numar":"5734","numar_intern":5734},{"rang":"R","numar":"5735","numar_intern":5735},{"rang":"R","numar":"5736","numar_intern":5736},{"rang":"R","numar":"5737","numar_intern":5737},{"rang":"R","numar":"5738","numar_intern":5738},{"rang":"R","numar":"5739","numar_intern":5739},{"rang":"R","numar":"5741","numar_intern":5741},{"rang":"R","numar":"5743","numar_intern":5743},{"rang":"R","numar":"6311","numar_intern":6311},{"rang":"R","numar":"6312","numar_intern":6312},{"rang":"R","numar":"6313","numar_intern":6313},{"rang":"R","numar":"6314","numar_intern":6314},{"rang":"R","numar":"6315","numar_intern":6315},{"rang":"R","numar":"6316","numar_intern":6316},{"rang":"R","numar":"6401","numar_intern":6401},{"rang":"R","numar":"6402","numar_intern":6402},{"rang":"R","numar":"6403","numar_intern":6403},{"rang":"R","numar":"6404","numar_intern":6404},{"rang":"R","numar":"6406","numar_intern":6406},{"rang":"R","numar":"6408","numar_intern":6408},{"rang":"R","numar":"6411","numar_intern":6411},{"rang":"R","numar":"6412","numar_intern":6412},{"rang":"R","numar":"6413","numar_intern":6413},{"rang":"R","numar":"6414","numar_intern":6414},{"rang":"R","numar":"6415","numar_intern":6415},{"rang":"R","numar":"6441","numar_intern":6441},{"rang":"R","numar":"6442","numar_intern":6442},{"rang":"R","numar":"6443","numar_intern":6443},{"rang":"R","numar":"6444","numar_intern":6444},{"rang":"R","numar":"6501","numar_intern":6501},{"rang":"R","numar":"6502","numar_intern":6502},{"rang":"R","numar":"6503","numar_intern":6503},{"rang":"R","numar":"6504","numar_intern":6504},{"rang":"R","numar":"6505","numar_intern":6505},{"rang":"R","numar":"6506","numar_intern":6506},{"rang":"R","numar":"6507","numar_intern":6507},{"rang":"R","numar":"6508","numar_intern":6508},{"rang":"R","numar":"6811","numar_intern":6811},{"rang":"R","numar":"6812","numar_intern":6812},{"rang":"R","numar":"6822","numar_intern":6822},{"rang":"R","numar":"6823-2","numar_intern":6823},{"rang":"R","numar":"6826","numar_intern":6826},{"rang":"R","numar":"6827-2","numar_intern":6827},{"rang":"R","numar":"7031","numar_intern":7031},{"rang":"R","numar":"7032","numar_intern":7032},{"rang":"R","numar":"7033","numar_intern":7033},{"rang":"R","numar":"7034","numar_intern":7034},{"rang":"R","numar":"7035","numar_intern":7035},{"rang":"R","numar":"7036","numar_intern":7036},{"rang":"R","numar":"7037","numar_intern":7037},{"rang":"R","numar":"7038","numar_intern":7038},{"rang":"R","numar":"7360","numar_intern":7360},{"rang":"R","numar":"7361","numar_intern":7361},{"rang":"R","numar":"7362","numar_intern":7362},{"rang":"R","numar":"7363","numar_intern":7363},{"rang":"R","numar":"7364","numar_intern":7364},{"rang":"R","numar":"7365","numar_intern":7365},{"rang":"R","numar":"7366","numar_intern":7366},{"rang":"R","numar":"7371","numar_intern":7371},{"rang":"R","numar":"7372","numar_intern":7372},{"rang":"R","numar":"7375","numar_intern":7375},{"rang":"R","numar":"7381","numar_intern":7381},{"rang":"R","numar":"7385","numar_intern":7385},{"rang":"R","numar":"7401","numar_intern":7401},{"rang":"R","numar":"7402","numar_intern":7402},{"rang":"R","numar":"7565","numar_intern":7565},{"rang":"R","numar":"7566","numar_intern":7566},{"rang":"R","numar":"7567","numar_intern":7567},{"rang":"R","numar":"7568","numar_intern":7568},{"rang":"R","numar":"7569","numar_intern":7569},{"rang":"R","numar":"7570","numar_intern":7570},{"rang":"R","numar":"7571","numar_intern":7571},{"rang":"R","numar":"7572","numar_intern":7572},{"rang":"R","numar":"7573","numar_intern":7573},{"rang":"R","numar":"7574","numar_intern":7574},{"rang":"R","numar":"7575","numar_intern":7575},{"rang":"R","numar":"7576","numar_intern":7576},{"rang":"R","numar":"7584","numar_intern":7584},{"rang":"R","numar":"7585","numar_intern":7585},{"rang":"R","numar":"7586","numar_intern":7586},{"rang":"R","numar":"7587","numar_intern":7587},{"rang":"R","numar":"7588","numar_intern":7588},{"rang":"R","numar":"8001","numar_intern":8001},{"rang":"R","numar":"8002","numar_intern":8002},{"rang":"R","numar":"8003","numar_intern":8003},{"rang":"R","numar":"8004","numar_intern":8004},{"rang":"R","numar":"8011","numar_intern":8011},{"rang":"R","numar":"8012","numar_intern":8012},{"rang":"R","numar":"8013","numar_intern":8013},{"rang":"R","numar":"8014","numar_intern":8014},{"rang":"R","numar":"8015","numar_intern":8015},{"rang":"R","numar":"8016","numar_intern":8016},{"rang":"R","numar":"8021","numar_intern":8021},{"rang":"R","numar":"8023","numar_intern":8023},{"rang":"R","numar":"8024","numar_intern":8024},{"rang":"R","numar":"8025","numar_intern":8025},{"rang":"R","numar":"8031","numar_intern":8031},{"rang":"R","numar":"8032","numar_intern":8032},{"rang":"R","numar":"8033","numar_intern":8033},{"rang":"R","numar":"8034","numar_intern":8034},{"rang":"R","numar":"8036","numar_intern":8036},{"rang":"R","numar":"8081","numar_intern":8081},{"rang":"R","numar":"8082-1","numar_intern":8082},{"rang":"R","numar":"8083","numar_intern":8083},{"rang":"R","numar":"8084-1","numar_intern":8084},{"rang":"R","numar":"8121","numar_intern":8121},{"rang":"R","numar":"8122","numar_intern":8122},{"rang":"R","numar":"8123","numar_intern":8123},{"rang":"R","numar":"8124","numar_intern":8124},{"rang":"R","numar":"8151","numar_intern":8151},{"rang":"R","numar":"8152","numar_intern":8152},{"rang":"R","numar":"8153","numar_intern":8153},{"rang":"R","numar":"8154","numar_intern":8154},{"rang":"R","numar":"8201","numar_intern":8201},{"rang":"R","numar":"8202","numar_intern":8202},{"rang":"R","numar":"8203","numar_intern":8203},{"rang":"R","numar":"8204","numar_intern":8204},{"rang":"R","numar":"8224","numar_intern":8224},{"rang":"R","numar":"8343-2","numar_intern":8343},{"rang":"R","numar":"8344","numar_intern":8344},{"rang":"R","numar":"8345-2","numar_intern":8345},{"rang":"R","numar":"8346","numar_intern":8346},{"rang":"R","numar":"8370","numar_intern":8370},{"rang":"R","numar":"8371","numar_intern":8371},{"rang":"R","numar":"8372","numar_intern":8372},{"rang":"R","numar":"8380","numar_intern":8380},{"rang":"R","numar":"8381","numar_intern":8381},{"rang":"R","numar":"8382","numar_intern":8382},{"rang":"R","numar":"8383","numar_intern":8383},{"rang":"R","numar":"8384","numar_intern":8384},{"rang":"R","numar":"8385","numar_intern":8385},{"rang":"R","numar":"8386","numar_intern":8386},{"rang":"R","numar":"8387","numar_intern":8387},{"rang":"R","numar":"8388","numar_intern":8388},{"rang":"R","numar":"8389","numar_intern":8389},{"rang":"R","numar":"8390","numar_intern":8390},{"rang":"R","numar":"8391","numar_intern":8391},{"rang":"R","numar":"8392","numar_intern":8392},{"rang":"R","numar":"8393","numar_intern":8393},{"rang":"R","numar":"8394","numar_intern":8394},{"rang":"R","numar":"8395","numar_intern":8395},{"rang":"R","numar":"8397","numar_intern":8397},{"rang":"R","numar":"8443","numar_intern":8443},{"rang":"R","numar":"8444","numar_intern":8444},{"rang":"R","numar":"8580","numar_intern":8580},{"rang":"R","numar":"8581","numar_intern":8581},{"rang":"R","numar":"8582","numar_intern":8582},{"rang":"R","numar":"8583","numar_intern":8583},{"rang":"R","numar":"8584","numar_intern":8584},{"rang":"R","numar":"8585","numar_intern":8585},{"rang":"R","numar":"8586","numar_intern":8586},{"rang":"R","numar":"8587","numar_intern":8587},{"rang":"R","numar":"8588","numar_intern":8588},{"rang":"R","numar":"8589","numar_intern":8589},{"rang":"R","numar":"8591","numar_intern":8591},{"rang":"R","numar":"8592","numar_intern":8592},{"rang":"R","numar":"8593","numar_intern":8593},{"rang":"R","numar":"8594","numar_intern":8594},{"rang":"R","numar":"8595","numar_intern":8595},{"rang":"R","numar":"8596","numar_intern":8596},{"rang":"R","numar":"8651","numar_intern":8651},{"rang":"R","numar":"8652","numar_intern":8652},{"rang":"R","numar":"8653","numar_intern":8653},{"rang":"R","numar":"8654","numar_intern":8654},{"rang":"R","numar":"8655-2","numar_intern":8655},{"rang":"R","numar":"8656","numar_intern":8656},{"rang":"R","numar":"8657","numar_intern":8657},{"rang":"R","numar":"8695","numar_intern":8695},{"rang":"R","numar":"8800","numar_intern":8800},{"rang":"R","numar":"8801","numar_intern":8801},{"rang":"R","numar":"8802","numar_intern":8802},{"rang":"R","numar":"8803","numar_intern":8803},{"rang":"R","numar":"8804","numar_intern":8804},{"rang":"R","numar":"8805","numar_intern":8805},{"rang":"R","numar":"8806","numar_intern":8806},{"rang":"R","numar":"8807","numar_intern":8807},{"rang":"R","numar":"8809","numar_intern":8809},{"rang":"R","numar":"9001","numar_intern":9001},{"rang":"R","numar":"9002","numar_intern":9002},{"rang":"R","numar":"9003","numar_intern":9003},{"rang":"R","numar":"9004","numar_intern":9004},{"rang":"R","numar":"9005","numar_intern":9005},{"rang":"R","numar":"9006","numar_intern":9006},{"rang":"R","numar":"9008","numar_intern":9008},{"rang":"R","numar":"9010","numar_intern":9010},{"rang":"R","numar":"9011","numar_intern":9011},{"rang":"R","numar":"9012","numar_intern":9012},{"rang":"R","numar":"9014","numar_intern":9014},{"rang":"R","numar":"9021","numar_intern":9021},{"rang":"R","numar":"9022","numar_intern":9022},{"rang":"R","numar":"9023","numar_intern":9023},{"rang":"R","numar":"9024","numar_intern":9024},{"rang":"R","numar":"9025","numar_intern":9025},{"rang":"R","numar":"9026","numar_intern":9026},{"rang":"R","numar":"9027","numar_intern":9027},{"rang":"R","numar":"9028","numar_intern":9028},{"rang":"R","numar":"9030","numar_intern":9030},{"rang":"R","numar":"9051","numar_intern":9051},{"rang":"R","numar":"9052","numar_intern":9052},{"rang":"R","numar":"9053","numar_intern":9053},{"rang":"R","numar":"9054","numar_intern":9054},{"rang":"R","numar":"9061-2","numar_intern":9061},{"rang":"R","numar":"9062","numar_intern":9062},{"rang":"R","numar":"9063-2","numar_intern":9063},{"rang":"R","numar":"9064","numar_intern":9064},{"rang":"R","numar":"9065-2","numar_intern":9065},{"rang":"R","numar":"9066","numar_intern":9066},{"rang":"R","numar":"9101","numar_intern":9101},{"rang":"R","numar":"9102","numar_intern":9102},{"rang":"R","numar":"9103","numar_intern":9103},{"rang":"R","numar":"9104","numar_intern":9104},{"rang":"R","numar":"9105","numar_intern":9105},{"rang":"R","numar":"9106","numar_intern":9106},{"rang":"R","numar":"9107","numar_intern":9107},{"rang":"R","numar":"9108","numar_intern":9108},{"rang":"R","numar":"9109","numar_intern":9109},{"rang":"R","numar":"9110","numar_intern":9110},{"rang":"R","numar":"9111","numar_intern":9111},{"rang":"R","numar":"9112","numar_intern":9112},{"rang":"R","numar":"9113","numar_intern":9113},{"rang":"R","numar":"9120","numar_intern":9120},{"rang":"R","numar":"9121","numar_intern":9121},{"rang":"R","numar":"9122","numar_intern":9122},{"rang":"R","numar":"9123","numar_intern":9123},{"rang":"R","numar":"9132","numar_intern":9132},{"rang":"R","numar":"9134","numar_intern":9134},{"rang":"R","numar":"9135","numar_intern":9135},{"rang":"R","numar":"9137","numar_intern":9137},{"rang":"R","numar":"9138","numar_intern":9138},{"rang":"R","numar":"9139","numar_intern":9139},{"rang":"R","numar":"9159","numar_intern":9159},{"rang":"R","numar":"9160","numar_intern":9160},{"rang":"R","numar":"9161","numar_intern":9161},{"rang":"R","numar":"9162","numar_intern":9162},{"rang":"R","numar":"9163","numar_intern":9163},{"rang":"R","numar":"9164","numar_intern":9164},{"rang":"R","numar":"9165","numar_intern":9165},{"rang":"R","numar":"9166","numar_intern":9166},{"rang":"R","numar":"9167","numar_intern":9167},{"rang":"R","numar":"9168","numar_intern":9168},{"rang":"R","numar":"9169","numar_intern":9169},{"rang":"R","numar":"9170","numar_intern":9170},{"rang":"R","numar":"9171","numar_intern":9171},{"rang":"R","numar":"9172","numar_intern":9172},{"rang":"R","numar":"9190","numar_intern":9190},{"rang":"R","numar":"9192","numar_intern":9192},{"rang":"R","numar":"9193","numar_intern":9193},{"rang":"R","numar":"9301","numar_intern":9301},{"rang":"R","numar":"9302","numar_intern":9302},{"rang":"R","numar":"9303","numar_intern":9303},{"rang":"R","numar":"9304","numar_intern":9304},{"rang":"R","numar":"9305","numar_intern":9305},{"rang":"R","numar":"9306","numar_intern":9306},{"rang":"R","numar":"9307","numar_intern":9307},{"rang":"R","numar":"9340","numar_intern":9340},{"rang":"R","numar":"9341","numar_intern":9341},{"rang":"R","numar":"9342","numar_intern":9342},{"rang":"R","numar":"9343","numar_intern":9343},{"rang":"R","numar":"9344","numar_intern":9344},{"rang":"R","numar":"9345","numar_intern":9345},{"rang":"R","numar":"9346","numar_intern":9346},{"rang":"R","numar":"9347","numar_intern":9347},{"rang":"R","numar":"9348","numar_intern":9348},{"rang":"R","numar":"9349","numar_intern":9349},{"rang":"R","numar":"9351","numar_intern":9351},{"rang":"R","numar":"9352","numar_intern":9352},{"rang":"R","numar":"9358","numar_intern":9358},{"rang":"R","numar":"9359","numar_intern":9359},{"rang":"R","numar":"9360","numar_intern":9360},{"rang":"R","numar":"9361","numar_intern":9361},{"rang":"R","numar":"9362","numar_intern":9362},{"rang":"R","numar":"9363","numar_intern":9363},{"rang":"R","numar":"9364","numar_intern":9364},{"rang":"R","numar":"9365","numar_intern":9365},{"rang":"R","numar":"9366","numar_intern":9366},{"rang":"R","numar":"9367","numar_intern":9367},{"rang":"R","numar":"9368","numar_intern":9368},{"rang":"R","numar":"9370","numar_intern":9370},{"rang":"R","numar":"9371","numar_intern":9371},{"rang":"R","numar":"9372","numar_intern":9372},{"rang":"R","numar":"9373","numar_intern":9373},{"rang":"R","numar":"9375","numar_intern":9375},{"rang":"R","numar":"9381","numar_intern":9381},{"rang":"R","numar":"9382","numar_intern":9382},{"rang":"R","numar":"9383","numar_intern":9383},{"rang":"R","numar":"9384","numar_intern":9384},{"rang":"R","numar":"9385","numar_intern":9385},{"rang":"R","numar":"9386","numar_intern":9386},{"rang":"R","numar":"9387","numar_intern":9387},{"rang":"R","numar":"9388","numar_intern":9388},{"rang":"R","numar":"9389","numar_intern":9389},{"rang":"R","numar":"9401","numar_intern":9401},{"rang":"R","numar":"9402","numar_intern":9402},{"rang":"R","numar":"9404-1","numar_intern":9404},{"rang":"R","numar":"9405","numar_intern":9405},{"rang":"R","numar":"9411","numar_intern":9411},{"rang":"R","numar":"9414","numar_intern":9414},{"rang":"R","numar":"9416","numar_intern":9416},{"rang":"R","numar":"9432","numar_intern":9432},{"rang":"R","numar":"9433","numar_intern":9433},{"rang":"R","numar":"9434","numar_intern":9434},{"rang":"R","numar":"9435","numar_intern":9435},{"rang":"R","numar":"9440","numar_intern":9440},{"rang":"R","numar":"9441","numar_intern":9441},{"rang":"R","numar":"9442","numar_intern":9442},{"rang":"R","numar":"9445","numar_intern":9445},{"rang":"R","numar":"9446","numar_intern":9446},{"rang":"R","numar":"9447","numar_intern":9447},{"rang":"R","numar":"9448","numar_intern":9448},{"rang":"R","numar":"9471","numar_intern":9471},{"rang":"R","numar":"9472","numar_intern":9472},{"rang":"R","numar":"9473","numar_intern":9473},{"rang":"R","numar":"9475","numar_intern":9475},{"rang":"R","numar":"9476","numar_intern":9476},{"rang":"R","numar":"9477","numar_intern":9477},{"rang":"R","numar":"9478","numar_intern":9478},{"rang":"R","numar":"9479","numar_intern":9479},{"rang":"R","numar":"9480","numar_intern":9480},{"rang":"R","numar":"9481","numar_intern":9481},{"rang":"R","numar":"9482","numar_intern":9482},{"rang":"R","numar":"9483","numar_intern":9483},{"rang":"R","numar":"9484","numar_intern":9484},{"rang":"R","numar":"9485","numar_intern":9485},{"rang":"R","numar":"9486","numar_intern":9486},{"rang":"R","numar":"9501","numar_intern":9501},{"rang":"R","numar":"9502","numar_intern":9502},{"rang":"R","numar":"9503","numar_intern":9503},{"rang":"R","numar":"9504","numar_intern":9504},{"rang":"R","numar":"9505","numar_intern":9505},{"rang":"R","numar":"9506","numar_intern":9506},{"rang":"R","numar":"9507","numar_intern":9507},{"rang":"R","numar":"9508","numar_intern":9508},{"rang":"R","numar":"9512","numar_intern":9512},{"rang":"R","numar":"9530","numar_intern":9530},{"rang":"R","numar":"9531","numar_intern":9531},{"rang":"R","numar":"9532","numar_intern":9532},{"rang":"R","numar":"9533","numar_intern":9533},{"rang":"R","numar":"9534","numar_intern":9534},{"rang":"R","numar":"9535","numar_intern":9535},{"rang":"R","numar":"9551","numar_intern":9551},{"rang":"R","numar":"9552","numar_intern":9552},{"rang":"R","numar":"9553","numar_intern":9553},{"rang":"R","numar":"9554","numar_intern":9554},{"rang":"R","numar":"9556","numar_intern":9556},{"rang":"R","numar":"9557","numar_intern":9557},{"rang":"R","numar":"9571","numar_intern":9571},{"rang":"R","numar":"9572","numar_intern":9572},{"rang":"R","numar":"9573","numar_intern":9573},{"rang":"R","numar":"9575","numar_intern":9575},{"rang":"R","numar":"9576","numar_intern":9576},{"rang":"R","numar":"9577","numar_intern":9577},{"rang":"R","numar":"9578","numar_intern":9578},{"rang":"R","numar":"9580","numar_intern":9580},{"rang":"R","numar":"9608","numar_intern":9608},{"rang":"R","numar":"9610","numar_intern":9610},{"rang":"R","numar":"9611","numar_intern":9611},{"rang":"R","numar":"9612","numar_intern":9612},{"rang":"R","numar":"9613","numar_intern":9613},{"rang":"R","numar":"9614-1","numar_intern":9614},{"rang":"R","numar":"9615","numar_intern":9615},{"rang":"R","numar":"9616-1","numar_intern":9616},{"rang":"R","numar":"9617","numar_intern":9617},{"rang":"R","numar":"9618-1","numar_intern":9618},{"rang":"R","numar":"9619","numar_intern":9619},{"rang":"R","numar":"9620-1","numar_intern":9620},{"rang":"R","numar":"9662","numar_intern":9662},{"rang":"R","numar":"9663","numar_intern":9663},{"rang":"R","numar":"9664","numar_intern":9664},{"rang":"R","numar":"9665","numar_intern":9665},{"rang":"R","numar":"9666","numar_intern":9666},{"rang":"R","numar":"9667","numar_intern":9667},{"rang":"R","numar":"9668","numar_intern":9668},{"rang":"R","numar":"9669","numar_intern":9669},{"rang":"R","numar":"9694","numar_intern":9694},{"rang":"R","numar":"9695","numar_intern":9695},{"rang":"R","numar":"9696","numar_intern":9696},{"rang":"R","numar":"36313","numar_intern":36313},{"rang":"R","numar":"36322","numar_intern":36322},{"rang":"R","numar":"36326","numar_intern":36326},{"rang":"R","numar":"36329","numar_intern":36329},{"rang":"R","numar":"37630","numar_intern":37630},{"rang":"R","numar":"37647","numar_intern":37647},{"rang":"R","numar":"37651","numar_intern":37651},{"rang":"R","numar":"37656","numar_intern":37656}]} \ No newline at end of file diff --git a/assets/lines/files.txt b/assets/lines/files.txt new file mode 100644 index 0000000..ae2d390 --- /dev/null +++ b/assets/lines/files.txt @@ -0,0 +1,6 @@ +atc.json +cfr.json +interregional.json +rc.json +st.json +tfc.json \ No newline at end of file diff --git a/assets/lines/interregional.json b/assets/lines/interregional.json new file mode 100644 index 0000000..c365b90 --- /dev/null +++ b/assets/lines/interregional.json @@ -0,0 +1 @@ +{"short_name":"Interregional","operator":"Interregional Călători","data_export":"20181212","valabil":{"de_la":"20181209","pana_la":"20191214"},"versiune":"1","trenuri":[{"rang":"R","numar":"15700","numar_intern":15700},{"rang":"R","numar":"15701","numar_intern":15701},{"rang":"R","numar":"15702","numar_intern":15702},{"rang":"R","numar":"15703","numar_intern":15703},{"rang":"R","numar":"15704","numar_intern":15704},{"rang":"R","numar":"15705","numar_intern":15705},{"rang":"R","numar":"15706","numar_intern":15706},{"rang":"R","numar":"15707","numar_intern":15707},{"rang":"R","numar":"15750","numar_intern":15750},{"rang":"R","numar":"15751","numar_intern":15751},{"rang":"R","numar":"15752","numar_intern":15752},{"rang":"R","numar":"15753","numar_intern":15753},{"rang":"R","numar":"15754","numar_intern":15754},{"rang":"R","numar":"15755","numar_intern":15755},{"rang":"R","numar":"15756","numar_intern":15756},{"rang":"R","numar":"15757","numar_intern":15757},{"rang":"R","numar":"15758","numar_intern":15758},{"rang":"R","numar":"15759","numar_intern":15759},{"rang":"R","numar":"15800","numar_intern":15800},{"rang":"R","numar":"15801","numar_intern":15801},{"rang":"R","numar":"15802","numar_intern":15802},{"rang":"R","numar":"15803","numar_intern":15803},{"rang":"R","numar":"15804","numar_intern":15804},{"rang":"R","numar":"15805","numar_intern":15805},{"rang":"R","numar":"15806","numar_intern":15806},{"rang":"R","numar":"15807","numar_intern":15807},{"rang":"R","numar":"15808","numar_intern":15808},{"rang":"R","numar":"15809","numar_intern":15809},{"rang":"R","numar":"15810","numar_intern":15810},{"rang":"R","numar":"15811","numar_intern":15811},{"rang":"R","numar":"15812","numar_intern":15812},{"rang":"R","numar":"15813","numar_intern":15813},{"rang":"R","numar":"15814","numar_intern":15814},{"rang":"R","numar":"15815","numar_intern":15815},{"rang":"R","numar":"15816","numar_intern":15816},{"rang":"R","numar":"15817","numar_intern":15817},{"rang":"R","numar":"15818","numar_intern":15818},{"rang":"R","numar":"15819","numar_intern":15819},{"rang":"R","numar":"15820","numar_intern":15820},{"rang":"R","numar":"15821","numar_intern":15821},{"rang":"R","numar":"15822","numar_intern":15822},{"rang":"R","numar":"15823","numar_intern":15823},{"rang":"R","numar":"15824","numar_intern":15824},{"rang":"R","numar":"15825","numar_intern":15825},{"rang":"R","numar":"15826","numar_intern":15826},{"rang":"R","numar":"15827","numar_intern":15827},{"rang":"R","numar":"15828","numar_intern":15828},{"rang":"R","numar":"15829","numar_intern":15829},{"rang":"R","numar":"15830","numar_intern":15830},{"rang":"R","numar":"15831","numar_intern":15831},{"rang":"R","numar":"15832","numar_intern":15832},{"rang":"R","numar":"15833","numar_intern":15833},{"rang":"R","numar":"15834","numar_intern":15834},{"rang":"R","numar":"15835","numar_intern":15835},{"rang":"R","numar":"15836","numar_intern":15836},{"rang":"R","numar":"15837-2","numar_intern":15837},{"rang":"R","numar":"15838","numar_intern":15838},{"rang":"R","numar":"15840","numar_intern":15840},{"rang":"R","numar":"15841","numar_intern":15841},{"rang":"R","numar":"15842","numar_intern":15842},{"rang":"R","numar":"15843","numar_intern":15843},{"rang":"R","numar":"15845","numar_intern":15845},{"rang":"R","numar":"15846","numar_intern":15846},{"rang":"R","numar":"15847","numar_intern":15847},{"rang":"R","numar":"15848","numar_intern":15848},{"rang":"R","numar":"15849","numar_intern":15849},{"rang":"R","numar":"15850","numar_intern":15850},{"rang":"R","numar":"15852","numar_intern":15852},{"rang":"R","numar":"15853","numar_intern":15853},{"rang":"R","numar":"15854","numar_intern":15854},{"rang":"R","numar":"15855","numar_intern":15855},{"rang":"R","numar":"15856","numar_intern":15856},{"rang":"R","numar":"15857","numar_intern":15857},{"rang":"R","numar":"15858","numar_intern":15858},{"rang":"R","numar":"15859","numar_intern":15859},{"rang":"R","numar":"15860","numar_intern":15860},{"rang":"R","numar":"15862","numar_intern":15862},{"rang":"R","numar":"15863","numar_intern":15863},{"rang":"R","numar":"15865","numar_intern":15865},{"rang":"R","numar":"15866","numar_intern":15866},{"rang":"R","numar":"15867","numar_intern":15867},{"rang":"R","numar":"15868","numar_intern":15868},{"rang":"R","numar":"15870","numar_intern":15870},{"rang":"R","numar":"15871","numar_intern":15871},{"rang":"R","numar":"15872","numar_intern":15872},{"rang":"R","numar":"15873","numar_intern":15873},{"rang":"R","numar":"15874","numar_intern":15874},{"rang":"R","numar":"15875","numar_intern":15875},{"rang":"R","numar":"15876","numar_intern":15876},{"rang":"R","numar":"15877","numar_intern":15877},{"rang":"R","numar":"15878","numar_intern":15878},{"rang":"R","numar":"15879","numar_intern":15879},{"rang":"R","numar":"15881","numar_intern":15881},{"rang":"R","numar":"15884","numar_intern":15884},{"rang":"R","numar":"15885","numar_intern":15885},{"rang":"R","numar":"15886","numar_intern":15886},{"rang":"R","numar":"15887","numar_intern":15887},{"rang":"R","numar":"15888","numar_intern":15888},{"rang":"R","numar":"15889","numar_intern":15889},{"rang":"R","numar":"15890","numar_intern":15890},{"rang":"R","numar":"15891","numar_intern":15891},{"rang":"R","numar":"15892","numar_intern":15892},{"rang":"R","numar":"15893-2","numar_intern":15893},{"rang":"R","numar":"15894","numar_intern":15894},{"rang":"R","numar":"15895","numar_intern":15895},{"rang":"R","numar":"15896","numar_intern":15896},{"rang":"R","numar":"15897","numar_intern":15897},{"rang":"R","numar":"15898","numar_intern":15898},{"rang":"R","numar":"15899","numar_intern":15899}]} \ No newline at end of file diff --git a/assets/lines/rc.json b/assets/lines/rc.json new file mode 100644 index 0000000..8450050 --- /dev/null +++ b/assets/lines/rc.json @@ -0,0 +1 @@ +{"short_name":"Regio","operator":"Regio Călători","data_export":"20181212","valabil":{"de_la":"20181209","pana_la":"20191214"},"versiune":"1","trenuri":[{"rang":"R","numar":"16011","numar_intern":16011},{"rang":"R","numar":"16012","numar_intern":16012},{"rang":"R","numar":"16013","numar_intern":16013},{"rang":"R","numar":"16014","numar_intern":16014},{"rang":"R","numar":"16015","numar_intern":16015},{"rang":"R","numar":"16016","numar_intern":16016},{"rang":"R","numar":"16017","numar_intern":16017},{"rang":"R","numar":"16018","numar_intern":16018},{"rang":"R","numar":"16019","numar_intern":16019},{"rang":"R","numar":"16020","numar_intern":16020},{"rang":"R","numar":"16030","numar_intern":16030},{"rang":"R","numar":"16031","numar_intern":16031},{"rang":"R","numar":"16032","numar_intern":16032},{"rang":"R","numar":"16033","numar_intern":16033},{"rang":"R","numar":"16034","numar_intern":16034},{"rang":"R","numar":"16035","numar_intern":16035},{"rang":"R","numar":"16036","numar_intern":16036},{"rang":"R","numar":"16037","numar_intern":16037},{"rang":"R","numar":"16038","numar_intern":16038},{"rang":"R","numar":"16039","numar_intern":16039},{"rang":"IR","numar":"16067-2","numar_intern":16067},{"rang":"IR","numar":"16068","numar_intern":16068},{"rang":"IR","numar":"16088","numar_intern":16088},{"rang":"IR","numar":"16089-2","numar_intern":16089},{"rang":"R","numar":"16100","numar_intern":16100},{"rang":"R","numar":"16101","numar_intern":16101},{"rang":"R","numar":"16102","numar_intern":16102},{"rang":"R","numar":"16103","numar_intern":16103},{"rang":"R","numar":"16104","numar_intern":16104},{"rang":"R","numar":"16105","numar_intern":16105},{"rang":"R","numar":"16106","numar_intern":16106},{"rang":"R","numar":"16107","numar_intern":16107},{"rang":"R","numar":"16108","numar_intern":16108},{"rang":"R","numar":"16109","numar_intern":16109},{"rang":"R","numar":"16110","numar_intern":16110},{"rang":"R","numar":"16111","numar_intern":16111},{"rang":"R","numar":"16113","numar_intern":16113},{"rang":"R","numar":"16120","numar_intern":16120},{"rang":"R","numar":"16121","numar_intern":16121},{"rang":"R","numar":"16122","numar_intern":16122},{"rang":"R","numar":"16123","numar_intern":16123},{"rang":"R","numar":"16124","numar_intern":16124},{"rang":"R","numar":"16125","numar_intern":16125},{"rang":"R","numar":"16126","numar_intern":16126},{"rang":"R","numar":"16127","numar_intern":16127},{"rang":"R","numar":"16142","numar_intern":16142},{"rang":"R","numar":"16144","numar_intern":16144},{"rang":"R","numar":"16145","numar_intern":16145},{"rang":"R","numar":"16146","numar_intern":16146},{"rang":"R","numar":"16147","numar_intern":16147},{"rang":"R","numar":"16148","numar_intern":16148},{"rang":"R","numar":"16149","numar_intern":16149},{"rang":"R","numar":"16150","numar_intern":16150},{"rang":"R","numar":"16151","numar_intern":16151},{"rang":"R","numar":"16152","numar_intern":16152},{"rang":"R","numar":"16153","numar_intern":16153},{"rang":"R","numar":"16154","numar_intern":16154},{"rang":"R","numar":"16155","numar_intern":16155},{"rang":"R","numar":"16156","numar_intern":16156},{"rang":"R","numar":"16157","numar_intern":16157},{"rang":"R","numar":"16168","numar_intern":16168},{"rang":"R","numar":"16169-2","numar_intern":16169},{"rang":"R","numar":"16170","numar_intern":16170},{"rang":"R","numar":"16171-2","numar_intern":16171},{"rang":"R","numar":"16172","numar_intern":16172},{"rang":"R","numar":"16173-2","numar_intern":16173},{"rang":"R","numar":"16174","numar_intern":16174},{"rang":"R","numar":"16175","numar_intern":16175},{"rang":"R","numar":"16176","numar_intern":16176},{"rang":"R","numar":"16177-2","numar_intern":16177},{"rang":"R","numar":"16178","numar_intern":16178},{"rang":"R","numar":"16179-2","numar_intern":16179},{"rang":"R","numar":"16180","numar_intern":16180},{"rang":"R","numar":"16181","numar_intern":16181},{"rang":"R","numar":"16182","numar_intern":16182},{"rang":"R","numar":"16183","numar_intern":16183},{"rang":"R","numar":"16184","numar_intern":16184},{"rang":"R","numar":"16185","numar_intern":16185},{"rang":"R","numar":"16186","numar_intern":16186},{"rang":"R","numar":"16187","numar_intern":16187},{"rang":"R","numar":"16188","numar_intern":16188},{"rang":"R","numar":"16189","numar_intern":16189},{"rang":"R","numar":"16190","numar_intern":16190},{"rang":"R","numar":"16202","numar_intern":16202},{"rang":"R","numar":"16203","numar_intern":16203},{"rang":"R","numar":"16204","numar_intern":16204},{"rang":"R","numar":"16205","numar_intern":16205},{"rang":"R","numar":"16206","numar_intern":16206},{"rang":"R","numar":"16207","numar_intern":16207},{"rang":"R","numar":"16208","numar_intern":16208},{"rang":"R","numar":"16209","numar_intern":16209},{"rang":"R","numar":"16210","numar_intern":16210},{"rang":"R","numar":"16211","numar_intern":16211},{"rang":"R","numar":"16212","numar_intern":16212},{"rang":"R","numar":"16213","numar_intern":16213},{"rang":"R","numar":"16214","numar_intern":16214},{"rang":"R","numar":"16215","numar_intern":16215},{"rang":"R","numar":"16219","numar_intern":16219},{"rang":"R","numar":"16220","numar_intern":16220},{"rang":"R","numar":"16221","numar_intern":16221},{"rang":"R","numar":"16222","numar_intern":16222},{"rang":"R","numar":"16223","numar_intern":16223},{"rang":"R","numar":"16224","numar_intern":16224},{"rang":"R","numar":"16225","numar_intern":16225},{"rang":"R","numar":"16226","numar_intern":16226},{"rang":"R","numar":"16227","numar_intern":16227},{"rang":"R","numar":"16228","numar_intern":16228},{"rang":"R","numar":"16237","numar_intern":16237},{"rang":"R","numar":"16238","numar_intern":16238},{"rang":"R","numar":"16239","numar_intern":16239},{"rang":"R","numar":"16240","numar_intern":16240},{"rang":"R","numar":"16241","numar_intern":16241},{"rang":"R","numar":"16242","numar_intern":16242},{"rang":"R","numar":"16243","numar_intern":16243},{"rang":"R","numar":"16244","numar_intern":16244},{"rang":"R","numar":"16245","numar_intern":16245},{"rang":"R","numar":"16246","numar_intern":16246},{"rang":"R","numar":"16247","numar_intern":16247},{"rang":"R","numar":"16248","numar_intern":16248},{"rang":"R","numar":"16249","numar_intern":16249},{"rang":"R","numar":"16260","numar_intern":16260},{"rang":"R","numar":"16261","numar_intern":16261},{"rang":"R","numar":"16262","numar_intern":16262},{"rang":"R","numar":"16263","numar_intern":16263},{"rang":"R","numar":"16270","numar_intern":16270},{"rang":"R","numar":"16271","numar_intern":16271},{"rang":"R","numar":"16272","numar_intern":16272},{"rang":"R","numar":"16273","numar_intern":16273},{"rang":"R","numar":"16274","numar_intern":16274},{"rang":"R","numar":"16275","numar_intern":16275},{"rang":"R","numar":"16276","numar_intern":16276},{"rang":"R","numar":"16277","numar_intern":16277},{"rang":"R","numar":"16278","numar_intern":16278},{"rang":"R","numar":"16279","numar_intern":16279},{"rang":"R","numar":"16280","numar_intern":16280},{"rang":"R","numar":"16281","numar_intern":16281},{"rang":"R","numar":"16300","numar_intern":16300},{"rang":"R","numar":"16301","numar_intern":16301},{"rang":"R","numar":"16302","numar_intern":16302},{"rang":"R","numar":"16303","numar_intern":16303},{"rang":"R","numar":"16310","numar_intern":16310},{"rang":"R","numar":"16311","numar_intern":16311},{"rang":"R","numar":"16312","numar_intern":16312},{"rang":"R","numar":"16313","numar_intern":16313},{"rang":"R","numar":"16314","numar_intern":16314},{"rang":"R","numar":"16315","numar_intern":16315},{"rang":"R","numar":"16316","numar_intern":16316},{"rang":"R","numar":"16317","numar_intern":16317},{"rang":"R","numar":"16318","numar_intern":16318},{"rang":"R","numar":"16319","numar_intern":16319},{"rang":"R","numar":"16320","numar_intern":16320},{"rang":"R","numar":"16321","numar_intern":16321},{"rang":"R","numar":"16323","numar_intern":16323},{"rang":"R","numar":"16330","numar_intern":16330},{"rang":"R","numar":"16331","numar_intern":16331},{"rang":"R","numar":"16332","numar_intern":16332},{"rang":"R","numar":"16333","numar_intern":16333},{"rang":"R","numar":"16334","numar_intern":16334},{"rang":"R","numar":"16335","numar_intern":16335},{"rang":"R","numar":"16336","numar_intern":16336},{"rang":"R","numar":"16337","numar_intern":16337},{"rang":"R","numar":"16338","numar_intern":16338},{"rang":"R","numar":"16339","numar_intern":16339},{"rang":"R","numar":"16340","numar_intern":16340},{"rang":"R","numar":"16341","numar_intern":16341},{"rang":"R","numar":"16342","numar_intern":16342},{"rang":"R","numar":"16343","numar_intern":16343},{"rang":"R","numar":"16344","numar_intern":16344},{"rang":"R","numar":"16345","numar_intern":16345},{"rang":"R","numar":"16346","numar_intern":16346},{"rang":"R","numar":"16347","numar_intern":16347},{"rang":"R","numar":"16350","numar_intern":16350},{"rang":"R","numar":"16351-2","numar_intern":16351},{"rang":"R","numar":"16352","numar_intern":16352},{"rang":"R","numar":"16353-2","numar_intern":16353},{"rang":"R","numar":"16354","numar_intern":16354},{"rang":"R","numar":"16355-2","numar_intern":16355},{"rang":"R","numar":"16356","numar_intern":16356},{"rang":"R","numar":"16357-2","numar_intern":16357},{"rang":"R","numar":"16360","numar_intern":16360},{"rang":"R","numar":"16361","numar_intern":16361},{"rang":"R","numar":"16362","numar_intern":16362},{"rang":"R","numar":"16363","numar_intern":16363},{"rang":"R","numar":"16364","numar_intern":16364},{"rang":"R","numar":"16365","numar_intern":16365},{"rang":"R","numar":"16366","numar_intern":16366},{"rang":"R","numar":"16367","numar_intern":16367},{"rang":"R","numar":"16368","numar_intern":16368},{"rang":"R","numar":"16500","numar_intern":16500},{"rang":"R","numar":"16501","numar_intern":16501},{"rang":"R","numar":"16502","numar_intern":16502},{"rang":"R","numar":"16503","numar_intern":16503},{"rang":"R","numar":"16504","numar_intern":16504},{"rang":"R","numar":"16505","numar_intern":16505},{"rang":"R","numar":"16506","numar_intern":16506},{"rang":"R","numar":"16507","numar_intern":16507},{"rang":"R","numar":"16508","numar_intern":16508},{"rang":"R","numar":"16509","numar_intern":16509},{"rang":"R","numar":"16510","numar_intern":16510},{"rang":"R","numar":"16511","numar_intern":16511},{"rang":"R","numar":"16512","numar_intern":16512},{"rang":"R","numar":"16513","numar_intern":16513},{"rang":"R","numar":"16514","numar_intern":16514},{"rang":"R","numar":"16515","numar_intern":16515},{"rang":"R","numar":"16516","numar_intern":16516},{"rang":"R","numar":"16517","numar_intern":16517},{"rang":"R","numar":"16518","numar_intern":16518},{"rang":"R","numar":"16519","numar_intern":16519},{"rang":"R","numar":"16520","numar_intern":16520},{"rang":"R","numar":"16521","numar_intern":16521},{"rang":"R","numar":"16522","numar_intern":16522},{"rang":"R","numar":"16523","numar_intern":16523},{"rang":"R","numar":"16524","numar_intern":16524},{"rang":"R","numar":"16525","numar_intern":16525},{"rang":"R","numar":"16527","numar_intern":16527},{"rang":"R","numar":"16540","numar_intern":16540},{"rang":"R","numar":"16541","numar_intern":16541},{"rang":"R","numar":"16542","numar_intern":16542},{"rang":"R","numar":"16543","numar_intern":16543},{"rang":"R","numar":"16544","numar_intern":16544},{"rang":"R","numar":"16545","numar_intern":16545},{"rang":"R","numar":"16546","numar_intern":16546},{"rang":"R","numar":"16547","numar_intern":16547},{"rang":"R","numar":"16548","numar_intern":16548},{"rang":"R","numar":"16549","numar_intern":16549},{"rang":"R","numar":"16550","numar_intern":16550},{"rang":"R","numar":"16551","numar_intern":16551},{"rang":"R","numar":"16670","numar_intern":16670},{"rang":"R","numar":"16671","numar_intern":16671},{"rang":"R","numar":"16672","numar_intern":16672},{"rang":"R","numar":"16673","numar_intern":16673},{"rang":"R","numar":"16674","numar_intern":16674},{"rang":"R","numar":"16675","numar_intern":16675},{"rang":"R","numar":"16676","numar_intern":16676},{"rang":"R","numar":"16677","numar_intern":16677},{"rang":"R","numar":"16678","numar_intern":16678},{"rang":"R","numar":"16679","numar_intern":16679},{"rang":"R","numar":"16680","numar_intern":16680},{"rang":"R","numar":"16681","numar_intern":16681},{"rang":"R","numar":"16682","numar_intern":16682},{"rang":"R","numar":"16683","numar_intern":16683},{"rang":"R","numar":"16684","numar_intern":16684},{"rang":"R","numar":"16685","numar_intern":16685},{"rang":"R","numar":"16687","numar_intern":16687},{"rang":"R","numar":"16689","numar_intern":16689}]} \ No newline at end of file diff --git a/assets/lines/st.json b/assets/lines/st.json new file mode 100644 index 0000000..2eb981e --- /dev/null +++ b/assets/lines/st.json @@ -0,0 +1 @@ +{"short_name":"Softrans","operator":"Softrans S.R.L.","data_export":"20181212","valabil":{"de_la":"20181209","pana_la":"20191214"},"versiune":"1","trenuri":[{"rang":"IR","numar":"15931-2","numar_intern":15931},{"rang":"IR","numar":"15932","numar_intern":15932},{"rang":"IR","numar":"15933-2","numar_intern":15933},{"rang":"IR","numar":"15934","numar_intern":15934},{"rang":"IR","numar":"15935-2","numar_intern":15935},{"rang":"IR","numar":"15936","numar_intern":15936},{"rang":"IR","numar":"15982","numar_intern":15982},{"rang":"IR","numar":"15984","numar_intern":15984}]} \ No newline at end of file diff --git a/assets/lines/tfc.json b/assets/lines/tfc.json new file mode 100644 index 0000000..1b50b61 --- /dev/null +++ b/assets/lines/tfc.json @@ -0,0 +1 @@ +{"short_name":"TFC","operator":"Transferoviar Călători S.R.L.","data_export":"20181212","valabil":{"de_la":"20181209","pana_la":"20191214"},"versiune":"1","trenuri":[{"rang":"R","numar":"15002","numar_intern":15002},{"rang":"R","numar":"15003","numar_intern":15003},{"rang":"R","numar":"15004","numar_intern":15004},{"rang":"IR","numar":"15031","numar_intern":15031},{"rang":"IR","numar":"15032","numar_intern":15032},{"rang":"IR","numar":"15033","numar_intern":15033},{"rang":"IR","numar":"15034","numar_intern":15034},{"rang":"IR","numar":"15035","numar_intern":15035},{"rang":"IR","numar":"15036","numar_intern":15036},{"rang":"IR","numar":"15037","numar_intern":15037},{"rang":"IR","numar":"15038","numar_intern":15038},{"rang":"IR","numar":"15051","numar_intern":15051},{"rang":"IR","numar":"15053","numar_intern":15053},{"rang":"IR","numar":"15054","numar_intern":15054},{"rang":"IR","numar":"15056","numar_intern":15056},{"rang":"IR","numar":"15057","numar_intern":15057},{"rang":"IR","numar":"15061","numar_intern":15061},{"rang":"IR","numar":"15071","numar_intern":15071},{"rang":"IR","numar":"15072","numar_intern":15072},{"rang":"R","numar":"15110","numar_intern":15110},{"rang":"R","numar":"15111","numar_intern":15111},{"rang":"R","numar":"15120","numar_intern":15120},{"rang":"R","numar":"15121","numar_intern":15121},{"rang":"R","numar":"15122","numar_intern":15122},{"rang":"R","numar":"15123","numar_intern":15123},{"rang":"R","numar":"15124","numar_intern":15124},{"rang":"R","numar":"15125","numar_intern":15125},{"rang":"R","numar":"15126","numar_intern":15126},{"rang":"R","numar":"15127","numar_intern":15127},{"rang":"R","numar":"15128","numar_intern":15128},{"rang":"R","numar":"15129","numar_intern":15129},{"rang":"R","numar":"15130","numar_intern":15130},{"rang":"R","numar":"15131","numar_intern":15131},{"rang":"R","numar":"15132","numar_intern":15132},{"rang":"R","numar":"15133","numar_intern":15133},{"rang":"R","numar":"15134","numar_intern":15134},{"rang":"R","numar":"15135","numar_intern":15135},{"rang":"R","numar":"15136","numar_intern":15136},{"rang":"R","numar":"15137","numar_intern":15137},{"rang":"R","numar":"15138","numar_intern":15138},{"rang":"R","numar":"15139","numar_intern":15139},{"rang":"R","numar":"15140","numar_intern":15140},{"rang":"R","numar":"15141","numar_intern":15141},{"rang":"R","numar":"15142","numar_intern":15142},{"rang":"R","numar":"15143","numar_intern":15143},{"rang":"R","numar":"15144","numar_intern":15144},{"rang":"R","numar":"15145","numar_intern":15145},{"rang":"R","numar":"15146","numar_intern":15146},{"rang":"R","numar":"15147","numar_intern":15147},{"rang":"R","numar":"15148","numar_intern":15148},{"rang":"R","numar":"15149","numar_intern":15149},{"rang":"R","numar":"15150","numar_intern":15150},{"rang":"R","numar":"15151","numar_intern":15151},{"rang":"R","numar":"15152","numar_intern":15152},{"rang":"R","numar":"15153","numar_intern":15153},{"rang":"R","numar":"15154","numar_intern":15154},{"rang":"R","numar":"15155","numar_intern":15155},{"rang":"R","numar":"15156","numar_intern":15156},{"rang":"R","numar":"15157","numar_intern":15157},{"rang":"R","numar":"15160","numar_intern":15160},{"rang":"R","numar":"15161","numar_intern":15161},{"rang":"R","numar":"15162","numar_intern":15162},{"rang":"R","numar":"15163","numar_intern":15163},{"rang":"R","numar":"15164","numar_intern":15164},{"rang":"R","numar":"15165","numar_intern":15165},{"rang":"R","numar":"15167","numar_intern":15167},{"rang":"R","numar":"15170","numar_intern":15170},{"rang":"R","numar":"15171","numar_intern":15171},{"rang":"R","numar":"15172","numar_intern":15172},{"rang":"R","numar":"15173","numar_intern":15173},{"rang":"R","numar":"15174","numar_intern":15174},{"rang":"R","numar":"15175","numar_intern":15175},{"rang":"R","numar":"15176","numar_intern":15176},{"rang":"R","numar":"15177","numar_intern":15177},{"rang":"R","numar":"15178","numar_intern":15178},{"rang":"R","numar":"15179","numar_intern":15179},{"rang":"R","numar":"15180","numar_intern":15180},{"rang":"R","numar":"15181","numar_intern":15181},{"rang":"R","numar":"15182","numar_intern":15182},{"rang":"R","numar":"15183","numar_intern":15183},{"rang":"R","numar":"15184","numar_intern":15184},{"rang":"R","numar":"15185","numar_intern":15185},{"rang":"R","numar":"15186","numar_intern":15186},{"rang":"R","numar":"15187","numar_intern":15187},{"rang":"R","numar":"15188","numar_intern":15188},{"rang":"R","numar":"15189","numar_intern":15189},{"rang":"R","numar":"15190","numar_intern":15190},{"rang":"R","numar":"15191","numar_intern":15191},{"rang":"R","numar":"15193","numar_intern":15193},{"rang":"R","numar":"15250","numar_intern":15250},{"rang":"R","numar":"15251","numar_intern":15251},{"rang":"R","numar":"15252","numar_intern":15252},{"rang":"R","numar":"15253","numar_intern":15253},{"rang":"R","numar":"15254","numar_intern":15254},{"rang":"R","numar":"15255","numar_intern":15255},{"rang":"R","numar":"15256","numar_intern":15256},{"rang":"R","numar":"15257","numar_intern":15257},{"rang":"R","numar":"15258","numar_intern":15258},{"rang":"R","numar":"15259","numar_intern":15259},{"rang":"R","numar":"15260","numar_intern":15260},{"rang":"R","numar":"15261","numar_intern":15261},{"rang":"R","numar":"15262","numar_intern":15262},{"rang":"R","numar":"15263","numar_intern":15263},{"rang":"R","numar":"15264","numar_intern":15264},{"rang":"R","numar":"15265","numar_intern":15265},{"rang":"R","numar":"15300","numar_intern":15300},{"rang":"R","numar":"15301","numar_intern":15301},{"rang":"R","numar":"15302","numar_intern":15302},{"rang":"R","numar":"15303","numar_intern":15303},{"rang":"R","numar":"15304","numar_intern":15304},{"rang":"R","numar":"15305","numar_intern":15305},{"rang":"R","numar":"15306","numar_intern":15306},{"rang":"R","numar":"15307","numar_intern":15307},{"rang":"R","numar":"15308","numar_intern":15308},{"rang":"R","numar":"15309","numar_intern":15309},{"rang":"R","numar":"15310","numar_intern":15310},{"rang":"R","numar":"15311","numar_intern":15311},{"rang":"R","numar":"15312","numar_intern":15312},{"rang":"R","numar":"15313","numar_intern":15313},{"rang":"R","numar":"15314","numar_intern":15314},{"rang":"R","numar":"15315","numar_intern":15315},{"rang":"R","numar":"15316","numar_intern":15316},{"rang":"R","numar":"15317","numar_intern":15317},{"rang":"R","numar":"15318","numar_intern":15318},{"rang":"R","numar":"15319","numar_intern":15319},{"rang":"R","numar":"15320","numar_intern":15320},{"rang":"R","numar":"15321","numar_intern":15321},{"rang":"R","numar":"15322","numar_intern":15322},{"rang":"R","numar":"15349","numar_intern":15349},{"rang":"R","numar":"15350","numar_intern":15350},{"rang":"R","numar":"15351","numar_intern":15351},{"rang":"R","numar":"15352","numar_intern":15352},{"rang":"R","numar":"15353","numar_intern":15353},{"rang":"R","numar":"15354","numar_intern":15354},{"rang":"R","numar":"15355","numar_intern":15355},{"rang":"R","numar":"15356","numar_intern":15356},{"rang":"R","numar":"15357","numar_intern":15357},{"rang":"R","numar":"15358","numar_intern":15358},{"rang":"R","numar":"15359","numar_intern":15359},{"rang":"R","numar":"15360","numar_intern":15360},{"rang":"R","numar":"15361","numar_intern":15361},{"rang":"R","numar":"15362","numar_intern":15362},{"rang":"R","numar":"15363","numar_intern":15363},{"rang":"R","numar":"15364","numar_intern":15364}]} \ No newline at end of file diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index 592ceee..e8efba1 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index 592ceee..399e934 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..e9286cb --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,74 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def parse_KV_file(file, separator='=') + file_abs_path = File.expand_path(file) + if !File.exists? file_abs_path + return []; + end + pods_ary = [] + skip_line_start_symbols = ["#", "/"] + File.foreach(file_abs_path) { |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + pods_ary.push({:name => podname, :path => podpath}); + else + puts "Invalid plugin specification: #{line}" + end + } + return pods_ary +end + +target 'Runner' do + use_frameworks! + + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + system('rm -rf .symlinks') + system('mkdir -p .symlinks/plugins') + + # Flutter Pods + generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') + if generated_xcode_build_settings.empty? + puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first." + end + generated_xcode_build_settings.map { |p| + if p[:name] == 'FLUTTER_FRAMEWORK_DIR' + symlink = File.join('.symlinks', 'flutter') + File.symlink(File.dirname(p[:path]), symlink) + pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) + end + } + + # Plugin Pods + plugin_pods = parse_KV_file('../.flutter-plugins') + plugin_pods.map { |p| + symlink = File.join('.symlinks', 'plugins', p[:name]) + File.symlink(p[:path], symlink) + pod p[:name], :path => File.join(symlink, 'ios') + } +end + +# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. +install! 'cocoapods', :disable_input_output_paths => true + +post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['ENABLE_BITCODE'] = 'NO' + end + end +end diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 0000000..4d6db99 --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,22 @@ +PODS: + - Flutter (1.0.0) + - webview_flutter (0.0.1): + - Flutter + +DEPENDENCIES: + - Flutter (from `.symlinks/flutter/ios`) + - webview_flutter (from `.symlinks/plugins/webview_flutter/ios`) + +EXTERNAL SOURCES: + Flutter: + :path: ".symlinks/flutter/ios" + webview_flutter: + :path: ".symlinks/plugins/webview_flutter/ios" + +SPEC CHECKSUMS: + Flutter: 58dd7d1b27887414a370fcccb9e645c08ffd7a6a + webview_flutter: 1aa7604e6cdb451a9b7ed2c37d5454c0b440246b + +PODFILE CHECKSUM: b6a0a141693093b304368d08511b46cf3d1d0ac5 + +COCOAPODS: 1.6.1 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 11a2188..08687f1 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 722F441253D3B79676E4DE80 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F72320B12B1F4015789BBC8E /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -38,10 +39,13 @@ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 313F1E773DA06364A0C4F20A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 636963D381657D3BAEDC0A47 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 74CD890ACD2E394E606FCBEB /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; @@ -51,6 +55,7 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + F72320B12B1F4015789BBC8E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -60,12 +65,21 @@ files = ( 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, + 722F441253D3B79676E4DE80 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 0B24EBF53F1DCC708FA961FD /* Frameworks */ = { + isa = PBXGroup; + children = ( + F72320B12B1F4015789BBC8E /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -85,6 +99,8 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + A2E7A2EB20EFBBAC4AB0299B /* Pods */, + 0B24EBF53F1DCC708FA961FD /* Frameworks */, ); sourceTree = ""; }; @@ -119,6 +135,16 @@ name = "Supporting Files"; sourceTree = ""; }; + A2E7A2EB20EFBBAC4AB0299B /* Pods */ = { + isa = PBXGroup; + children = ( + 313F1E773DA06364A0C4F20A /* Pods-Runner.debug.xcconfig */, + 74CD890ACD2E394E606FCBEB /* Pods-Runner.release.xcconfig */, + 636963D381657D3BAEDC0A47 /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -126,12 +152,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + 0D525F98970BF5A8EFFD825C /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 1B7EDCF8AB293318D8391906 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -155,6 +183,7 @@ CreatedOnToolsVersion = 7.3.1; DevelopmentTeam = NF9A3KMT8Q; LastSwiftMigration = 0910; + ProvisioningStyle = Automatic; }; }; }; @@ -192,6 +221,47 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 0D525F98970BF5A8EFFD825C /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 1B7EDCF8AB293318D8391906 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -309,6 +379,8 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = NF9A3KMT8Q; ENABLE_BITCODE = NO; @@ -317,6 +389,7 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -324,6 +397,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = ml.dandevelop.infoTren; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 4.0; VERSIONING_SYSTEM = "apple-generic"; @@ -443,6 +517,8 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = NF9A3KMT8Q; ENABLE_BITCODE = NO; @@ -451,6 +527,7 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -458,6 +535,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = ml.dandevelop.infoTren; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = On; @@ -472,6 +550,8 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = NF9A3KMT8Q; ENABLE_BITCODE = NO; @@ -480,6 +560,7 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", @@ -487,6 +568,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = ml.dandevelop.infoTren; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_SWIFT3_OBJC_INFERENCE = On; SWIFT_VERSION = 4.0; diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 18541b1..0218884 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -52,5 +52,7 @@ UIViewControllerBasedStatusBarAppearance + io.flutter.embedded_views_preview + diff --git a/lib/hidden_webview.dart b/lib/hidden_webview.dart new file mode 100644 index 0000000..236787d --- /dev/null +++ b/lib/hidden_webview.dart @@ -0,0 +1,23 @@ + +import 'package:flutter/widgets.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +class HiddenWebView extends StatelessWidget { + final WebView webView; + final Widget child; + + HiddenWebView({@required this.child, this.webView}); + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Offstage( + offstage: true, + child: webView, + ), + Positioned.fill(child: child) + ], + ); + } +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index bcdf7f4..53112e5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,247 +1,168 @@ +import 'dart:io' show Platform; + +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:info_tren/train_info_page/train_info_prompt.dart'; -import 'models/train_data.dart'; -import 'train_info_display.dart'; -void main() => runApp(MyApp()); -class MyApp extends StatelessWidget { - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Info Tren', - theme: ThemeData( - primarySwatch: Colors.blue, - brightness: Brightness.dark, - primaryColor: Colors.blue.shade600, - accentColor: Colors.blue.shade700, - ), - home: MainPageWrapper(), - ); - } -} +void main() => runApp(StartPoint()); -class MainPageWrapper extends StatelessWidget { +class StartPoint extends StatelessWidget { @override Widget build(BuildContext context) { - return TrainDataSourceWidget( - child: MainPage() - ); + if (Platform.isAndroid) { + return MaterialApp( + title: 'Info Tren', + theme: ThemeData( + primarySwatch: Colors.blue, + brightness: Brightness.dark, + primaryColor: Colors.blue.shade600, + accentColor: Colors.blue.shade700, + ), + home: MainPageMaterial(), + ); + } + else if (Platform.isIOS) { + return CupertinoApp( + title: "Info Tren", + theme: CupertinoThemeData( + primaryColor: Colors.blue.shade600, + brightness: Brightness.dark, + ), + home: MainPageCupertino(), + ); + } + return null; } } -class MainPage extends StatefulWidget { - @override - _MainPageState createState() => _MainPageState(); -} +mixin MainPageAction { + onTrainInfoPageInvoke(BuildContext context) { + if (Platform.isAndroid) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) { + return TrainInfoPromptMaterial(); + } + ) + ); + } + else if (Platform.isIOS) { + Navigator.of(context).push( + CupertinoPageRoute( + builder: (context) { + return TrainInfoPromptCupertino(); + }, + title: "Informații despre tren" + ) + ); + } + } -class _MainPageState extends State { - final trainNumberController = TextEditingController(); - bool showAlternate = false; + onStationBoardPageInvoke(BuildContext context) { - bool get shouldTap { - return trainNumberController.text.isNotEmpty; } - onTap(BuildContext context) { - FocusScope.of(context).requestFocus(FocusNode()); - TrainDataSourceWidget.of(context).lookup(trainNumberController.text, showAlternate); + onRoutePlanPageInvoke(BuildContext context) { + } +} +class MainPageMaterial extends StatelessWidget with MainPageAction { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Info Tren"), centerTitle: true, - actions: [ - IconButton( - icon: Icon(Icons.settings), - onPressed: () { - showModalBottomSheet(context: context, builder: (context) { - return StatefulBuilder( - builder: (context, ssSheet) => - SafeArea( - bottom: true, - top: false, - left: true, - right: true, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - title: Text( - showAlternate - ? "Afișează rezultatul principal" - : "Afișează rezultatul alternativ" - ), - onTap: () { - showAlternate = !showAlternate; - setState(() {}); - ssSheet(() {}); - }, - ) - ], - ), - ), - ); - }); - }, - ) - ], ), - body: Column( - children: [ - Row( + body: SafeArea( + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: TextField( - controller: trainNumberController, - onChanged: (_) => setState(() => {}), - autofocus: true, - decoration: InputDecoration( - labelText: "Numărul trenului", - border: OutlineInputBorder(), - hintText: "Scrieți doar numărul" - ), - onSubmitted: (_) { - if (shouldTap) { - onTap(context); - } - }, - keyboardType: TextInputType.numberWithOptions(), - textInputAction: TextInputAction.search, - ), + RaisedButton( + child: Text( + "Informații despre tren", + style: Theme.of(context).textTheme.button.copyWith(fontSize: 18), + ), + onPressed: () { + onTrainInfoPageInvoke(context); + }, + ), + RaisedButton( + child: Text( + "Tabelă plecari/sosiri", + style: Theme.of(context).textTheme.button.copyWith(fontSize: 18), ), + onPressed: () { + onStationBoardPageInvoke(context); + }, ), - Padding( - padding: const EdgeInsets.all(8.0), - child: RaisedButton( - child: Text("Caută"), - onPressed: (() { - if (!shouldTap) return null; - return () { - onTap(context); - }; - })(), + RaisedButton( + child: Text( + "Planificare rută", + style: Theme.of(context).textTheme.button.copyWith(fontSize: 18), ), + onPressed: () { + onRoutePlanPageInvoke(context); + }, ) - ], - ), - Text( - showAlternate - ? "Se va afișa rezultatul alternativ\nla următoarea căutare" - : "Se va afișa rezultatul principal\nla următoarea căutare", - style: Theme.of(context).textTheme.caption.copyWith(fontStyle: FontStyle.italic), - textAlign: TextAlign.center, - ), - Divider( - color: Theme.of(context).accentColor, - height: 4, + ].map((w) => Padding( + padding: const EdgeInsets.fromLTRB(4, 2, 4, 2), + child: SizedBox( + width: double.infinity, + child: w, + ), + )).toList(), ), - Expanded( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: TrainInfoDisplay(), - ), - ) - ], + ), ), ); } } -class TrainInfoDisplay extends StatelessWidget { +class MainPageCupertino extends StatelessWidget with MainPageAction { @override Widget build(BuildContext context) { - return StreamBuilder( - stream: TrainDataSourceWidget.of(context).dataStream, - builder: (context, snapshot) { - if (snapshot.connectionState != ConnectionState.active) { - return Container(); - } - - if (snapshot.hasError) { - final error = snapshot.error as TrainDataEvent; - - if (error.kind == TrainDataEventKind.NOT_FOUND) { - return Center( - child: Text( - "Train number ${error.trainNumber}\nwas not found!", - style: Theme.of(context).textTheme.headline, - textAlign: TextAlign.center, - ) - ); - } - - if (error.kind == TrainDataEventKind.TIMEOUT) { - return Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - "The request has timed out!", - style: Theme.of(context).textTheme.headline, - textAlign: TextAlign.center, - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: RaisedButton( - child: Text("Retry"), - onPressed: () { - TrainDataSourceWidget.of(context).lookup(error.trainNumber); - }, - ), - ) - ], - ) - ); - } - - if (error.kind == TrainDataEventKind.UNKNOWN_ERROR) { - return Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Text("An unknown error with the status code ${error.errorStatusCode} has occured."), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: RaisedButton( - child: Text("Retry"), - onPressed: () { - TrainDataSourceWidget.of(context).lookup(error.trainNumber); - }, - ), - ) - ], + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text("Info Tren"), + ), + child: SafeArea( + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CupertinoButton.filled( + child: Text("Informații despre tren"), + onPressed: () { + onTrainInfoPageInvoke(context); + }, ), - ); - } - } - - if (snapshot.hasData) { - if (snapshot.data.kind == TrainDataEventKind.LOADING) { - return Center( - child: CircularProgressIndicator(), - ); - } - - if (snapshot.data.kind == TrainDataEventKind.GOT_DATA) { - return TrainInfoDisplayData(snapshot.data.trainData); - } - } - - return Placeholder(); - }, + CupertinoButton.filled( + child: Text("Tabelă plecari/sosiri"), + onPressed: () { + onStationBoardPageInvoke(context); + }, + ), + CupertinoButton.filled( + child: Text("Planificare rută"), + onPressed: () { + onRoutePlanPageInvoke(context); + }, + ), + ].map((w) => Padding( + padding: const EdgeInsets.fromLTRB(4, 2, 4, 2), + child: SizedBox( + width: double.infinity, + child: w, + ), + )).toList(), + ), + ), + ), ); } -} - +} \ No newline at end of file diff --git a/lib/models/train_data.dart b/lib/models/train_data.dart index 1348232..9ba6839 100644 --- a/lib/models/train_data.dart +++ b/lib/models/train_data.dart @@ -1,129 +1,942 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:io' show Platform; import 'package:flutter/widgets.dart'; -import 'package:http/http.dart' as http; +import 'package:info_tren/hidden_webview.dart'; +import 'package:info_tren/utils/webview_invoke.dart'; import 'package:json_annotation/json_annotation.dart'; -import 'package:rxdart/subjects.dart'; +import 'package:webview_flutter/webview_flutter.dart'; part 'train_data.g.dart'; -class TrainDataSourceWidget extends InheritedWidget { - final TrainDataSource dataSource; +enum TrainLookupResult { + FOUND, + NOT_FOUND, + OTHER +} + +class OnDemandInvalidatedException implements Exception { + final String propertyName; + final OnDemand onDemandClass; + + OnDemandInvalidatedException({this.propertyName, this.onDemandClass}); @override - bool updateShouldNotify(InheritedWidget oldWidget) { - return true; + String toString() { + return "OnDemandInvalidatedException: An attempt to get $propertyName from ${onDemandClass.runtimeType} failed because the source was invalidated."; } +} + +class OnDemand { + bool valid; - TrainDataSourceWidget({Widget child, Key key}) - : dataSource = TrainDataSource(), - super(key: key, child: child); + final Function onInvalidation; - static TrainDataSource of(BuildContext context) { - return (context.inheritFromWidgetOfExactType(TrainDataSourceWidget) as TrainDataSourceWidget).dataSource; + void invalidate() { + if (valid) { + valid = false; + if (onInvalidation != null) onInvalidation(); + } } + + OnDemand(this.onInvalidation): valid = true; } -class TrainDataSource { - Future lookup(String trainNumber, [bool alternate = false]) async { - _controller.add( - TrainDataEvent( - trainNumber: trainNumber, - kind: TrainDataEventKind.LOADING - ) +class OnDemandTrainData extends OnDemand { + final WebViewController _controller; + + OnDemandTrainData({ + WebViewController controller, + Function onInvalidation + }) + : _controller = controller, + _route = OnDemandTrainRoute(controller: controller), + _lastInfo = OnDemandLastInfo(controller: controller), + _destination = OnDemandDestination(controller: controller), + _nextStop = OnDemandNextStop(controller: controller), + _stations = OnDemandStations(controller: controller), + super(onInvalidation); + + @override + invalidate() { + super.invalidate(); + route.invalidate(); + lastInfo.invalidate(); + destination.invalidate(); + nextStop.invalidate(); + stations.invalidate(); + } + + Future get _originalDepartureDate async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_originalDepartureDate"); + + final tempRes = await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + let table = document.querySelector("#DetailsView1"); + let field = table.querySelector("caption"); + return field.textContent.trim(); + })() + """, + isFunctionAlready: true, ); - try { - final response = await http.get( - "http://cfr-scrapper.herokuapp.com/train/$trainNumber${alternate ? "?alt" : ""}", headers: { - "Authorization": "Flutter - Info Tren", - "X-Issued-By": "Flutter App" - } - ); + return tempRes.split(" ").last; + } - if (200 <= response.statusCode && response.statusCode <= 299) { - // OK - final text = response.body; - final ser = json.decode(text - ); - final data = TrainData.fromJson(ser - ); - _controller.add( - TrainDataEvent( - kind: TrainDataEventKind.GOT_DATA, - trainNumber: trainNumber, - trainData: data - ) - ); - } - else if (response.statusCode == 404) { - _controller.addError( - TrainDataEvent( - kind: TrainDataEventKind.NOT_FOUND, - trainNumber: trainNumber, - errorStatusCode: 404 - ) - ); + Future get departureDate async { + final str = await _originalDepartureDate; + + final parts = str.split(".").map((str) => int.parse(str)).toList(); + + return DateTime(parts[2], parts[1], parts[0]); + } + + Future get rang async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "rang"); + + return await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + let table = document.querySelector("#DetailsView1"); + let rows = table.querySelectorAll("tr"); + let currentRow = rows[0]; + let currentDataCell = currentRow.querySelectorAll("td")[1]; + return currentDataCell.textContent; + })() + """, + isFunctionAlready: true, + ); + } + + Future get trainNumber async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "trainNumber"); + + return await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + let table = document.querySelector("#DetailsView1"); + let rows = table.querySelectorAll("tr"); + let currentRow = rows[1]; + let currentDataCell = currentRow.querySelectorAll("td")[1]; + return currentDataCell.textContent; + })() + """, + isFunctionAlready: true, + ); + } + + Future get operator async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "operator"); + + return await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + let table = document.querySelector("#DetailsView1"); + let rows = table.querySelectorAll("tr"); + let currentRow = rows[2]; + let currentDataCell = currentRow.querySelectorAll("td")[1]; + return currentDataCell.textContent; + })() + """, + isFunctionAlready: true, + ); + } + + final OnDemandTrainRoute _route; + OnDemandTrainRoute get route => _route; + + Future get state async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "state"); + + return await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + let table = document.querySelector("#DetailsView1"); + let rows = table.querySelectorAll("tr"); + let currentRow = rows[4]; + let currentDataCell = currentRow.querySelectorAll("td")[1]; + return currentDataCell.textContent; + })() + """, + isFunctionAlready: true, + ); + } + + final OnDemandLastInfo _lastInfo; + OnDemandLastInfo get lastInfo => _lastInfo; + + final OnDemandDestination _destination; + OnDemandDestination get destination => _destination; + + final OnDemandNextStop _nextStop; + OnDemandNextStop get nextStop => _nextStop; + + Future get routeDistance async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "routeDistance"); + + final result = (await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + let table = document.querySelector("#DetailsView1"); + let rows = table.querySelectorAll("tr"); + let currentRow = rows[12]; + let currentDataCell = currentRow.querySelectorAll("td")[1]; + return currentDataCell.textContent; + })() + """, + isFunctionAlready: true, + )).trim(); + + return takeWhile(result, (char) => char != ' '.codeUnitAt(0)); + } + + Future get _routeDuration async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_routeDuration"); + + var result = (await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + let table = document.querySelector("#DetailsView1"); + let rows = table.querySelectorAll("tr"); + let currentRow = rows[13]; + let currentDataCell = currentRow.querySelectorAll("td")[1]; + return currentDataCell.textContent; + })() + """, + isFunctionAlready: true, + )).trim(); + + if (result[result.length - 1] == '.') result = result.substring(0, result.length - 1); + + return result; + } + + Future get routeDuration async { + final input = await _routeDuration; + + var result = Duration(); + + StringBuffer buffer = StringBuffer(); + + for (var i = 0; i < input.length; i++) { + if ('0'.codeUnitAt(0) <= input.codeUnitAt(i) && input.codeUnitAt(i) <= '9'.codeUnitAt(0)) { + buffer.writeCharCode(input.codeUnitAt(i)); } - else if (response.statusCode == 503) { - _controller.addError( - TrainDataEvent( - kind: TrainDataEventKind.TIMEOUT, - trainNumber: trainNumber, - errorStatusCode: 503 - ) - ); + else if (input.startsWith("min", i)) { + result += Duration(minutes: int.parse(buffer.toString())); + buffer = StringBuffer(); + i += 2; } - else { - // Throw error - _controller.addError( - TrainDataEvent( - kind: TrainDataEventKind.UNKNOWN_ERROR, - trainNumber: trainNumber, - errorStatusCode: response.statusCode - ) - ); + else if (input.startsWith("h", i)) { + result += Duration(hours: int.parse(buffer.toString())); + buffer = StringBuffer(); } + else throw FormatException("Unrecognised!"); + } + + return result; + } + + final OnDemandStations _stations; + OnDemandStations get stations => _stations; +} + +class OnDemandTrainRoute extends OnDemand { + final WebViewController _controller; + + OnDemandTrainRoute({ + WebViewController controller, + Function onInvalidation + }) : _controller = controller, super(onInvalidation); + + Future get original async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "original"); + + return await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + let table = document.querySelector("#DetailsView1"); + let rows = table.querySelectorAll("tr"); + let currentRow = rows[3]; + let currentDataCell = currentRow.querySelectorAll("td")[1]; + return currentDataCell.textContent; + })() + """, + isFunctionAlready: true, + ); + } + + Future get from async { + final original = await this.original; + return original.split("-")[0]; + } + + Future get to async { + final original = await this.original; + return original.split("-")[1]; + } +} + +class OnDemandLastInfo extends OnDemand { + final WebViewController _controller; + + OnDemandLastInfo({ + WebViewController controller, + Function onInvalidation + }) : _controller = controller, super(onInvalidation); + + Future get _lastInfoOriginal async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_lastInfoOriginal"); + + return await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + let table = document.querySelector("#DetailsView1"); + let rows = table.querySelectorAll("tr"); + let currentRow = rows[5]; + let currentDataCell = currentRow.querySelectorAll("td")[1]; + return currentDataCell.textContent; + })() + """, + isFunctionAlready: true, + ); + } + + Future get station async { + final original = await _lastInfoOriginal; + + return original + .split("[")[0] + .trim(); + } + + Future get event async { + final original = await _lastInfoOriginal; + + return original + .split("[")[1] + .split("]")[0] + .trim(); + } + + Future get originalDateAndTime async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "originalDateAndTime"); + + return await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + let table = document.querySelector("#DetailsView1"); + let rows = table.querySelectorAll("tr"); + let currentRow = rows[6]; + let currentDataCell = currentRow.querySelectorAll("td")[1]; + return currentDataCell.textContent; + })() + """, + isFunctionAlready: true, + ); + } + + Future get dateAndTime async => parseCFRDateTime(await originalDateAndTime); + + Future get delay async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "delay"); + + return int.parse( + await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + let table = document.querySelector("#DetailsView1"); + let rows = table.querySelectorAll("tr"); + let currentRow = rows[7]; + let currentDataCell = currentRow.querySelectorAll("td")[1]; + return currentDataCell.textContent; + })() + """, + isFunctionAlready: true, + ) + ); + } +} + +class OnDemandDestination extends OnDemand { + final WebViewController _controller; + + OnDemandDestination({ + WebViewController controller, + Function onInvalidation + }) : _controller = controller, super(onInvalidation); + + Future get stationName async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "destinationStation"); + + final result = (await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + let table = document.querySelector("#DetailsView1"); + let rows = table.querySelectorAll("tr"); + let currentRow = rows[8]; + let currentDataCell = currentRow.querySelectorAll("td")[1]; + return currentDataCell.textContent; + })() + """, + isFunctionAlready: true, + )).trim(); + + if (result.isEmpty) return null; + else return result; + } + + Future get _originalDestinationArrival async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_originalDestinationArrival"); + + final result = (await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + let table = document.querySelector("#DetailsView1"); + let rows = table.querySelectorAll("tr"); + let currentRow = rows[9]; + let currentDataCell = currentRow.querySelectorAll("td")[1]; + return currentDataCell.textContent; + })() + """, + isFunctionAlready: true, + )).trim(); + + if (result.isEmpty) return null; + else return result; + } + + Future get arrival => _originalDestinationArrival.then((value) => parseCFRDateTime(value)); +} + +class OnDemandNextStop extends OnDemand { + final WebViewController _controller; + + OnDemandNextStop({ + WebViewController controller, + Function onInvalidation + }) : _controller = controller, super(onInvalidation); + + Future get stationName async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "destinationStation"); + + final result = (await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + let table = document.querySelector("#DetailsView1"); + let rows = table.querySelectorAll("tr"); + let currentRow = rows[10]; + let currentDataCell = currentRow.querySelectorAll("td")[1]; + return currentDataCell.textContent; + })() + """, + isFunctionAlready: true, + )).trim(); + + if (result.isEmpty) return null; + else return result; + } + + Future get _originalNextStopArrival async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_originalDestinationArrival"); + + final result = (await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + let table = document.querySelector("#DetailsView1"); + let rows = table.querySelectorAll("tr"); + let currentRow = rows[11]; + let currentDataCell = currentRow.querySelectorAll("td")[1]; + return currentDataCell.textContent; + })() + """, + isFunctionAlready: true, + )).trim(); + + if (result.isEmpty) return null; + else return result; + } + + Future get arrival => _originalNextStopArrival.then((value) => parseCFRDateTime(value)); +} + +class OnDemandStations extends OnDemand { + final WebViewController _controller; + List issuedOnDemands = List(); + + @override + void invalidate() { + issuedOnDemands.map((od) => od.invalidate()); + super.invalidate(); + } + + OnDemandStations({ + @required WebViewController controller, + Function onInvalidation + }) : _controller = controller, super(onInvalidation); + + Future get _stationsLoaded async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "_stationsLoaded"); + + final result = await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => JSON.stringify(document.querySelector("#GridView1") == null))() + """, + isFunctionAlready: true, + ); + + final decoder = JsonDecoder(); + return !(decoder.convert(result) as bool); + } + + Stream call({@required Future pageLoadFuture}) async* { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "call"); + + if (!await _stationsLoaded) { + await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + const button = document.querySelector("#Button2"); + button.click(); + })() + """, + isFunctionAlready: true, + ); + await pageLoadFuture; } - catch (e) { - _controller.addError( - TrainDataEvent( - kind: TrainDataEventKind.UNKNOWN_ERROR, - trainNumber: trainNumber, - errorStatusCode: -1 - ) + + final count = int.parse(await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + const table = document.querySelector("#GridView1"); + const rows = table.querySelectorAll("tr"); + const rowsArray = Array.from(rows); + const count = rowsArray.length - 1; + return String(count); + })() + """, + isFunctionAlready: true, + )); + + for (int i = 1; i <= count; i++) { + final ods = OnDemandStation( + controller: _controller, + index: i, ); + issuedOnDemands.add(ods); + yield ods; } } +} + +class OnDemandStation extends OnDemand { + final WebViewController _controller; + final int index; - TrainDataSource() { - _controller = BehaviorSubject(); + OnDemandStation({ + @required WebViewController controller, + @required this.index, + Function onInvalidation + }) : _controller = controller, super(onInvalidation); + + Future get km async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "km"); + + return int.parse(await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + const table = document.querySelector("#GridView1"); + const rows = table.querySelectorAll("tr"); + const rowsArray = Array.from(rows); + const row = rowsArray[$index]; + const columns = row.querySelectorAll("td"); + const kmCell = columns[0]; + return kmCell.textContent; + })() + """, + isFunctionAlready: true, + )); + } + + Future get stationName async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "stationName"); + + return await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + const table = document.querySelector("#GridView1"); + const rows = table.querySelectorAll("tr"); + const rowsArray = Array.from(rows); + const row = rowsArray[$index]; + const columns = row.querySelectorAll("td"); + const kmCell = columns[1]; + return kmCell.textContent; + })() + """, + isFunctionAlready: true, + ); } - dispose() { - _controller.close(); + Future get arrivalTime async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "arrivalTime"); + + return await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + const table = document.querySelector("#GridView1"); + const rows = table.querySelectorAll("tr"); + const rowsArray = Array.from(rows); + const row = rowsArray[$index]; + const columns = row.querySelectorAll("td"); + const kmCell = columns[2]; + return kmCell.textContent.trim(); + })() + """, + isFunctionAlready: true, + ); } - BehaviorSubject _controller; - Stream get dataStream => _controller.stream; + Future get stopsFor async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "stopsFor"); + + return await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + const table = document.querySelector("#GridView1"); + const rows = table.querySelectorAll("tr"); + const rowsArray = Array.from(rows); + const row = rowsArray[$index]; + const columns = row.querySelectorAll("td"); + const kmCell = columns[3]; + return kmCell.textContent.trim(); + })() + """, + isFunctionAlready: true, + ); + } + + Future get departureTime async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "departureTime"); + + return await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + const table = document.querySelector("#GridView1"); + const rows = table.querySelectorAll("tr"); + const rowsArray = Array.from(rows); + const row = rowsArray[$index]; + const columns = row.querySelectorAll("td"); + const kmCell = columns[4]; + return kmCell.textContent.trim(); + })() + """, + isFunctionAlready: true, + ); + } + + Future get realOrEstimate async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "realOrEstimate"); + + final value = await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + const table = document.querySelector("#GridView1"); + const rows = table.querySelectorAll("tr"); + const rowsArray = Array.from(rows); + const row = rowsArray[$index]; + const columns = row.querySelectorAll("td"); + const kmCell = columns[5]; + return kmCell.textContent.trim(); + })() + """, + isFunctionAlready: true, + ); + + if (value == "Real") return RealOrEstimate.real; + else if (value == "Estimat") return RealOrEstimate.estimate; + else return RealOrEstimate.UNKNOWN; + } + + Future get delay async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "delay"); + + final value = await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + const table = document.querySelector("#GridView1"); + const rows = table.querySelectorAll("tr"); + const rowsArray = Array.from(rows); + const row = rowsArray[$index]; + const columns = row.querySelectorAll("td"); + const kmCell = columns[6]; + return kmCell.textContent.trim(); + })() + """, + isFunctionAlready: true, + ); + + if (value.isEmpty) return 0; + else return int.parse(value); + } + + Future get observations async { + if (!valid) throw OnDemandInvalidatedException(onDemandClass: this, propertyName: "observations"); + + return await wInvoke( + webViewController: _controller, + jsFunctionContent: """ + (() => { + const table = document.querySelector("#GridView1"); + const rows = table.querySelectorAll("tr"); + const rowsArray = Array.from(rows); + const row = rowsArray[$index]; + const columns = row.querySelectorAll("td"); + const kmCell = columns[7]; + return kmCell.textContent.trim(); + })() + """, + isFunctionAlready: true, + ); + } } -enum TrainDataEventKind { - LOADING, - TIMEOUT, - NOT_FOUND, - UNKNOWN_ERROR, - GOT_DATA +enum RealOrEstimate { + real, + estimate, + UNKNOWN } -class TrainDataEvent { - final TrainData trainData; - final TrainDataEventKind kind; - final String trainNumber; - final int errorStatusCode; +class TrainDataWebViewAdapter extends StatefulWidget { + final WidgetBuilder builder; + + TrainDataWebViewAdapter({@required this.builder}); + + @override + State createState() { + return _TrainDataWebViewAdapterState(); + } - TrainDataEvent({this.kind, this.trainData, this.trainNumber, this.errorStatusCode}); + static _TrainDataWebViewAdapterState of(BuildContext context) => + (context.inheritFromWidgetOfExactType(_TrainDataWebViewAdapterInheritedWidget) as _TrainDataWebViewAdapterInheritedWidget) + .state; +} + +class ProgressReport { + final int current; + final int total; + final String description; + + ProgressReport({@required this.current, @required this.total, this.description}); + + @override + String toString() { + return description == null ? "ProgressReport($current/$total)" : "ProgressReport($current/$total: $description)"; + } +} + +class _TrainDataWebViewAdapterState extends State { + Completer _webViewControllerCompleter = Completer(); + Future get webViewController => _webViewControllerCompleter.future; + + StreamController _pageLoadController; + Stream pageLoadStream; + Future get nextLoadFuture => pageLoadStream.take(1).first; + + StreamController _progressController; + Stream progressStream; + + Future loadTrain(int trainNo) async { + currentDatas.removeWhere((ondemand) { + ondemand.invalidate(); + return true; + }); + + final controller = await webViewController; + var nlf; + + nlf = nextLoadFuture; + await controller.loadUrl("https://appiris.infofer.ro/MytrainRO.aspx"); + await nlf; + _reportStatus( + current: 2, + description: "Loaded Informatica Feroviară webpage" + ); + + nlf = nextLoadFuture; + await controller.evaluateJavascript(""" + ( () => { + let inputField = document.querySelector("#TextTrnNo"); + inputField.value = $trainNo; + let submitButton = document.querySelector("#Button1"); + submitButton.click(); + } ) () + """); + await nlf; + + _reportStatus( + current: 3, + description: "Loaded train information" + ); + + var result = await wInvoke( + webViewController: controller, + jsFunctionContent: """ + (() => { + let errorMessage = document.querySelector("#Lblx"); + return errorMessage.textContent; + })() + """, + isFunctionAlready: true, + ); + + if (result.isNotEmpty) { + return TrainLookupResult.NOT_FOUND; + } + + final jsonDecoder = JsonDecoder(); + + final foundTable = jsonDecoder.convert(await wInvoke( + webViewController: controller, + jsFunctionContent: """ + (() => { + let table = document.querySelector("#DetailsView1"); + return JSON.stringify(table !== null); + })() + """, + isFunctionAlready: true, + )) as bool; + + if (foundTable) { + return TrainLookupResult.FOUND; + } + + /// Should not happen, report error in this case + return TrainLookupResult.OTHER; + } + + List currentDatas = List(); + + Future trainData({Function onInvalidation}) async { + final controller = await webViewController; + + final result = OnDemandTrainData( + controller: controller, + onInvalidation: onInvalidation + ); + + currentDatas.add(result); + + return result; + } + + int lastStatusReported; + + _reportStatus({@required int current, String description}) { + lastStatusReported = current; + _progressController.add(ProgressReport( + current: current, + total: TOTAL_PROGRESS, + description: description + )); + } + + recallLastReport() { + _progressController.add(ProgressReport(current: lastStatusReported, total: TOTAL_PROGRESS)); + } + + restartProgressReport() { + lastStatusReported = 0; + webViewController.then((_) { + _reportStatus(current: 1, description: "WebView created"); + }); + } + + @override + void initState() { + _pageLoadController = StreamController(); + pageLoadStream = _pageLoadController.stream.asBroadcastStream(); + + _progressController = StreamController(); + progressStream = _progressController.stream.asBroadcastStream(); + + lastStatusReported = 0; + + super.initState(); + } + + @override + void dispose() { + _pageLoadController.close(); + _progressController.close(); + + super.dispose(); + } + + static const int TOTAL_PROGRESS = 3; + + @override + Widget build(BuildContext context) { + return HiddenWebView( + webView: WebView( + javascriptMode: JavascriptMode.unrestricted, + onWebViewCreated: (controller) { + _webViewControllerCompleter.complete(controller); + _reportStatus( + current: 1, + description: "WebView created" + ); + }, + onPageFinished: (url) { + _pageLoadController.add(url); + }, + ), + child: _TrainDataWebViewAdapterInheritedWidget( + child: Builder( + builder: widget.builder, + ), + state: this, + ), + ); + } +} + +class _TrainDataWebViewAdapterInheritedWidget extends InheritedWidget { + final _TrainDataWebViewAdapterState state; + + _TrainDataWebViewAdapterInheritedWidget({this.state, Widget child, Key key}) + :super(key: key, child: child); + + @override + bool updateShouldNotify(InheritedWidget oldWidget) { + return true; + } } @JsonSerializable() @@ -237,6 +1050,8 @@ class StationEntry { } DateTime parseCFRDateTime(String dateAndTime) { + if (dateAndTime == null || dateAndTime.isEmpty) return null; + final parts = dateAndTime.split(" "); final dateParts = parts[0].split("."); @@ -249,4 +1064,15 @@ DateTime parseCFRDateTime(String dateAndTime) { final minute = int.parse(timeParts[1]); return DateTime(year, month, day, hour, minute); +} + +String takeWhile(String input, Function charValidator) { + StringBuffer output = StringBuffer(); + + for (final char in input.codeUnits) { + if (charValidator(char)) output.writeCharCode(char); + else break; + } + + return output.toString(); } \ No newline at end of file diff --git a/lib/stations_list.dart b/lib/stations_list.dart index 71902cc..a02034e 100644 --- a/lib/stations_list.dart +++ b/lib/stations_list.dart @@ -172,8 +172,13 @@ class StopOnLineDetails extends StatelessWidget { textAlign: TextAlign.center, ), ), + if (station.observations == "ONI") Padding( - padding: const EdgeInsets.fromLTRB(8.0, 1.0, 8.0, 8.0), + padding: const EdgeInsets.fromLTRB(8.0, 0.0, 8.0, 0.5), + child: Text("oprire ne-itinerarică", style: Theme.of(context).textTheme.body1.copyWith(color: Colors.red.shade700),), + ), + Padding( + padding: const EdgeInsets.fromLTRB(8.0, 0.5, 8.0, 8.0), child: Text( "km ${station.km}", style: Theme.of(context).textTheme.caption, diff --git a/lib/train_info_page/train_info.dart b/lib/train_info_page/train_info.dart new file mode 100644 index 0000000..c8dc129 --- /dev/null +++ b/lib/train_info_page/train_info.dart @@ -0,0 +1,70 @@ +import 'dart:io' show Platform; + +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; + +import 'package:info_tren/models/train_data.dart'; +import 'package:info_tren/train_info_page/train_info_cupertino.dart'; +import 'package:info_tren/train_info_page/train_info_material.dart'; + +mixin TrainInfoMixin { + String title; + bool showTrainData; + TrainLookupResult lookupResult; + bool requestedData; +} + +class TrainInfo extends StatelessWidget { + final int trainNumber; + + TrainInfo({@required this.trainNumber}); + + @override + Widget build(BuildContext context) { + return TrainDataWebViewAdapter( + builder: (context) { + if (Platform.isAndroid) { + return TrainInfoMaterial(trainNumber: trainNumber,); + } + else if (Platform.isIOS) { + return TrainInfoCupertino(trainNumber: trainNumber,); + } + + return null; + }, + ); + } +} + +typedef FutureDisplayCallback(BuildContext context, T data); + +class FutureDisplay extends StatelessWidget { + final Future future; + final FutureDisplayCallback builder; + + FutureDisplay({Key key, @required this.future, @required this.builder}): super(key: key); + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: future, + builder: (context, snapshot) { + if (snapshot.hasData) return builder(context, snapshot.data); + if (snapshot.hasError) throw snapshot.error; + if (snapshot.connectionState == ConnectionState.done) return Container(); + + Widget loadingWidget; + if (Platform.isAndroid) { + loadingWidget = CircularProgressIndicator(); + } + else if (Platform.isIOS) { + loadingWidget = CupertinoActivityIndicator(); + } + + return Center( + child: loadingWidget, + ); + }, + ); + } +} \ No newline at end of file diff --git a/lib/train_info_page/train_info_animation_helpers.dart b/lib/train_info_page/train_info_animation_helpers.dart new file mode 100644 index 0000000..dc891a7 --- /dev/null +++ b/lib/train_info_page/train_info_animation_helpers.dart @@ -0,0 +1,254 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:info_tren/train_info_page/train_info_constants.dart'; + +import 'dart:io' show Platform; + +class AnimatedBackground extends StatefulWidget { + final Color initialColor; + final Color backgroundColor; + final Widget child; + final Duration animationDuration; + + AnimatedBackground({Key key, @required this.initialColor, @required this.backgroundColor, Duration animationDuration, @required this.child}) + : this.animationDuration = animationDuration ?? Duration(milliseconds: 250) + , super(key: key); + + @override + State createState() => _AnimatedBackgroundState(); +} + +class _AnimatedBackgroundState extends State with SingleTickerProviderStateMixin { + AnimationController controller; + Animatable animation; + + @override + void initState() { + super.initState(); + controller = AnimationController(vsync: this, duration: widget.animationDuration); + controller.forward(); + animation = ColorTween(begin: widget.initialColor, end: widget.backgroundColor); + } + + @override + void didUpdateWidget(AnimatedBackground oldWidget) { + super.didUpdateWidget(oldWidget); + + if (oldWidget.backgroundColor != widget.backgroundColor) { + controller = AnimationController( + duration: widget.animationDuration, + vsync: this + ); + controller.forward(); + + animation = ColorTween( + begin: oldWidget.backgroundColor, + end: widget.backgroundColor + ); + } + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: controller, + child: widget.child, + builder: (context, child) { + return Container( + decoration: BoxDecoration( + color: animation.evaluate(controller), + ), + child: child, + ); + }, + ); + } +} + +class ProgressReportDisplayEntry extends StatefulWidget { + final bool completed; + final String waitingText; + final String completedText; + String get text => completed ? completedText : waitingText; + + ProgressReportDisplayEntry({Key key, @required this.completed, @required this.waitingText, @required this.completedText}): super(key: key); + + @override + State createState() { + if (Platform.isIOS) { + return _ProgressReportDisplayEntryCupertinoState(); + } + else if (Platform.isAndroid) { + return _ProgressReportDisplayEntryMaterialState(); + } + else return null; + } +} + +class _ProgressReportDisplayEntryCupertinoState extends State with SingleTickerProviderStateMixin { + Animatable background; + Animatable checkMark; + AnimationController _controller; + + initAnimation() { + background = ColorTween( + begin: CupertinoTheme.of(context).scaffoldBackgroundColor, + end: BACKGROUND_GREEN, + ); + checkMark = ColorTween( + begin: FOREGROUND_WHITE, + end: FOREGROUND_GREEN, + ); + } + + @override + void initState() { + super.initState(); + + _controller = AnimationController( + duration: const Duration(milliseconds: 250), + vsync: this, + ); + _controller.value = widget.completed ? 1 : 0; + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + initAnimation(); + } + + @override + void didUpdateWidget(ProgressReportDisplayEntry oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.completed != widget.completed) { + if (widget.completed) { + _controller.forward(); + } + else { + _controller.reverse(); + } + } + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _controller, + builder: (context, _) { + return Container( + decoration: BoxDecoration( + color: background.evaluate(_controller) + ), + child: Row( + children: [ + Center( + child: Padding( + padding: const EdgeInsets.all(4), + child: Container( + width: 32, + height: 32, + child: + !widget.completed + ? CupertinoActivityIndicator() + : Icon(CupertinoIcons.check_mark_circled, color: checkMark.evaluate(_controller)), + ), + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.all(4), + child: Text(widget.text), + ), + ) + ], + ), + ); + } + ); + } +} + +class _ProgressReportDisplayEntryMaterialState extends State with SingleTickerProviderStateMixin { + Animatable background; + Animatable checkMark; + AnimationController _controller; + + initAnimation() { + background = ColorTween( + begin: Theme.of(context).scaffoldBackgroundColor, + end: Colors.green, + ); + checkMark = ColorTween( + begin: Colors.white, + end: Colors.greenAccent, + ); + } + + @override + void initState() { + super.initState(); + + _controller = AnimationController( + duration: const Duration(milliseconds: 250), + vsync: this, + ); + _controller.value = widget.completed ? 1 : 0; + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + initAnimation(); + } + + @override + void didUpdateWidget(ProgressReportDisplayEntry oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.completed != widget.completed) { + if (widget.completed) { + _controller.forward(); + } + else { + _controller.reverse(); + } + } + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _controller, + builder: (context, _) { + return Container( + decoration: BoxDecoration( + color: background.evaluate(_controller) + ), + child: Row( + children: [ + Center( + child: Padding( + padding: const EdgeInsets.all(4), + child: Container( + width: 32, + height: 32, + child: + !widget.completed + ? CircularProgressIndicator(strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Colors.orangeAccent),) + : Icon(Icons.check_circle, color: checkMark.evaluate(_controller), size: 32,), + ), + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.all(4), + child: Text(widget.text), + ), + ) + ], + ), + ); + } + ); + } +} \ No newline at end of file diff --git a/lib/train_info_page/train_info_constants.dart b/lib/train_info_page/train_info_constants.dart new file mode 100644 index 0000000..e8d5273 --- /dev/null +++ b/lib/train_info_page/train_info_constants.dart @@ -0,0 +1,10 @@ +import 'dart:ui'; + +const BACKGROUND_GREEN = Color.fromRGBO(5, 66, 10, 1); +const FOREGROUND_GREEN = Color.fromRGBO(20, 180, 50, 1); + +const BACKGROUND_RED = Color.fromRGBO(66, 10, 5, 1); + +const FOREGROUND_WHITE = Color.fromRGBO(240, 250, 240, 1); + +const FOREGROUND_DARK_GREY = Color.fromRGBO(55, 55, 55, 1); \ No newline at end of file diff --git a/lib/train_info_page/train_info_cupertino.dart b/lib/train_info_page/train_info_cupertino.dart new file mode 100644 index 0000000..fe9c5c3 --- /dev/null +++ b/lib/train_info_page/train_info_cupertino.dart @@ -0,0 +1,1238 @@ +import 'package:flutter/cupertino.dart'; +import 'package:info_tren/models/train_data.dart'; +import 'package:info_tren/train_info_page/train_info.dart'; +import 'package:info_tren/train_info_page/train_info_animation_helpers.dart'; +import 'package:info_tren/train_info_page/train_info_constants.dart'; +import 'package:info_tren/utils/stream_list.dart'; + +class TrainInfoCupertino extends StatefulWidget { + final int trainNumber; + + TrainInfoCupertino({@required this.trainNumber}); + + @override + _TrainInfoCupertinoState createState() => _TrainInfoCupertinoState(); +} + +class _TrainInfoCupertinoState extends State with TrainInfoMixin { + @override + void initState() { + super.initState(); + + title = widget.trainNumber.toString(); + showTrainData = false; + requestedData = false; + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + if (!requestedData) { + requestedData = true; + + TrainDataWebViewAdapter.of(context).loadTrain(widget.trainNumber).then((value) { + setState(() { + lookupResult = value; + }); + + if (lookupResult == TrainLookupResult.NOT_FOUND) { + Future.delayed(Duration(seconds: 5), () { + Navigator.of(context).pop(); + }); + } + else if (lookupResult == TrainLookupResult.FOUND) { + Future.delayed(Duration(seconds: 1, milliseconds: 500), () { + setState(() { + showTrainData = true; + }); + }); + } + }); + } + } + + @override + Widget build(BuildContext context) { + if (!showTrainData) { + return _TrainDataCupertinoBefore( + title: title, + lookupResult: lookupResult, + ); + } + else { + return _TrainDataCupertinoAfter( + title: title, + ); + } + } +} + +class _TrainDataCupertinoBefore extends StatefulWidget { + final String title; + final TrainLookupResult lookupResult; + + _TrainDataCupertinoBefore({ + @required this.title, + @required this.lookupResult, + }); + + @override + __TrainDataCupertinoBeforeState createState() => __TrainDataCupertinoBeforeState(); +} + +class __TrainDataCupertinoBeforeState extends State<_TrainDataCupertinoBefore> { + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text(widget.title ?? ""), + ), + child: SafeArea( + child: StreamBuilder( + stream: TrainDataWebViewAdapter.of(context).progressStream, + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.none: + return Container(); + case ConnectionState.waiting: + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CupertinoActivityIndicator(), + Text( + "Conectare...", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ); + case ConnectionState.active: + break; + case ConnectionState.done: + Navigator.of(context).pop(); + return Container(); + } + + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ProgressReportDisplayEntry( + key: ValueKey(1), + completed: 1 <= snapshot.data.current, + waitingText: "Se crează WebView", + completedText: "WebView a fost creat", + ), + ProgressReportDisplayEntry( + key: ValueKey(2), + completed: 2 <= snapshot.data.current, + waitingText: "Se încarcă pagina Informatica Feroviară", + completedText: "Pagina Informatica Feroviară a fost încărcată", + ), + ProgressReportDisplayEntry( + key: ValueKey(3), + completed: 3 <= snapshot.data.current, + waitingText: "Se încarcă informațiile despre tren", + completedText: "Informațiile despre tren au fost încărcate", + ), + if (widget.lookupResult != null) + ...[ + Container(height: 20,), + SizedBox( + width: double.infinity, + child: AnimatedBackground( + animationDuration: Duration(milliseconds: 250), + initialColor: CupertinoTheme.of(context).scaffoldBackgroundColor, + backgroundColor: + widget.lookupResult == TrainLookupResult.FOUND + ? BACKGROUND_GREEN + : BACKGROUND_RED, + child: Center( + child: Row( + children: [ + Expanded(child: Container(),), + if (widget.lookupResult == TrainLookupResult.FOUND) + Padding( + padding: const EdgeInsets.fromLTRB(8, 8, 0, 8), + child: Center( + child: CupertinoActivityIndicator() + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + widget.lookupResult == TrainLookupResult.FOUND + ? "Trenul a fost găsit" + : widget.lookupResult == TrainLookupResult.NOT_FOUND + ? "Trenul nu a fost găsit" + : "A apărut o eroare în căutarea trenului", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(fontSize: 20), + ), + ), + Expanded(child: Container(),), + ], + ), + ), + ), + ), + ], + ], + ), + ); + } + ), + ), + ); + } +} + +class _TrainDataCupertinoAfter extends StatefulWidget { + final String title; + + _TrainDataCupertinoAfter({ + @required this.title, + }); + + @override + __TrainDataCupertinoAfterState createState() => __TrainDataCupertinoAfterState(); +} + +class __TrainDataCupertinoAfterState extends State<_TrainDataCupertinoAfter> { + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: TrainDataWebViewAdapter.of(context).trainData(onInvalidation: () { + Navigator.of(context).pop(); + }), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text(widget.title ?? ""), + ), + child: SafeArea( + child: Center( + child: CupertinoActivityIndicator(), + ), + ), + ); + } + + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: FutureBuilder>( + future: Future.wait([ + snapshot.data.rang, + snapshot.data.trainNumber + ]), + builder: (context, snapshot) { + if (snapshot.hasData) { + return Text("Informații despre ${snapshot.data[0]} ${snapshot.data[1]}"); + } + else { + return Text(widget.title ?? ""); + } + }, + ), + ), + child: SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (context) { + final topPadding = MediaQuery.of(context).padding.top; + + return CustomScrollView( + slivers: [ + SliverToBoxAdapter( + child: Padding( + padding: EdgeInsets.only( + top: topPadding, + ), + child: Container(), + ), + ), + DisplayTrainID(trainData: snapshot.data,), + DisplayTrainOperator(trainData: snapshot.data,), + DisplayTrainRoute(trainData: snapshot.data,), + DisplayTrainDeparture(trainData: snapshot.data,), + SliverToBoxAdapter( + child: CupertinoDivider( + color: FOREGROUND_WHITE, + ), + ), + DisplayTrainLastInfo(trainData: snapshot.data,), + SliverToBoxAdapter( + child: CupertinoDivider(), + ), + SliverToBoxAdapter( + child: IntrinsicHeight( + child: Row( + children: [ + Expanded( + child: DisplayTrainNextStop(trainData: snapshot.data,), + ), + SizedBox( + height: double.infinity, + child: CupertinoVerticalDivider(), + ), + Expanded( + child: DisplayTrainDestination(trainData: snapshot.data,), + ) + ], + ), + ), + ), + SliverToBoxAdapter( + child: CupertinoDivider(), + ), + SliverToBoxAdapter( + child: IntrinsicHeight( + child: Row( + children: [ + Expanded( + child: DisplayTrainRouteDuration(trainData: snapshot.data,), + ), + SizedBox( + height: double.infinity, + child: CupertinoVerticalDivider(), + ), + Expanded( + child: DisplayTrainRouteDistance(trainData: snapshot.data,), + ) + ], + ), + ), + ), + SliverToBoxAdapter( + child: CupertinoDivider( + color: FOREGROUND_WHITE, + ), + ), + DisplayTrainStations( + trainData: snapshot.data, + pageLoadFuture: TrainDataWebViewAdapter.of(context).nextLoadFuture, + ), + SliverToBoxAdapter( + child: Container( + height: MediaQuery.of(context).viewPadding.bottom, + ), + ), + ], + ); + } + ), + ), + ); + }, + ); + + // return CupertinoPageScaffold( + // navigationBar: CupertinoNavigationBar( + // middle: Text(title ?? ""), + // ), + // child: SafeArea( + // bottom: false, + // child: FutureBuilder( + // future: TrainDataWebViewAdapter.of(context).trainData(onInvalidation: () { + // Navigator.of(context).pop(); + // }), + // builder: (context, snapshot) { + // if (!snapshot.hasData) { + // return Center( + // child: CupertinoActivityIndicator(), + // ); + // } + + // try { + // Future.wait([ + // snapshot.data.rang, + // snapshot.data.trainNumber + // ]).then((values) { + // setState(() { + // title = "Informații despre ${values[0]} ${values[1]}"; + // }); + // }); + + // return CustomScrollView( + // slivers: [ + // DisplayTrainID(data: snapshot.data,), + // DisplayTrainOperator(data: snapshot.data,), + // DisplayTrainRoute(data: snapshot.data,), + // DisplayTrainDeparture(data: snapshot.data,), + // SliverToBoxAdapter( + // child: CupertinoDivider( + // color: FOREGROUND_WHITE, + // ), + // ), + // DisplayTrainLastInfo(data: snapshot.data,), + // SliverToBoxAdapter( + // child: CupertinoDivider(), + // ), + // SliverToBoxAdapter( + // child: IntrinsicHeight( + // child: Row( + // children: [ + // Expanded( + // child: DisplayTrainNextStop(data: snapshot.data,), + // ), + // SizedBox( + // height: double.infinity, + // child: CupertinoVerticalDivider(), + // ), + // Expanded( + // child: DisplayTrainDestination(data: snapshot.data,), + // ) + // ], + // ), + // ), + // ), + // SliverToBoxAdapter( + // child: CupertinoDivider(), + // ), + // SliverToBoxAdapter( + // child: IntrinsicHeight( + // child: Row( + // children: [ + // Expanded( + // child: DisplayTrainRouteDuration(data: snapshot.data,), + // ), + // SizedBox( + // height: double.infinity, + // child: CupertinoVerticalDivider(), + // ), + // Expanded( + // child: DisplayTrainRouteDistance(data: snapshot.data,), + // ) + // ], + // ), + // ), + // ), + // SliverToBoxAdapter( + // child: CupertinoDivider( + // color: FOREGROUND_WHITE, + // ), + // ), + // DisplayTrainStations( + // data: snapshot.data, + // pageLoadFuture: TrainDataWebViewAdapter.of(context).nextLoadFuture, + // ), + // ], + // ); + // } + // on OnDemandInvalidatedException { + // Navigator.of(context).pop(); + // print("Got OnDemandInvalidatedException!"); + // return Container(); + // } + // }, + // ), + // ), + // ); + } +} + +class DisplayTrainID extends StatelessWidget { + final OnDemandTrainData trainData; + DisplayTrainID({@required this.trainData}); + + @override + Widget build(BuildContext context) { + return SliverToBoxAdapter( + child: FutureDisplay( + future: Future.wait([ + trainData.rang, + trainData.trainNumber + ]), + builder: (context, datas) { + return Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "${datas[0]} ${datas[1]}", + style: CupertinoTheme.of(context).textTheme.navLargeTitleTextStyle, + ), + ), + ); + }, + ), + ); + } +} + +class DisplayTrainRoute extends StatelessWidget { + final OnDemandTrainData trainData; + + DisplayTrainRoute({@required this.trainData}); + + @override + Widget build(BuildContext context) { + return SliverToBoxAdapter( + child: FutureDisplay( + future: Future.wait([trainData.route.from, trainData.route.to]), + builder: (context, routePieces) { + return Row( + children: [ + Center( + child: Padding( + padding: const EdgeInsets.all(4), + child: Text( + routePieces[0], + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 16, + ), + ), + ), + ), + Expanded(child: Container(),), + Center(child: Text("-")), + Expanded(child: Container(),), + Center( + child: Padding( + padding: const EdgeInsets.all(4), + child: Text( + routePieces[1], + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 16, + ), + textAlign: TextAlign.right, + ), + ), + ), + ], + ); + }, + ), + ); + } +} + +class DisplayTrainOperator extends StatelessWidget { + final OnDemandTrainData trainData; + + DisplayTrainOperator({@required this.trainData}); + + @override + Widget build(BuildContext context) { + return SliverToBoxAdapter( + child: FutureDisplay( + future: trainData.operator, + builder: (context, operator) { + return Center( + child: Text( + operator, + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 14, + fontStyle: FontStyle.italic, + ), + ), + ); + }, + ), + ); + } +} + +class DisplayTrainDeparture extends StatelessWidget { + final OnDemandTrainData trainData; + + DisplayTrainDeparture({@required this.trainData}); + + @override + Widget build(BuildContext context) { + return SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.all(2), + child: FutureDisplay( + future: trainData.departureDate, + builder: (context, dataPlecare) { + return Text( + "Plecare în ${dataPlecare.day.toString().padLeft(2, '0')}.${dataPlecare.month.toString().padLeft(2, '0')}.${dataPlecare.year.toString().padLeft(4, '0')}", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontStyle: FontStyle.italic, + fontWeight: FontWeight.w200, + ), + textAlign: TextAlign.center, + ); + }, + ), + ), + ); + } +} + +class DisplayTrainLastInfo extends StatelessWidget { + final OnDemandTrainData trainData; + + DisplayTrainLastInfo({@required this.trainData}); + + @override + Widget build(BuildContext context) { + return SliverToBoxAdapter( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Center( + child: Padding( + padding: const EdgeInsets.all(2), + child: Text( + "Ultima informație", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + Row( + children: [ + Padding( + padding: const EdgeInsets.all(4), + child: FutureDisplay( + future: trainData.lastInfo.station, + builder: (context, station) { + return Text( + station, + style: CupertinoTheme.of(context).textTheme.textStyle, + textAlign: TextAlign.left, + ); + }, + ), + ), + Expanded(child: Container(),), + Padding( + padding: const EdgeInsets.all(4), + child: FutureDisplay( + future: trainData.lastInfo.event, + builder: (context, event) { + return Text( + event, + style: CupertinoTheme.of(context).textTheme.textStyle, + textAlign: TextAlign.right, + ); + }, + ), + ), + ], + ), + FutureDisplay( + future: trainData.lastInfo.dateAndTime, + builder: (context, dt) { + return Text( + "Raportat în ${dt.day.toString().padLeft(2, '0')}.${dt.month.toString().padLeft(2, '0')}.${dt.year.toString().padLeft(4, '0')}, la ${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}", + textAlign: TextAlign.center, + ); + }, + ), + FutureBuilder( + initialData: 0, + future: trainData.lastInfo.delay, + builder: (context, snapshot) { + if (snapshot.data == 0) { + return Container(); + } + + if (snapshot.data > 0) { + return Text( + "${snapshot.data} minute întârziere", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 14, + color: Color.fromRGBO(200, 30, 15, 1), + ), + ); + } + else { + return Text( + "${-snapshot.data} minute mai devreme", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 12, + color: Color.fromRGBO(15, 200, 15, 1), + ), + ); + } + }, + ) + ], + ), + ); + } +} + +class DisplayTrainNextStop extends StatelessWidget { + final OnDemandTrainData trainData; + + DisplayTrainNextStop({@required this.trainData}); + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: trainData.nextStop.stationName, + builder: (context, snapshot) { + if (!snapshot.hasData) return Container(); + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(4), + child: Text( + "Următoarea oprire", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ), + CupertinoDivider( + color: Color.fromRGBO(15, 15, 15, 1), + ), + FutureDisplay( + future: trainData.nextStop.stationName, + builder: (context, station) { + return Padding( + padding: const EdgeInsets.fromLTRB(4, 0, 4, 0), + child: Text( + station, + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 18, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + ); + }, + ), + FutureDisplay( + future: trainData.nextStop.arrival, + builder: (context, arrival) { + const months = ["ian", "feb", "mar", "apr", "mai", "iun", "iul", "aug", "sep", "oct", "noi", "dec"]; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "în ${arrival.day} ${months[arrival.month - 1]} ${arrival.year}", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 14, + ), + textAlign: TextAlign.center, + ), + Text( + "la ${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 14, + ), + textAlign: TextAlign.center, + ), + ], + ); + }, + ) + ], + ); + } + ); + } +} + +class DisplayTrainDestination extends StatelessWidget { + final OnDemandTrainData trainData; + + DisplayTrainDestination({@required this.trainData}); + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: trainData.destination.stationName, + builder: (context, snapshot) { + if (!snapshot.hasData) return Container(); + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(4), + child: Text( + "Destinația", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ), + CupertinoDivider( + color: Color.fromRGBO(15, 15, 15, 1), + ), + FutureDisplay( + future: trainData.destination.stationName, + builder: (context, station) { + return Padding( + padding: const EdgeInsets.fromLTRB(4, 0, 4, 0), + child: Text( + station, + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 18, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + ); + }, + ), + FutureDisplay( + future: trainData.destination.arrival, + builder: (context, arrival) { + const months = ["ian", "feb", "mar", "apr", "mai", "iun", "iul", "aug", "sep", "oct", "noi", "dec"]; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "în ${arrival.day} ${months[arrival.month - 1]} ${arrival.year}", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 14, + ), + textAlign: TextAlign.center, + ), + Text( + "la ${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 14, + ), + textAlign: TextAlign.center, + ), + ], + ); + }, + ) + ], + ); + } + ); + } +} + +class DisplayTrainRouteDistance extends StatelessWidget { + final OnDemandTrainData trainData; + + DisplayTrainRouteDistance({@required this.trainData}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Distanța rutei", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + FutureDisplay( + future: trainData.routeDistance, + builder: (context, distance) { + return Text( + "$distance km", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 16, + ), + textAlign: TextAlign.center, + ); + }, + ), + ], + ); + } +} + +class DisplayTrainRouteDuration extends StatelessWidget { + final OnDemandTrainData trainData; + + DisplayTrainRouteDuration({@required this.trainData}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Durata rutei", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + FutureDisplay( + future: trainData.routeDuration, + builder: (context, duration) { + var durationString = StringBuffer(); + + bool firstWritten = false; + + if (duration.inDays > 0) { + firstWritten = true; + if (duration.inDays == 1) durationString.write("1 zi"); + else durationString.write("${duration.inDays} zile"); + duration -= Duration(days: duration.inDays); + } + + if (duration.inHours > 0) { + if (firstWritten) { + durationString.write(", "); + } + firstWritten = true; + if (duration.inHours == 1) durationString.write("1 oră"); + else durationString.write("${duration.inHours} ore"); + duration -= Duration(hours: duration.inHours); + } + + if (duration.inMinutes > 0) { + if (firstWritten) { + durationString.write(", "); + } + firstWritten = true; + if (duration.inMinutes == 1) durationString.write("1 minut"); + else durationString.write("${duration.inMinutes} minute"); + duration -= Duration(minutes: duration.inMinutes); + } + + return Text( + durationString.toString(), + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 16, + ), + textAlign: TextAlign.center, + ); + }, + ), + ], + ); + } +} + +class DisplayTrainStations extends StatelessWidget { + final OnDemandTrainData trainData; + final Future pageLoadFuture; + + DisplayTrainStations({@required this.trainData, @required this.pageLoadFuture}); + + @override + Widget build(BuildContext context) { + return StreamBuilder>( + stream: listifyStream(trainData.stations(pageLoadFuture: pageLoadFuture)), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return SliverToBoxAdapter( + child: Container(), + ); + } + + return SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + if (index.isOdd) { + return CupertinoDivider(); + } + else { + final itemIndex = index ~/ 2; + return IndexedSemantics( + child: DisplayTrainStation( + station: snapshot.data[itemIndex], + ), + index: itemIndex, + ); + } + }, + childCount: snapshot.data.length * 2 - 1, + addSemanticIndexes: false, + ), + ); + }, + ); + } +} + +class DisplayTrainStation extends StatelessWidget { + final OnDemandStation station; + + DisplayTrainStation({@required this.station}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: const EdgeInsets.all(8), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all( + width: 2, + color: FOREGROUND_WHITE, + ), + // color: CupertinoColors.activeOrange, + ), + width: 48, + height: 48, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Center( + child: FutureDisplay( + future: station.km, + builder: (context, value) { + return Text( + value.toString(), + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 18, + fontWeight: FontWeight.w100, + ), + textAlign: TextAlign.center, + ); + }, + ), + ), + ), + Text( + "km", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(fontSize: 10), + ), + ], + ), + ), + ), + Expanded( + child: FutureDisplay>( + future: Future.wait([ + station.stationName, + station.observations + ]), + builder: (context, items) { + return Text( + items[0], + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 22, + fontWeight: FontWeight.w100, + fontStyle: items[1] == "ONI" ? FontStyle.italic : FontStyle.normal, + ), + textAlign: TextAlign.center, + ); + }, + ) + ) + ], + ), + FutureDisplay>( + future: Future.wait([ + station.arrivalTime, + station.stopsFor, + station.departureTime + ]), + builder: (context, items) { + if (items[0].isEmpty) { + // Plecare + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded(child: Container(),), + Text("plecare la ${items[2]}"), + Container(width: 2,), + Text( + "→", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 22, + ), + ), + ], + ); + } + + if (items[2].isEmpty) { + // Sosire + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "→", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 22, + ), + ), + Container(width: 2,), + Text("sosire la ${items[0]}"), + Expanded(child: Container(),), + ], + ); + } + + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "→", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 22, + ), + ), + Container(width: 2,), + Text(items[0]), + Expanded(child: Container(),), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Builder( + builder: (context) { + if (items[1].isEmpty || items[1] == "0") { + return Container(); + } + if (items[1] == "1") { + return Text( + "staționează pentru\n1 minut", + textAlign: TextAlign.center, + ); + } + return Text( + "staționează pentru\n${items[1]} minute", + textAlign: TextAlign.center, + ); + } + ), + FutureBuilder( + future: station.observations, + builder: (context, snapshot) { + if (snapshot.data == "ONI") { + return Text( + "oprire ne-itinerarică", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontStyle: FontStyle.italic, + ), + textAlign: TextAlign.center, + ); + } + + return Container(); + }, + ) + ], + ), + Expanded(child: Container(),), + Text(items[2]), + Container(width: 2,), + Text( + "→", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + fontSize: 22, + ), + ), + ], + ); + }, + ), + FutureDisplay( + future: station.delay, + builder: (context, delay) { + if (delay == 0) return Container(); + + else if (delay > 0) { + return Text( + "$delay minute întârziere", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + color: CupertinoColors.destructiveRed, + fontSize: 12, + fontStyle: FontStyle.italic, + ), + ); + } + + else if (delay < 0) { + return Text( + "${-delay} minute mai devreme", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith( + color: CupertinoColors.activeGreen, + fontSize: 12, + fontStyle: FontStyle.italic, + ), + ); + } + + return Container(); + }, + ) + ], + ); + } +} + +class CupertinoDivider extends StatelessWidget { + final Color color; + + CupertinoDivider({Key key, Color color}): + color = color ?? FOREGROUND_DARK_GREY, + super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + height: 1, + ), + Container( + height: 1, + decoration: BoxDecoration( + color: color, + ), + ), + Container( + height: 1, + ), + ], + ); + } +} + +class CupertinoVerticalDivider extends StatelessWidget { + final Color color; + + CupertinoVerticalDivider({Key key, Color color}): + color = color ?? FOREGROUND_DARK_GREY, + super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 1, + ), + Container( + width: 1, + decoration: BoxDecoration( + color: color, + ), + ), + Container( + width: 1, + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/train_info_page/train_info_material.dart b/lib/train_info_page/train_info_material.dart new file mode 100644 index 0000000..f660e85 --- /dev/null +++ b/lib/train_info_page/train_info_material.dart @@ -0,0 +1,1165 @@ +import 'package:info_tren/train_info_page/train_info_animation_helpers.dart'; +import 'package:info_tren/utils/stream_list.dart'; + +import '../models/train_data.dart'; +import './train_info.dart'; + +import 'package:flutter/material.dart'; + +class TrainInfoMaterial extends StatefulWidget { + final int trainNumber; + + TrainInfoMaterial({@required this.trainNumber}); + + @override + _TrainInfoMaterialState createState() => _TrainInfoMaterialState(); +} + +class _TrainInfoMaterialState extends State with TrainInfoMixin { + @override + void initState() { + super.initState(); + + title = widget.trainNumber.toString(); + showTrainData = false; + requestedData = false; + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + if (!requestedData) { + requestedData = true; + + TrainDataWebViewAdapter.of(context).loadTrain(widget.trainNumber).then((value) { + setState(() { + lookupResult = value; + }); + + if (lookupResult == TrainLookupResult.NOT_FOUND) { + Future.delayed(Duration(seconds: 5), () { + Navigator.of(context).pop(); + }); + } + else if (lookupResult == TrainLookupResult.FOUND) { + Future.delayed(Duration(seconds: 1, milliseconds: 500), () { + setState(() { + showTrainData = true; + }); + }); + } + }); + } + } + + @override + Widget build(BuildContext context) { + if (!showTrainData) { + return _TrainInfoMaterialBefore( + title: title, + lookupResult: lookupResult, + ); + } + else { + return _TrainDataMaterialAfter( + title: title, + ); + } + } +} + +class _TrainInfoMaterialBefore extends StatefulWidget { + final String title; + final TrainLookupResult lookupResult; + + _TrainInfoMaterialBefore({@required this.title, @required this.lookupResult}); + + @override + _TrainInfoMaterialBeforeState createState() => _TrainInfoMaterialBeforeState(); +} + +class _TrainInfoMaterialBeforeState extends State<_TrainInfoMaterialBefore> { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: Text(widget.title ?? ""), + ), + body: SafeArea( + bottom: false, + child: StreamBuilder( + stream: TrainDataWebViewAdapter.of(context).progressStream, + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.none: + return Container(); + case ConnectionState.waiting: + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CircularProgressIndicator(), + Text( + "Conectare...", + style: Theme.of(context).textTheme.title, + ), + ], + ), + ); + case ConnectionState.active: + break; + case ConnectionState.done: + Navigator.of(context).pop(); + return Container(); + } + + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ProgressReportDisplayEntry( + key: ValueKey(1), + completed: 1 <= snapshot.data.current, + waitingText: "Se crează WebView", + completedText: "WebView a fost creat", + ), + ProgressReportDisplayEntry( + key: ValueKey(2), + completed: 2 <= snapshot.data.current, + waitingText: "Se încarcă pagina Informatica Feroviară", + completedText: "Pagina Informatica Feroviară a fost încărcată", + ), + ProgressReportDisplayEntry( + key: ValueKey(3), + completed: 3 <= snapshot.data.current, + waitingText: "Se încarcă informațiile despre tren", + completedText: "Informațiile despre tren au fost încărcate", + ), + if (widget.lookupResult != null) + ...[ + Container(height: 20,), + SizedBox( + width: double.infinity, + child: AnimatedBackground( + animationDuration: Duration(milliseconds: 250), + initialColor: Theme.of(context).scaffoldBackgroundColor, + backgroundColor: + widget.lookupResult == TrainLookupResult.FOUND + ? Colors.green + : Colors.red, + child: Center( + child: Row( + children: [ + Expanded(child: Container(),), + if (widget.lookupResult == TrainLookupResult.FOUND) + Padding( + padding: const EdgeInsets.fromLTRB(8, 8, 0, 8), + child: Center( + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Colors.greenAccent), + ) + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + widget.lookupResult == TrainLookupResult.FOUND + ? "Trenul a fost găsit" + : widget.lookupResult == TrainLookupResult.NOT_FOUND + ? "Trenul nu a fost găsit" + : "A apărut o eroare în căutarea trenului", + style: Theme.of(context).textTheme.title, + ), + ), + Expanded(child: Container(),), + ], + ), + ), + ), + ), + ], + ], + ), + ); + }, + ), + ), + ); + } +} + +bool isSmallScreen(BuildContext context) => MediaQuery.of(context).size.height <= 425; + +class _TrainDataMaterialAfter extends StatefulWidget { + final String title; + + _TrainDataMaterialAfter({@required this.title}); + + @override + _TrainDataMaterialAfterState createState() => _TrainDataMaterialAfterState(); +} + +class _TrainDataMaterialAfterState extends State<_TrainDataMaterialAfter> { + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: TrainDataWebViewAdapter.of(context).trainData(onInvalidation: () { + Navigator.of(context).pop(); + }), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: Text(widget.title ?? ""), + ), + body: SafeArea( + child: Center( + child: CircularProgressIndicator(), + ), + ), + ); + } + + return Scaffold( + appBar: isSmallScreen(context) ? null : AppBar( + centerTitle: true, + title: FutureBuilder>( + future: Future.wait([ + snapshot.data.rang, + snapshot.data.trainNumber + ]), + builder: (context, snapshot) { + if (snapshot.hasData) { + return Text("Informații despre ${snapshot.data[0]} ${snapshot.data[1]}"); + } + else { + return Text(widget.title ?? ""); + } + }, + ), + ), + body: Column( + children: [ + if (isSmallScreen(context)) + FutureBuilder>( + future: Future.wait([ + snapshot.data.rang, + snapshot.data.trainNumber, + ]), + builder: (context, snapshot) { + var title = "INFO TREN"; + if (snapshot.hasData) title = "INFO TREN ─ ${snapshot.data[0]} ${snapshot.data[1]}"; + + return SlimAppBar( + title: title, + ); + } + ), + Expanded( + child: SafeArea( + bottom: false, + child: CustomScrollView( + slivers: [ + SliverToBoxAdapter( + child: DisplayTrainID(trainData: snapshot.data,), + ), + SliverToBoxAdapter( + child: DisplayTrainOperator(trainData: snapshot.data,), + ), + SliverPadding( + padding: const EdgeInsets.only(left: 2, right: 2), + sliver: SliverToBoxAdapter( + child: DisplayTrainRoute(trainData: snapshot.data,), + ), + ), + SliverToBoxAdapter( + child: DisplayTrainDeparture(trainData: snapshot.data,), + ), + SliverToBoxAdapter( + child: Divider( + color: Colors.white70, + height: isSmallScreen(context) ? 8 : 16, + ), + ), + SliverToBoxAdapter( + child: DisplayTrainLastInfo(trainData: snapshot.data,), + ), + SliverToBoxAdapter( + child: IntrinsicHeight( + child: Row( + children: [ + Expanded(child: DisplayTrainNextStop(trainData: snapshot.data,)), + Expanded(child: DisplayTrainDestination(trainData: snapshot.data,)), + ], + ), + ), + ), + SliverToBoxAdapter( + child: IntrinsicHeight( + child: Row( + children: [ + Expanded(child: DisplayTrainRouteDuration(trainData: snapshot.data,)), + Expanded(child: DisplayTrainRouteDistance(trainData: snapshot.data,)), + ], + ), + ), + ), + SliverToBoxAdapter( + child: Divider( + color: Colors.white70, + height: isSmallScreen(context) ? 8 : 16, + ), + ), + DisplayTrainStations( + trainData: snapshot.data, + pageLoadFuture: TrainDataWebViewAdapter.of(context).nextLoadFuture, + ), + SliverToBoxAdapter( + child: Container( + height: MediaQuery.of(context).viewPadding.bottom, + ), + ), + ], + ), + ), + ), + ], + ), + ); + }, + ); + } +} + +class DisplayTrainID extends StatelessWidget { + final OnDemandTrainData trainData; + + DisplayTrainID({@required this.trainData}); + + @override + Widget build(BuildContext context) { + return FutureDisplay>( + future: Future.wait([ + trainData.rang, + trainData.trainNumber, + ]), + builder: (context, list) { + return Text( + "${list[0]} ${list[1]}", + style: (isSmallScreen(context) + ? Theme.of(context).textTheme.display1 + : Theme.of(context).textTheme.display2).copyWith( + color: Theme.of(context).textTheme.body1.color, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ); + }, + ); + } +} + +class DisplayTrainOperator extends StatelessWidget { + final OnDemandTrainData trainData; + + DisplayTrainOperator({@required this.trainData}); + + @override + Widget build(BuildContext context) { + return FutureDisplay( + future: trainData.operator, + builder: (context, op) { + return Text( + op, + style: Theme.of(context).textTheme.body1.copyWith( + fontStyle: FontStyle.italic, + fontSize: isSmallScreen(context) ? 12 : 14, + ), + textAlign: TextAlign.center, + ); + }, + ); + } +} + +class DisplayTrainRoute extends StatelessWidget { + final OnDemandTrainData trainData; + + DisplayTrainRoute({@required this.trainData}); + + @override + Widget build(BuildContext context) { + return FutureDisplay( + future: Future.wait([trainData.route.from, trainData.route.to]), + builder: (context, routePieces) { + return Row( + children: [ + Center( + child: Padding( + padding: const EdgeInsets.all(4), + child: Text( + routePieces[0], + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: 16, + ), + ), + ), + ), + Expanded(child: Container(),), + Center(child: Text("-")), + Expanded(child: Container(),), + Center( + child: Padding( + padding: const EdgeInsets.all(4), + child: Text( + routePieces[1], + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: 16, + ), + textAlign: TextAlign.right, + ), + ), + ), + ], + ); + }, + ); + } +} + +class DisplayTrainDeparture extends StatelessWidget { + final OnDemandTrainData trainData; + + DisplayTrainDeparture({@required this.trainData}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(2), + child: FutureDisplay( + future: trainData.departureDate, + builder: (context, dataPlecare) { + return Text( + "Plecare în ${dataPlecare.day.toString().padLeft(2, '0')}.${dataPlecare.month.toString().padLeft(2, '0')}.${dataPlecare.year.toString().padLeft(4, '0')}", + style: Theme.of(context).textTheme.body1.copyWith( + fontStyle: FontStyle.italic, + fontWeight: FontWeight.w200, + fontSize: isSmallScreen(context) ? 12 : 14, + ), + textAlign: TextAlign.center, + ); + }, + ), + ); + } +} + +class DisplayTrainLastInfo extends StatelessWidget { + final OnDemandTrainData trainData; + + DisplayTrainLastInfo({@required this.trainData}); + + @override + Widget build(BuildContext context) { + return Card( + child: Padding( + padding: const EdgeInsets.all(2), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Center( + child: Padding( + padding: const EdgeInsets.all(2), + child: Text( + "Ultima informație", + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: isSmallScreen(context) ? 18 : 20, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + Row( + children: [ + Padding( + padding: const EdgeInsets.all(4), + child: FutureDisplay( + future: trainData.lastInfo.station, + builder: (context, station) { + return Text( + station, + style: Theme.of(context).textTheme.body1, + textAlign: TextAlign.left, + ); + }, + ), + ), + Expanded(child: Container(),), + Padding( + padding: const EdgeInsets.all(4), + child: FutureDisplay( + future: trainData.lastInfo.event, + builder: (context, event) { + return Text( + event, + style: Theme.of(context).textTheme.body1, + textAlign: TextAlign.right, + ); + }, + ), + ), + ], + ), + Padding( + padding: const EdgeInsets.all(2), + child: Row( + children: [ + FutureDisplay( + future: trainData.lastInfo.dateAndTime, + builder: (context, dt) { + return Text( + "Raportat în ${dt.day.toString().padLeft(2, '0')}.${dt.month.toString().padLeft(2, '0')}.${dt.year.toString().padLeft(4, '0')}, la ${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}", + textAlign: TextAlign.center, + ); + }, + ), + Expanded(child: Container(),), + FutureBuilder( + initialData: 0, + future: trainData.lastInfo.delay, + builder: (context, snapshot) { + if (snapshot.data == 0) { + return Container(); + } + + if (snapshot.data > 0) { + return Text( + "${snapshot.data} minute întârziere", + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: 14, + color: Color.fromRGBO(200, 30, 15, 1), + ), + ); + } + else { + return Text( + "${-snapshot.data} minute mai devreme", + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: 12, + color: Color.fromRGBO(15, 200, 15, 1), + ), + ); + } + }, + ), + ], + ), + ), + ], + ), + ), + ); + } +} + +class DisplayTrainNextStop extends StatelessWidget { + final OnDemandTrainData trainData; + + DisplayTrainNextStop({@required this.trainData}); + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: trainData.nextStop.stationName, + builder: (context, snapshot) { + if (!snapshot.hasData) return Container(height: 0,); + + return Card( + child: Center( + child: Padding( + padding: const EdgeInsets.all(2), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(4), + child: Text( + "Următoarea oprire", + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: isSmallScreen(context) ? 18 : 20, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ), + FutureDisplay( + future: trainData.nextStop.stationName, + builder: (context, station) { + return Padding( + padding: const EdgeInsets.fromLTRB(4, 0, 4, 0), + child: Text( + station, + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: isSmallScreen(context) ? 16 : 18, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + ); + }, + ), + FutureDisplay( + future: trainData.nextStop.arrival, + builder: (context, arrival) { + const months = ["ian", "feb", "mar", "apr", "mai", "iun", "iul", "aug", "sep", "oct", "noi", "dec"]; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "în ${arrival.day} ${months[arrival.month - 1]} ${arrival.year}", + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: isSmallScreen(context) ? 12 : 14, + ), + textAlign: TextAlign.center, + ), + Text( + "la ${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}", + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: isSmallScreen(context) ? 12 : 14, + ), + textAlign: TextAlign.center, + ), + ], + ); + }, + ) + ], + ), + ), + ), + ); + } + ); + } +} + +class DisplayTrainDestination extends StatelessWidget { + final OnDemandTrainData trainData; + + DisplayTrainDestination({@required this.trainData}); + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: trainData.destination.stationName, + builder: (context, snapshot) { + if (!snapshot.hasData) return Container(height: 0,); + + return Card( + child: Center( + child: Padding( + padding: const EdgeInsets.all(2), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(4), + child: Text( + "Destinația", + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: isSmallScreen(context) ? 18 : 20, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ), + FutureDisplay( + future: trainData.destination.stationName, + builder: (context, station) { + return Padding( + padding: const EdgeInsets.fromLTRB(4, 0, 4, 0), + child: Text( + station, + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: isSmallScreen(context) ? 16 : 18, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + ); + }, + ), + FutureDisplay( + future: trainData.destination.arrival, + builder: (context, arrival) { + const months = ["ian", "feb", "mar", "apr", "mai", "iun", "iul", "aug", "sep", "oct", "noi", "dec"]; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "în ${arrival.day} ${months[arrival.month - 1]} ${arrival.year}", + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: isSmallScreen(context) ? 12 : 14, + ), + textAlign: TextAlign.center, + ), + Text( + "la ${arrival.hour.toString().padLeft(2, '0')}:${arrival.minute.toString().padLeft(2, '0')}", + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: isSmallScreen(context) ? 12 : 14, + ), + textAlign: TextAlign.center, + ), + ], + ); + }, + ) + ], + ), + ), + ), + ); + } + ); + } +} + +class DisplayTrainRouteDistance extends StatelessWidget { + final OnDemandTrainData trainData; + + DisplayTrainRouteDistance({@required this.trainData}); + + @override + Widget build(BuildContext context) { + return Card( + child: Center( + child: Padding( + padding: const EdgeInsets.all(2), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Distanța rutei", + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: isSmallScreen(context) ? 16 : 18, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + FutureDisplay( + future: trainData.routeDistance, + builder: (context, distance) { + return Text( + "$distance km", + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: isSmallScreen(context) ? 14 : 16, + ), + textAlign: TextAlign.center, + ); + }, + ), + ], + ), + ), + ), + ); + } +} + +class DisplayTrainRouteDuration extends StatelessWidget { + final OnDemandTrainData trainData; + + DisplayTrainRouteDuration({@required this.trainData}); + + @override + Widget build(BuildContext context) { + return Card( + child: Center( + child: Padding( + padding: const EdgeInsets.all(2), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Durata rutei", + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: isSmallScreen(context) ? 16 : 18, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + FutureDisplay( + future: trainData.routeDuration, + builder: (context, duration) { + var durationString = StringBuffer(); + + bool firstWritten = false; + + if (duration.inDays > 0) { + firstWritten = true; + if (duration.inDays == 1) durationString.write("1 zi"); + else durationString.write("${duration.inDays} zile"); + duration -= Duration(days: duration.inDays); + } + + if (duration.inHours > 0) { + if (firstWritten) { + durationString.write(", "); + } + firstWritten = true; + if (duration.inHours == 1) durationString.write("1 oră"); + else durationString.write("${duration.inHours} ore"); + duration -= Duration(hours: duration.inHours); + } + + if (duration.inMinutes > 0) { + if (firstWritten) { + durationString.write(", "); + } + firstWritten = true; + if (duration.inMinutes == 1) durationString.write("1 minut"); + else durationString.write("${duration.inMinutes} minute"); + duration -= Duration(minutes: duration.inMinutes); + } + + return Text( + durationString.toString(), + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: isSmallScreen(context) ? 14 : 16, + ), + textAlign: TextAlign.center, + ); + }, + ), + ], + ), + ), + ), + ); + } +} + +class DisplayTrainStations extends StatelessWidget { + final OnDemandTrainData trainData; + final Future pageLoadFuture; + + DisplayTrainStations({@required this.trainData, @required this.pageLoadFuture}); + + @override + Widget build(BuildContext context) { + return StreamBuilder>( + stream: listifyStream(trainData.stations(pageLoadFuture: pageLoadFuture)), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return SliverToBoxAdapter( + child: Container(), + ); + } + + return SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + return IndexedSemantics( + child: DisplayTrainStation( + station: snapshot.data[index], + ), + index: index, + ); + }, + childCount: snapshot.data.length, + addSemanticIndexes: true, + ), + ); + }, + ); + } +} + +class DisplayTrainStation extends StatelessWidget { + final OnDemandStation station; + + DisplayTrainStation({@required this.station}); + + @override + Widget build(BuildContext context) { + return Card( + child: Padding( + padding: const EdgeInsets.all(2), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: const EdgeInsets.all(8), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all( + width: 2, + color: Colors.white70, + ), + // color: CupertinoColors.activeOrange, + ), + width: isSmallScreen(context) ? 42 : 48, + height: isSmallScreen(context) ? 42 : 48, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Center( + child: FutureDisplay( + future: station.km, + builder: (context, value) { + return Text( + value.toString(), + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: isSmallScreen(context) ? 14 : 18, + fontWeight: FontWeight.w100, + ), + textAlign: TextAlign.center, + ); + }, + ), + ), + ), + Text( + "km", + style: Theme.of(context).textTheme.body1.copyWith(fontSize: 10), + ), + ], + ), + ), + ), + Expanded( + child: FutureDisplay>( + future: Future.wait([ + station.stationName, + station.observations + ]), + builder: (context, items) { + return Text( + items[0], + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: isSmallScreen(context) ? 18 : 22, + fontWeight: FontWeight.w100, + fontStyle: items[1] == "ONI" ? FontStyle.italic : FontStyle.normal, + ), + textAlign: TextAlign.center, + ); + }, + ) + ) + ], + ), + FutureDisplay>( + future: Future.wait([ + station.arrivalTime, + station.stopsFor, + station.departureTime + ]), + builder: (context, items) { + if (items[0].isEmpty) { + // Plecare + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded(child: Container(),), + Text("plecare la ${items[2]}"), + Container(width: 2,), + Text( + "→", + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: isSmallScreen(context) ? 18 : 22, + ), + ), + ], + ); + } + + if (items[2].isEmpty) { + // Sosire + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "→", + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: isSmallScreen(context) ? 18 : 22, + ), + ), + Container(width: 2,), + Text("sosire la ${items[0]}"), + Expanded(child: Container(),), + ], + ); + } + + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + "→", + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: isSmallScreen(context) ? 18 : 22, + ), + ), + Container(width: 2,), + Text(items[0]), + Expanded(child: Container(),), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Builder( + builder: (context) { + if (items[1].isEmpty || items[1] == "0") { + return Container(); + } + if (items[1] == "1") { + return Text( + "staționează pentru\n1 minut", + textAlign: TextAlign.center, + ); + } + return Text( + "staționează pentru\n${items[1]} minute", + textAlign: TextAlign.center, + ); + } + ), + FutureBuilder( + future: station.observations, + builder: (context, snapshot) { + if (snapshot.data == "ONI") { + return Text( + "oprire ne-itinerarică", + style: Theme.of(context).textTheme.body1.copyWith( + fontStyle: FontStyle.italic, + ), + textAlign: TextAlign.center, + ); + } + + return Container(); + }, + ) + ], + ), + Expanded(child: Container(),), + Text(items[2]), + Container(width: 2,), + Text( + "→", + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: isSmallScreen(context) ? 18 : 22, + ), + ), + ], + ); + }, + ), + FutureDisplay( + future: station.delay, + builder: (context, delay) { + if (delay == 0) return Container(); + + else if (delay > 0) { + return Text( + "$delay minute întârziere", + style: Theme.of(context).textTheme.body1.copyWith( + color: Colors.red, + fontSize: 12, + fontStyle: FontStyle.italic, + ), + ); + } + + else if (delay < 0) { + return Text( + "${-delay} minute mai devreme", + style: Theme.of(context).textTheme.body1.copyWith( + color: Colors.green, + fontSize: 12, + fontStyle: FontStyle.italic, + ), + ); + } + + return Container(); + }, + ) + ], + ), + ), + ); + } +} + +class SlimAppBar extends StatelessWidget { + final String title; + final double size; + // final Function onBackTap; + + SlimAppBar({ + @required this.title, + this.size = 24, + // this.onBackTap, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: double.infinity, + height: size, + child: Container( + color: + Theme.of(context).appBarTheme?.color ?? + Theme.of(context).primaryColor, + child: InkWell( + onTap: (ModalRoute.of(context)?.canPop ?? false) + ? () => Navigator.of(context).pop() + : null, + child: Row( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + height: size, + width: size, + child: (ModalRoute.of(context)?.canPop ?? false) + ? BackButtonIcon() + : null, + ), + Expanded( + child: Center( + child: Padding( + padding: const EdgeInsets.all(2), + child: Text( + title, + textAlign: TextAlign.center, + style: + Theme.of(context).appBarTheme.textTheme?.caption?.copyWith(color: Theme.of(context).appBarTheme.textTheme?.body1?.color) ?? + Theme.of(context).textTheme.caption.copyWith(color: Theme.of(context).textTheme.body1.color), + ), + ), + ), + ), + Container( + height: size, + width: size, + ), + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/train_info_page/train_info_prompt.dart b/lib/train_info_page/train_info_prompt.dart new file mode 100644 index 0000000..ac1108a --- /dev/null +++ b/lib/train_info_page/train_info_prompt.dart @@ -0,0 +1,402 @@ +import 'dart:convert'; +import 'dart:io' show Platform; + +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; +import 'package:info_tren/train_info_page/train_info.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:tuple/tuple.dart'; + +part 'train_info_prompt.g.dart'; + +typedef TrainSelectedCallback(int trainNumber); + +mixin TrainInfoPromptAction { + onTrainSelected(BuildContext context, int selection) { + if (Platform.isAndroid) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) { + return TrainInfo( + trainNumber: selection, + ); + }, + ) + ); + } + else if (Platform.isIOS) { + Navigator.of(context).push( + CupertinoPageRoute( + title: "Informații despre trenul $selection", + builder: (context) { + return TrainInfo( + trainNumber: selection, + ); + }, + ) + ); + } + } +} + +mixin TrainInfoPromptListHandling { + List operators = List(); + + Future loadOperators(BuildContext context) async { + operators = List(); + + final operatorsString = await DefaultAssetBundle.of(context).loadString("assets/lines/files.txt"); + final operatorsFilesList = operatorsString.split("\n"); + + final decoder = JsonDecoder(); + + for (final operatorFile in operatorsFilesList) { + final operatorString = await DefaultAssetBundle.of(context).loadString("assets/lines/$operatorFile"); + final operatorData = decoder.convert(operatorString); + final _operator = TrainOperatorLines.fromJson(operatorData); + operators.add(_operator); + } + } + + Widget getOperatorsListView(BuildContext context, {String currentInput = "", @required TrainSelectedCallback onTrainSelected}) { + var sliversTuple = operators.map( + (op) => Tuple2( + getFilteredLines(op, currentInput), + getOperatorSliver(context, op, currentInput, onTrainSelected) + ) + ) + .where((tuple) => tuple.item1.isNotEmpty).toList(); + if (currentInput.isNotEmpty) sliversTuple.sort((a, b) { + final aTrain = a.item1.first; + final bTrain = b.item1.first; + + final inputAsRegExp = RegExp(currentInput); + + final matchOnA = inputAsRegExp.firstMatch(aTrain.number); + final matchOnB = inputAsRegExp.firstMatch(bTrain.number); + + if (matchOnA.start != matchOnB.start) return matchOnA.start - matchOnB.start; + + if (aTrain.number.length != bTrain.number.length) return aTrain.number.length - bTrain.number.length; + + return aTrain.number.compareTo(bTrain.number); + }); + var slivers = sliversTuple.map((tuple) => tuple.item2).toList(); + + return CustomScrollView( + slivers: [ + ...slivers, + SliverToBoxAdapter( + child: getUseCurrentInputWidget(currentInput, onTrainSelected), + ), + SliverToBoxAdapter( + child: Container( + height: MediaQuery.of(context).viewPadding.bottom, + ), + ), + ], + ); + } + + Widget getUseCurrentInputWidget(String currentInput, TrainSelectedCallback onTrainSelected) { + if (currentInput.isEmpty) { + return Container(); + } + + if (int.tryParse(currentInput) == null) { + return Container(); + } + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (Platform.isAndroid) + ListTile( + title: Text("Caută trenul cu numărul $currentInput"), + onTap: () { + onTrainSelected(int.parse(currentInput)); + }, + ) + else if (Platform.isIOS) + GestureDetector( + onTap: () { + onTrainSelected(int.parse(currentInput)); + }, + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text("Caută trenul cu numărul $currentInput") + ], + ) + ), + ), + Divider(), + ], + ); + } + + List<_TrainOperatorTrainDescription> getFilteredLines(TrainOperatorLines _operator, String currentInput) { + if (currentInput.isNotEmpty) { + final filteredLines = _operator.trains + .where((elem) => elem.number.contains(currentInput)) + .toList(); + + filteredLines.sort((a, b) { + final inputAsRegExp = RegExp(currentInput); + + final matchOnA = inputAsRegExp.firstMatch(a.number); + final matchOnB = inputAsRegExp.firstMatch(b.number); + + if (matchOnA.start != matchOnB.start) return matchOnA.start - matchOnB.start; + + if (a.number.length != b.number.length) return a.number.length - b.number.length; + + return a.number.compareTo(b.number); + }); + + return filteredLines; + } + else { + return _operator.trains; + } + } + + Widget getOperatorSliver(BuildContext context, TrainOperatorLines _operator, String currentInput, TrainSelectedCallback onTrainSelected) { + final filteredLines = getFilteredLines(_operator, currentInput); + + if (filteredLines.isEmpty) { + return SliverToBoxAdapter(child: Container(),); + } + + return SliverPrototypeExtentList( + prototypeItem: Column( + children: [ + getLineListItem( + context, + op: TrainOperatorLines(), + line: _TrainOperatorTrainDescription() + ), + Divider(), + ], + ), + delegate: SliverChildBuilderDelegate( + (context, index) { + return Column( + children: [ + getLineListItem( + context, + op: _operator, + line: filteredLines[index], + onTrainSelected: onTrainSelected + ), + Divider(), + ], + ); + }, + childCount: filteredLines.length, + addSemanticIndexes: true, + ), + ); + } + + Widget getLineListItem(BuildContext context, {TrainOperatorLines op, _TrainOperatorTrainDescription line, TrainSelectedCallback onTrainSelected}) { + if (Platform.isAndroid) { + return ListTile( + dense: true, + title: Text("${line.rang ?? ""} ${line.number ?? ""}"), + subtitle: Text(op.operator ?? ""), + onTap: () { + onTrainSelected(line.internalNumber); + }, + ); + } + else if (Platform.isIOS) { + return GestureDetector( + onTap: () { + onTrainSelected(line.internalNumber); + }, + child: Padding( + padding: const EdgeInsets.fromLTRB(16, 2, 16, 2), + child: SizedBox( + width: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + op.operator ?? "", + style: CupertinoTheme.of(context).textTheme.textStyle.copyWith(fontSize: 10, fontWeight: FontWeight.w200), + textAlign: TextAlign.left, + ), + Text( + "${line.rang ?? ""} ${line.number ?? ""}", + textAlign: TextAlign.left, + ), + ], + ), + ), + ), + ); + } + + return null; + } +} + +class TrainInfoPromptMaterial extends StatefulWidget { + @override + _TrainInfoPromptMaterialState createState() => _TrainInfoPromptMaterialState(); +} + +class _TrainInfoPromptMaterialState extends State with TrainInfoPromptAction, TrainInfoPromptListHandling { + TextEditingController trainNoController = TextEditingController(); + + @override + void initState() { + super.initState(); + + loadOperators(context).then((_) { + setState(() {}); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("Informații despre tren"), + centerTitle: true, + ), + body: SafeArea( + bottom: false, + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: const EdgeInsets.all(4), + child: TextField( + controller: trainNoController, + autofocus: true, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: "Numărul trenului", + ), + inputFormatters: [ + WhitelistingTextInputFormatter.digitsOnly, + ], + textInputAction: TextInputAction.search, + keyboardType: TextInputType.number, + onChanged: (_) { + setState(() {}); + }, + ), + ), + Expanded( + child: getOperatorsListView(context, currentInput: trainNoController.text, onTrainSelected: (number) { + onTrainSelected(context, number); + }) + ) + ], + ), + ), + ); + } +} + +class TrainInfoPromptCupertino extends StatefulWidget { + @override + _TrainInfoPromptCupertinoState createState() => _TrainInfoPromptCupertinoState(); +} + +class _TrainInfoPromptCupertinoState extends State with TrainInfoPromptAction, TrainInfoPromptListHandling { + TextEditingController trainNoController = TextEditingController(); + + @override + void initState() { + super.initState(); + + loadOperators(context).then((_) { + setState(() {}); + }); + } + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: Text("Informații despre tren"), + ), + child: SafeArea( + bottom: false, + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: const EdgeInsets.all(4), + child: CupertinoTextField( + controller: trainNoController, + autofocus: true, + placeholder: "Numărul trenului", + textInputAction: TextInputAction.search, + keyboardType: TextInputType.number, + onChanged: (_) { + setState(() {}); + }, + inputFormatters: [ + WhitelistingTextInputFormatter.digitsOnly, + ], + ), + ), + Expanded( + child: getOperatorsListView(context, currentInput: trainNoController.text, onTrainSelected: (number) { + onTrainSelected(context, number); + }) + ) + ], + ), + ), + ); + } +} + +@JsonSerializable() +class TrainOperatorLines { + @JsonKey(name: "short_name") + final String shortName; + final String operator; + @JsonKey(name: "versiune") + final String version; + @JsonKey(name: "trenuri") + final List<_TrainOperatorTrainDescription> trains; + + TrainOperatorLines({ + this.operator, + this.shortName = "", + this.version, + this.trains, + }); + + factory TrainOperatorLines.fromJson(Map json) => _$TrainOperatorLinesFromJson(json); + Map toJson() => _$TrainOperatorLinesToJson(this); +} + +@JsonSerializable() +class _TrainOperatorTrainDescription { + final String rang; + @JsonKey(name: "numar") + final String number; + @JsonKey(name: "numar_intern") + final int internalNumber; + + _TrainOperatorTrainDescription({ + this.number, + this.rang, + this.internalNumber + }); + + factory _TrainOperatorTrainDescription.fromJson(Map json) => _$_TrainOperatorTrainDescriptionFromJson(json); + Map toJson() => _$_TrainOperatorTrainDescriptionToJson(this); +} \ No newline at end of file diff --git a/lib/train_info_page/train_info_prompt.g.dart b/lib/train_info_page/train_info_prompt.g.dart new file mode 100644 index 0000000..fed6cd2 --- /dev/null +++ b/lib/train_info_page/train_info_prompt.g.dart @@ -0,0 +1,44 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'train_info_prompt.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +TrainOperatorLines _$TrainOperatorLinesFromJson(Map json) { + return TrainOperatorLines( + operator: json['operator'] as String, + shortName: json['short_name'] as String, + version: json['versiune'] as String, + trains: (json['trenuri'] as List) + ?.map((e) => e == null + ? null + : _TrainOperatorTrainDescription.fromJson( + e as Map)) + ?.toList()); +} + +Map _$TrainOperatorLinesToJson(TrainOperatorLines instance) => + { + 'short_name': instance.shortName, + 'operator': instance.operator, + 'versiune': instance.version, + 'trenuri': instance.trains + }; + +_TrainOperatorTrainDescription _$_TrainOperatorTrainDescriptionFromJson( + Map json) { + return _TrainOperatorTrainDescription( + number: json['numar'] as String, + rang: json['rang'] as String, + internalNumber: json['numar_intern'] as int); +} + +Map _$_TrainOperatorTrainDescriptionToJson( + _TrainOperatorTrainDescription instance) => + { + 'rang': instance.rang, + 'numar': instance.number, + 'numar_intern': instance.internalNumber + }; diff --git a/lib/utils/stream_list.dart b/lib/utils/stream_list.dart new file mode 100644 index 0000000..355f99e --- /dev/null +++ b/lib/utils/stream_list.dart @@ -0,0 +1,10 @@ +import 'dart:async'; + +Stream> listifyStream(Stream stream) async* { + List list = List(); + + await for (T item in stream) { + list.add(item); + yield list; + } +} \ No newline at end of file diff --git a/lib/utils/webview_invoke.dart b/lib/utils/webview_invoke.dart new file mode 100644 index 0000000..8dba886 --- /dev/null +++ b/lib/utils/webview_invoke.dart @@ -0,0 +1,34 @@ +import 'dart:convert'; +import 'dart:io' show Platform; + +import 'package:flutter/foundation.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +/// Evaluates a JavaScript function on the given WebView. +/// +/// The JavaScript function must return a String. +/// +/// On Android, the `String` resulted from the evaluation +/// is JSON parsed. On iOS, the `String` is returned as is. +/// +/// Other platforms are not supported. The returned value +/// in this case will be `null`. +Future wInvoke({ + @required WebViewController webViewController, + @required String jsFunctionContent, + bool isFunctionAlready = false +}) async { + final actualJS = isFunctionAlready ? + jsFunctionContent : + """ + (() => { + $jsFunctionContent + })() + """; + + final res = await webViewController.evaluateJavascript(actualJS); + + if (Platform.isAndroid) return JsonDecoder().convert(res) as String; + else if (Platform.isIOS) return res; + else return null; +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 74c84ac..b5a0e43 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -127,6 +127,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.16.1" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.2" dart_style: dependency: transitive description: @@ -408,6 +415,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.1+1" + tuple: + dependency: "direct main" + description: + name: tuple + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" typed_data: dependency: transitive description: @@ -436,6 +450,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.14" + webview_flutter: + dependency: "direct main" + description: + name: webview_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.11+2" yaml: dependency: transitive description: @@ -445,3 +466,4 @@ packages: version: "2.1.16" sdks: dart: ">=2.3.0 <3.0.0" + flutter: ">=1.5.0 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index c8ef9cf..5facb60 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: info_tren -description: A new Flutter project. +description: O aplicație de vizualizare a datelor puse la dispoziție de Informatica Feroviară.xe # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 @@ -11,7 +11,7 @@ description: A new Flutter project. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.0.0+1 +version: 2.0.2 environment: sdk: ">=2.3.0 <3.0.0" @@ -26,6 +26,9 @@ dependencies: json_annotation: ^2.0.0 rxdart: ^0.22.0 http: ^0.12.0 + webview_flutter: ^0.3.0 + cupertino_icons: ^0.1.2 + tuple: ^1.0.2 dev_dependencies: flutter_test: @@ -75,3 +78,5 @@ flutter: # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages + assets: + - assets/lines/ \ No newline at end of file