Merge: Tnitter: read the latest tnits on the go with the new Tnitter portable app!
authorJean Privat <jean@pryen.org>
Fri, 13 Nov 2015 13:29:29 +0000 (08:29 -0500)
committerJean Privat <jean@pryen.org>
Fri, 13 Nov 2015 13:29:29 +0000 (08:29 -0500)
Intro a portable client for Tnitter listing the more recent 16 tnits. It is more of a test and example on using `AsyncHttpRequest` to do both simple request to a REST server and implement push notifications using an open request. By design, this client does not support posting Tnits, this feature would require much more code and it may be simplified by future services in the lib.

To support the client the server has a few new REST interfaces. One to list the tnits and the other for push notifications.

This PR also fixes a few bugs on the Tnitter server: losing the session on posting and empty post being accepted by the server.

-----

Note for the reviewers: You may be better to read commits individually. There are 2 bigs commits, one extracts the database logic from the model module and the other introduces code generated by Jwrapper for the native layer of that Android ui implementation.

Pull-Request: #1834
Reviewed-by: Jean Privat <jean@pryen.org>
Reviewed-by: Romain Chanoir <romain.chanoir@viacesi.fr>

18 files changed:
contrib/tnitter/.gitignore [new file with mode: 0644]
contrib/tnitter/Makefile
contrib/tnitter/art/icon.svg [new file with mode: 0644]
contrib/tnitter/net.xymus.tnitter.txt [new file with mode: 0644]
contrib/tnitter/package.ini
contrib/tnitter/src/action.nit
contrib/tnitter/src/database.nit [new file with mode: 0644]
contrib/tnitter/src/model.nit
contrib/tnitter/src/push.nit [new file with mode: 0644]
contrib/tnitter/src/tnitter.nit
contrib/tnitter/src/tnitter_app.nit [new file with mode: 0644]
contrib/tnitter/src/tnitter_app_android.nit [new file with mode: 0644]
lib/android/ui/native_ui.nit
lib/android/ui/ui.nit
lib/app/ui.nit
lib/linux/ui.nit
lib/nitcorn/examples/src/xymus_net.nit
lib/serialization/serialization.nit

diff --git a/contrib/tnitter/.gitignore b/contrib/tnitter/.gitignore
new file mode 100644 (file)
index 0000000..074efe1
--- /dev/null
@@ -0,0 +1,2 @@
+res/
+tnitter.db
index 5bb713b..daa66df 100644 (file)
@@ -1,3 +1,27 @@
-all:
+SERVER ?= localhost:8080
+
+all: bin/tnitter_server bin/tnitter
+
+bin/tnitter_server: $(shell ../../bin/nitls -M src/tnitter.nit)
        mkdir -p bin/
-       ../../bin/nitc --dir bin src/tnitter.nit
+       ../../bin/nitc -o bin/tnitter_server src/tnitter.nit -D tnitter_interface=$(SERVER)
+
+bin/tnitter: $(shell ../../bin/nitls -M src/tnitter_app.nit)
+       mkdir -p bin/
+       ../../bin/nitc -o bin/tnitter src/tnitter_app.nit -m linux -D tnitter_server_uri=http://$(SERVER)
+
+android: bin/tnitter.apk
+bin/tnitter.apk: $(shell ../../bin/nitls -M src/tnitter_app_android.nit) res/drawable-ldpi/icon.png
+       mkdir -p bin/
+       ../../bin/nitc -o bin/tnitter.apk src/tnitter_app_android.nit -D tnitter_server_uri=http://$(SERVER)
+
+android-release: $(shell ../../bin/nitls -M src/tnitter_app_android.nit) res/drawable-ldpi/icon.png
+       mkdir -p bin/
+       ../../bin/nitc -o bin/tnitter.apk src/tnitter_app_android.nit --release -D tnitter_server_uri=http://tnitter.xymus.net
+
+res/drawable-ldpi/icon.png: art/icon.svg
+       mkdir -p res
+       ../inkscape_tools/bin/svg_to_icons art/icon.svg --android --out res/
+
+clean:
+       rm -r res bin
diff --git a/contrib/tnitter/art/icon.svg b/contrib/tnitter/art/icon.svg
new file mode 100644 (file)
index 0000000..2852416
--- /dev/null
@@ -0,0 +1,177 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="128"
+   height="128"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.5 r10040"
+   sodipodi:docname="icon.svg">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#424242"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="2.8"
+     inkscape:cx="55.835349"
+     inkscape:cy="64.659054"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="1025"
+     inkscape:window-height="882"
+     inkscape:window-x="3574"
+     inkscape:window-y="219"
+     inkscape:window-maximized="0" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-924.36218)">
+    <g
+       id="g3849"
+       transform="matrix(0.86316133,0,0,0.92568956,1.3546541,74.131119)">
+      <g
+         transform="matrix(0.98889119,0,0,1.0112336,-3.8034556,-1.9096758)"
+         style="font-size:163.82507324px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#0056b8;fill-opacity:1;stroke:none;font-family:Sans"
+         id="text3003">
+        <path
+           style="font-variant:normal;font-weight:bold;font-stretch:normal;fill:#ffffff;font-family:URW Bookman L;-inkscape-font-specification:URW Bookman L Bold"
+           d="m 16.28125,918.6875 c -4.705635,13.04532 -9.4589216,26.10668 -14.1875,39.15625 7.0084772,1.03459 12.946005,7.14991 20.34375,5.65625 3.998453,-10.46648 12.089657,-21.32521 23.90625,-22.71875 0.02185,23.88035 0.939134,47.95599 -0.5,71.71875 -3.223782,4.5917 -10.820374,1.1397 -14.3125,4.2188 -2.65775,6.4155 -0.05103,13.5408 -1.25,20.2187 20.708333,0 41.416667,0 62.125,0 -1.587381,-6.9919 2.030222,-14.9268 -2.0625,-21.3125 -4.464239,-0.8933 -12.884373,1.1467 -14,-4.9062 -0.367202,-23.38258 -0.54133,-47.10622 0.0625,-70.2813 12.170603,1.57376 18.880393,12.93168 24.84375,22.28125 5.64266,3.82234 10.89596,-3.30022 16.4375,-3.6875 5.18679,-5.23592 -1.4805,-12.19999 -3,-17.5 -2.59942,-8.18301 -6.21861,-16.07757 -9.1875,-24.15625 -29.583333,0 -59.166667,0 -88.75,0 l -0.336336,0.94174 -0.132414,0.37076 z"
+           id="path3868"
+           inkscape:connector-curvature="0" />
+        <path
+           d="m 102.74224,921.36982 -83.16697,0 -12.2868809,33.91179 12.2868809,4.75092 c 5.733871,-13.92511 17.201643,-23.42698 28.177912,-23.42698 2.457374,0 2.785026,0.81913 2.785026,5.8977 l 0,62.90885 c 0,6.3892 -0.491476,8.6827 -1.9659,10.321 -1.474425,1.802 -5.570057,3.2765 -9.174205,3.4403 l -4.914752,0.3276 0,13.4337 53.678458,0 0,-13.4337 -4.914752,-0.3276 c -3.604148,-0.1638 -7.69978,-1.6383 -9.174204,-3.4403 -1.474424,-1.6383 -1.965901,-3.9318 -1.965901,-10.321 l 0,-63.40032 c 0,-5.73388 3e-6,-5.73388 3.276502,-5.73388 5.733872,0 11.959236,3.11268 18.020762,9.01038 3.76797,3.60415 5.07858,5.40623 10.648634,14.74425 l 11.95923,-4.75092 -13.26984,-33.91179"
+           style="font-variant:normal;font-weight:bold;font-stretch:normal;fill:#0056b8;font-family:URW Bookman L;-inkscape-font-specification:URW Bookman L Bold"
+           id="path3866"
+           inkscape:connector-curvature="0"
+           sodipodi:nodetypes="ccccsssccccccccssscccc" />
+      </g>
+    </g>
+    <g
+       id="g3854"
+       transform="translate(-64.407738,82.937113)">
+      <g
+         transform="translate(228.55805,10.397392)"
+         id="g3814">
+        <path
+           style="font-size:41.02794266px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#f9f9f9;fill-opacity:1;stroke:none;font-family:URW Bookman L;-inkscape-font-specification:URW Bookman L Bold"
+           d="m -145.34375,990.375 c -3.7065,0.9099 -5.59041,0.5441 -9.3125,0.375 -6.18402,-1.45274 -11.54689,5.24506 -6.28125,8.46875 3.24988,0.2177 0.53746,6.75195 0.9375,8.53125 -6.09851,1.7138 -2.2516,10.9927 4.3125,7.5625 7.61458,0 15.22917,0 22.84375,0 2.48858,-6.3131 -3.58762,-5.9391 -3.03125,-10.8125 0.9533,-5.30171 0.005,-13.15379 -6.53125,-14 -0.95852,-0.19727 -1.96125,-0.17788 -2.9375,-0.125 z m -1.21875,8.4375 c -0.27832,2.8197 0.84219,6.834 -0.40625,9.0937 -3.94943,1.0063 -2.06697,-7.0314 -1.375,-8.56245 0.55768,-0.26017 1.16587,-0.46001 1.78125,-0.53125 z"
+           id="path3784"
+           inkscape:connector-curvature="0" />
+        <path
+           style="font-size:41.02794266px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#f9f9f9;fill-opacity:1;stroke:none;font-family:URW Bookman L;-inkscape-font-specification:URW Bookman L Bold"
+           d="m -127.15625,981.375 c -4.82475,0.23855 -7.38628,6.89888 -3.78125,10.21875 -5.25095,-1.6637 -5.64832,8.62805 -1,7.75 0.67533,2.21505 0.27341,6.12945 0,8.40625 -6.30526,1.3829 -2.49822,11.0989 4.15625,7.5625 6.25663,1.6232 13.32182,-0.9694 8.87245,-7.1129 -4.94093,-1.3009 -0.7382,-9.68127 -2.12245,-13.6996 -0.01,-3.21562 -0.90884,-3.73474 0.3125,-6.6875 0.41635,-3.61723 -2.80904,-6.84579 -6.4375,-6.4375 z"
+           id="path3786"
+           inkscape:connector-curvature="0" />
+        <path
+           style="font-size:41.02794266px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#f9f9f9;fill-opacity:1;stroke:none;font-family:URW Bookman L;-inkscape-font-specification:URW Bookman L Bold"
+           d="m -114.125,985.9375 c 0.1473,3.22183 -2.60427,5.82702 -5.90625,5.5 -2.03484,7.72234 5.32745,6.04755 3.09375,13.9687 -1.04679,4.9714 2.04444,10.8915 7.6875,10.2813 4.36016,0.229 10.50688,-2.9576 10.508602,-6.9182 -2.092672,-5.2734 -5.562622,-4.1403 -7.539852,-2.6443 -3.5525,-9.40667 6.13994,-5.5684 5.625,-11.9375 -0.64163,-4.90004 -7.61276,-3.47057 -6.625,-10 -2.04644,0.73016 -7.2787,-1.70476 -6.84375,1.75 z"
+           id="path3788"
+           inkscape:connector-curvature="0" />
+        <path
+           style="font-size:41.02794266px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#f9f9f9;fill-opacity:1;stroke:none;font-family:URW Bookman L;-inkscape-font-specification:URW Bookman L Bold"
+           d="m -95.21875,985.9375 c 0.209507,3.34071 -2.682917,5.76623 -5.90625,5.5 -2.03484,7.72234 5.327447,6.04755 3.09375,13.9687 -1.081014,4.9828 2.04361,10.8619 7.6875,10.2813 4.364539,0.2201 10.502894,-2.9526 10.508602,-6.9182 -2.121768,-5.2406 -5.492611,-4.1707 -7.539852,-2.6443 -3.552504,-9.40667 6.139936,-5.5684 5.625,-11.9375 -0.641625,-4.90004 -7.612761,-3.47057 -6.625,-10 -2.046435,0.73016 -7.278705,-1.70476 -6.84375,1.75 z"
+           id="path3790"
+           inkscape:connector-curvature="0" />
+        <path
+           style="font-size:41.02794266px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#f9f9f9;fill-opacity:1;stroke:none;font-family:URW Bookman L;-inkscape-font-specification:URW Bookman L Bold"
+           d="m -70.4375,990.40625 c -10.317858,0.18203 -15.164879,14.21235 -8.09375,21.21875 6.156001,6.7944 18.754788,4.7181 22.108246,-4.0137 -2.901846,-3.5835 -0.826254,-1.2468 -0.764496,-6.9238 -0.0087,-6.87489 -6.930086,-11.07001 -13.25,-10.28125 z m 6.53125,14.46875 c -1.085489,2.6852 -5.862833,3.4887 -6.3125,0 2.104167,0 4.208333,0 6.3125,0 z"
+           id="path3792"
+           inkscape:connector-curvature="0" />
+        <path
+           style="font-size:41.02794266px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#f9f9f9;fill-opacity:1;stroke:none;font-family:URW Bookman L;-inkscape-font-specification:URW Bookman L Bold"
+           d="m -42.75,990.375 c -4.554778,1.13106 -9.064199,-0.29303 -14.0625,1.5625 -2.776314,0.27851 -2.857472,8.7305 1.28125,7.40625 -0.447767,3.18295 2.23733,9.34005 -2.90625,9.12505 -2.57007,8.5179 4.891488,7.0043 11,6.8437 6.202839,3.2417 8.353387,-6.8349 2.9375,-7.5625 0.212169,-1.4815 -2.076866,-9.08338 1.3125,-6.7188 5.874468,2.0588 9.501251,-6.7851 4.25,-9.7812 -1.118544,-0.73603 -2.487924,-1.01303 -3.8125,-0.875 z"
+           id="path3794"
+           inkscape:connector-curvature="0" />
+      </g>
+      <text
+         xml:space="preserve"
+         style="font-size:41.02794266px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#0056b8;fill-opacity:1;stroke:none;font-family:Sans"
+         x="66.978233"
+         y="1023.7053"
+         id="text3007"
+         sodipodi:linespacing="125%"><tspan
+           sodipodi:role="line"
+           id="tspan3009"
+           x="66.978233"
+           y="1023.7053"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;fill:#0056b8;fill-opacity:1;font-family:URW Bookman L;-inkscape-font-specification:URW Bookman L Bold">nitter</tspan></text>
+    </g>
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m -63.214286,998.07647 c 63.66311686,-15.23793 65.8748289,76.72883 26.785715,76.07143 -28.928572,1.0714 -79.285719,-32.1428 -79.285719,-32.1428"
+       id="path3908"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="ccc" />
+    <g
+       id="g3962"
+       transform="translate(-12.142857,0)">
+      <path
+         inkscape:connector-curvature="0"
+         id="path3920"
+         d="m 66.46875,970.4375 c -6.562779,-0.13271 -10.025109,9.67333 -3.75,12.625 0.789881,3.37687 -1.193479,5.13275 -3.125,7.34375 -2.690748,4.45074 1.951528,11.44545 7.03125,8.65625 8.687389,0.20255 17.37466,0.41161 26.0625,0.59375 1.467354,-3.91075 0.925253,-8.74606 -2.40625,-11.5625 1.123785,-6.51616 0.206678,-16.2791 -7.90625,-17.46875 -5.237659,-0.27805 -10.592185,0.53026 -15.90625,-0.1875 z"
+         style="font-size:41.03342056px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;line-height:125%;letter-spacing:-7px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:URW Bookman L;-inkscape-font-specification:URW Bookman L Bold" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3922"
+         d="m 98.8125,965.40625 c -4.305692,-0.1128 -7.186478,4.3379 -8,7.71875 -4.62439,1.19185 -6.361317,7.85092 -3.1875,11.03125 -0.387949,2.87359 -1.551682,4.64587 -4.375,5.03125 -5.108778,2.82518 -3.372908,12.3355 3.0625,11.25 4.274032,2.0206 13.149269,6.9298 14.6875,-0.71875 0.13221,-3.61996 -2.518059,-7.63625 0.4375,-11.15625 2.72294,-3.92152 2.25551,-9.07832 5.40625,-12.75 1.84186,-5.05465 -2.63792,-10.98276 -8.03125,-10.40625 z"
+         style="font-size:41.03342056px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;line-height:125%;letter-spacing:-7px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:URW Bookman L;-inkscape-font-specification:URW Bookman L Bold" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3924"
+         d="m 112.0625,975.03125 c -2.26829,0.95425 -3.80408,4.80933 -6.9375,2.375 -3.70026,-2.27152 -7.917357,4.18214 -6.3125,8.09375 0.285868,3.78414 -4.813994,5.35969 -5.46875,9.125 -3.769874,7.2045 3.673573,14.1293 10.59375,14.4375 3.8453,0.2145 10.07097,0 8.34375,-5.3125 0.37689,-2.6527 -0.76196,-6.06227 -3,-7.03125 1.99291,-2.87685 5.38998,0.29568 7.16906,-0.002 4.30747,-1.87365 5.36454,-7.44265 1.79969,-10.65407 -0.81179,-2.45399 4.36213,-4.02444 1.34375,-6.78125 -1.85623,-2.15224 -4.54547,-4.45958 -7.53125,-4.25 z"
+         style="font-size:41.03342056px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;line-height:125%;letter-spacing:-7px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:URW Bookman L;-inkscape-font-specification:URW Bookman L Bold" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3926"
+         d="m 118.375,988.5 c -4.16506,0.59412 -6.41213,4.50504 -6.78125,8.375 -3.39415,2.17572 -8.36499,3.8913 -9.5,8.625 -2.465245,9.0183 8.04775,15.7027 16.09375,14.9375 1.41453,-3.5791 3.21699,-9.2294 0.40625,-11.8125 3.50413,-0.3267 7.36143,3.3414 10.90625,0.4688 3.88412,-1.6793 1.70801,-6.2144 1.15625,-8.8126 5.82357,-0.573 3.14468,-7.85809 -0.3125,-10.0312 -2.2809,-2.09653 -5.07008,-0.2614 -7.0625,0.5625 -1.58487,-0.80025 -2.94233,-2.5364 -4.90625,-2.3125 z"
+         style="font-size:41.03342056px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;line-height:125%;letter-spacing:-7px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:URW Bookman L;-inkscape-font-specification:URW Bookman L Bold" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3928"
+         d="m 122.71875,1006.4062 c -11.66736,-0.3145 -18.65649,15.0468 -11.46875,23.9688 3.1584,4.507 8.8431,6.3245 14.09375,7.0938 -1.35031,-3.9148 3.0842,-2.2416 5.09375,-4.0313 12.43975,-5.172 7.76061,-26.1891 -5.25,-26.9375 -0.80353,-0.1488 -1.6497,-0.157 -2.46875,-0.094 z"
+         style="font-size:41.03342056px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;line-height:125%;letter-spacing:-7px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:URW Bookman L;-inkscape-font-specification:URW Bookman L Bold" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path3930"
+         d="m 132.8125,1025.125 c -2.60576,-0.045 -5.12599,1.3837 -5.75,3.8125 -1.86399,-0.6881 -4.54349,1.1734 -5.625,-1.5937 -3.65456,-4.677 -11.78915,-0.8158 -10.59375,5 0.47245,4.1766 0.24394,8.8597 1.09375,12.5 3.4265,3.8137 8.662,0.5493 11.71875,-1.5626 3.09382,-1.0345 0.61555,3.9955 3.03125,5.375 4.02656,5.4763 13.85638,2.4145 13.5,-4.5937 -1.53003,-5.9633 1.22754,-13.5727 -3.8125,-18.1563 -1.04157,-0.6897 -2.33061,-0.8868 -3.5625,-0.7812 z"
+         style="font-size:41.03342056px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;line-height:125%;letter-spacing:-7px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:URW Bookman L;-inkscape-font-specification:URW Bookman L Bold" />
+    </g>
+    <text
+       sodipodi:linespacing="125%"
+       id="text3876"
+       style="font-size:41.03342056000000326px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:-7px;word-spacing:0px;fill:#0056b8;fill-opacity:1;stroke:none;font-family:Sans"
+       xml:space="preserve"
+       transform="translate(113.57143,-1.0714286)"><textPath
+         xlink:href="#path3908"
+         id="textPath3910"><tspan
+   id="tspan3878"
+   style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;fill:#0056b8;fill-opacity:1;font-family:URW Bookman L;-inkscape-font-specification:URW Bookman L Bold;letter-spacing:-7px">nitter</tspan></textPath></text>
+  </g>
+</svg>
diff --git a/contrib/tnitter/net.xymus.tnitter.txt b/contrib/tnitter/net.xymus.tnitter.txt
new file mode 100644 (file)
index 0000000..b5bbe24
--- /dev/null
@@ -0,0 +1,10 @@
+Categories:Nit,Internet
+License:Apache2
+Web Site:http://tnitter.xymus.net/
+Source Code:http://nitlanguage.org/nit.git/tree/HEAD:/contrib/tnitter
+Issue Tracker:https://github.com/nitlang/nit/issues
+
+Summary:Minimal client for the Tnitter open source micro-blogging platform
+Description:
+Read the latest Tnits published on Tnitter and keep up to date with push notification support.
+This is a sample application to do asynchronous HTTP requests from portable Nit code.
index 26ba69a..a01d725 100644 (file)
@@ -10,3 +10,4 @@ git.directory=contrib/tnitter/
 homepage=http://nitlanguage.org
 issues=https://github.com/nitlang/nit/issues
 tryit=http://tnitter.xymus.net/
+apk=http://nitlanguage.org/fdroid/apk/tnitter.apk
index d95cfe3..e40e309 100644 (file)
 module action
 
 import nitcorn
+import json::serialization
 
 import model
+import database
+
+# Path to the Sqlite3 database
+fun tnitter_db_path: String do return "tnitter.db"
 
 redef class Session
        # User logged in
@@ -27,12 +32,9 @@ redef class Session
 end
 
 # Main Tnitter Action
-class Tnitter
+class TnitterWeb
        super Action
 
-       var db_path = "tnitter.db"
-       var db = new DB.open(db_path)
-
        # Header on pages served by this `Action`
        #
        # Keywords to `Text::replace`:
@@ -99,6 +101,8 @@ class Tnitter
                # Error to display on page as a dismissable panel
                var error = null
 
+               var db = new DB.open(tnitter_db_path)
+
                # Login/logout
                if turi == "/login" and request.post_args.keys.has("user") and
                   request.post_args.keys.has("pass") then
@@ -132,14 +136,16 @@ class Tnitter
                        session = null
                else if turi == "/post" and request.post_args.keys.has("text") and session != null then
                        var user = session.user
-                       if user != null then
+                       var text = request.post_args["text"].trim
+                       if user != null and not text.is_empty then
                                # Post a Tnit!
-                               var text = request.post_args["text"]
                                db.post(user, text)
+                               db.close
 
                                # Redirect the user to avoid double posting
                                var response = new HttpResponse(303)
                                response.header["Location"] = request.uri
+                               response.session = session
                                return response
                        end
                end
@@ -203,7 +209,8 @@ class Tnitter
                else error_html = ""
 
                # Load the last 16 Tnits
-               var posts = db.latest_posts(16)
+               var posts = db.list_posts(0, 16)
+               db.close
 
                var html_posts = new Array[String]
                for post in posts do
@@ -235,3 +242,32 @@ class Tnitter
                return response
        end
 end
+
+# Tnitter RESTful interface
+class TnitterREST
+       super Action
+
+       redef fun answer(request, turi)
+       do
+               if turi == "/list" then
+                       # list?from=1&count=2 -> Error | Array[Post]
+
+                       var from = request.int_arg("from") or else 0
+                       var count = request.int_arg("count") or else 8
+
+                       var db = new DB.open(tnitter_db_path)
+                       var posts = db.list_posts(from, count)
+                       db.close
+
+                       var response = new HttpResponse(200)
+                       response.body = posts.to_json_string
+                       return response
+               end
+
+               # Format not recognized
+               var error = new Error("Bad Request")
+               var response = new HttpResponse(400)
+               response.body = error.to_json_string
+               return response
+       end
+end
diff --git a/contrib/tnitter/src/database.nit b/contrib/tnitter/src/database.nit
new file mode 100644 (file)
index 0000000..b38fa9b
--- /dev/null
@@ -0,0 +1,93 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Database interface of Tnitter
+module database
+
+import sqlite3
+
+import model
+
+# The Tnitter database
+class DB
+       super Sqlite3DB
+
+       redef init open(path)
+       do
+               super
+               create_tables
+       end
+
+       # Create the needed tables
+       fun create_tables
+       do
+               assert create_table("IF NOT EXISTS users (user TEXT PRIMARY KEY, pass TEXT)") else
+                       print error or else "?"
+               end
+
+               assert create_table("IF NOT EXISTS posts (user TEXT, text TEXT, posted DATETIME DEFAULT CURRENT_TIMESTAMP)") else
+                       print error or else "?"
+               end
+       end
+
+       # Check if the login credentials are valid
+       #
+       # If valid, returns the username at database creation time. Otherwise returns `null`.
+       fun check_login(user, pass: String): nullable String
+       do
+               var stmt = select("user FROM users WHERE lower({user.to_sql_string}) = lower(user) " +
+                                 "AND {pass.tnitter_hash.to_sql_string} = pass")
+               assert stmt != null else print error or else "?"
+
+               for row in stmt do return row[0].to_s
+               return null
+       end
+
+       # Try to sign up a new user, return `true` on success
+       fun sign_up(user, pass: String): Bool
+       do
+               # Check if already in user
+               var stmt = select("user FROM users WHERE lower({user.to_sql_string}) = lower(user)")
+               assert stmt != null else print error or else "?"
+
+               if not stmt.iterator.to_a.is_empty then return false
+
+               # Insert intro BD
+               assert insert("INTO users(user, pass) VALUES ({user.to_sql_string}, {pass.tnitter_hash.to_sql_string})") else
+                       print error or else "?"
+               end
+
+               return true
+       end
+
+       # Tnit something
+       fun post(user, text: String)
+       do
+               assert insert("INTO posts(user, text) VALUES ({user.to_sql_string}, {text.to_sql_string})") else
+                       print error or else "?"
+               end
+       end
+
+       # List `count` of the latest Tnits skipping `offset`
+       fun list_posts(offset, count: Int): Array[Post]
+       do
+               var stmt = select("user, text FROM posts ORDER BY datetime(posted) DESC LIMIT {count} OFFSET {offset}")
+               assert stmt != null else print error or else "?"
+
+               var posts = new Array[Post]
+               for row in stmt do posts.add new Post(row[0].to_s, row[1].to_s)
+
+               return posts
+       end
+end
index bb46193..68aeb6e 100644 (file)
 # Data and DB model of Tnitter
 module model
 
-import sqlite3
 import md5
-
-# The Tnitter database
-class DB
-       super Sqlite3DB
-
-       redef init open(path)
-       do
-               super
-               create_tables
-       end
-
-       # Create the needed tables
-       fun create_tables
-       do
-               assert create_table("IF NOT EXISTS users (user TEXT PRIMARY KEY, pass TEXT)") else
-                       print error or else "?"
-               end
-
-               assert create_table("IF NOT EXISTS posts (user TEXT, text TEXT, posted DATETIME DEFAULT CURRENT_TIMESTAMP)") else
-                       print error or else "?"
-               end
-       end
-
-       # Check if the login credentials are valid
-       #
-       # If valid, returns the username at database creation time. Otherwise returns `null`.
-       fun check_login(user, pass: String): nullable String
-       do
-               var stmt = select("user FROM users WHERE lower({user.to_sql_string}) = lower(user) " +
-                                 "AND {pass.tnitter_hash.to_sql_string} = pass")
-               assert stmt != null else print error or else "?"
-
-               for row in stmt do return row[0].to_s
-               return null
-       end
-
-       # Try to sign up a new user, return `true` on success
-       fun sign_up(user, pass: String): Bool
-       do
-               # Check if already in user
-               var stmt = select("user FROM users WHERE lower({user.to_sql_string}) = lower(user)")
-               assert stmt != null else print error or else "?"
-
-               if not stmt.iterator.to_a.is_empty then return false
-
-               # Insert intro BD
-               assert insert("INTO users(user, pass) VALUES ({user.to_sql_string}, {pass.tnitter_hash.to_sql_string})") else
-                       print error or else "?"
-               end
-
-               return true
-       end
-
-       # Tnit something
-       fun post(user, text: String)
-       do
-               assert insert("INTO posts(user, text) VALUES ({user.to_sql_string}, {text.to_sql_string})") else
-                       print error or else "?"
-               end
-       end
-
-       # Get the latest tnits
-       fun latest_posts(count: Int): Array[Post]
-       do
-               var stmt = select("user, text FROM posts ORDER BY datetime(posted) DESC LIMIT {count}")
-               assert stmt != null else print error or else "?"
-
-               var posts = new Array[Post]
-               for row in stmt do posts.add new Post(row[0].to_s, row[1].to_s)
-
-               return posts
-       end
-end
+import serialization
 
 # A single post (or Tnit)
 class Post
+       serialize
+
        # The author
        var user: String
 
diff --git a/contrib/tnitter/src/push.nit b/contrib/tnitter/src/push.nit
new file mode 100644 (file)
index 0000000..cdb2945
--- /dev/null
@@ -0,0 +1,60 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Tnitter support for push notifications
+module push
+
+import nitcorn
+import json::serialization
+
+import model
+import database
+
+# Tnitter push notification interface
+class TnitterPush
+       super Action
+
+       # Intercept the full answer to set aside the connection and complete it later
+       redef fun prepare_respond_and_close(request, turi, connection)
+       do
+               push_connections.add connection
+       end
+end
+
+redef class Sys
+       # Connections left open for a push notification
+       private var push_connections = new Array[HttpServer]
+end
+
+redef class DB
+       # Reopen to trigger sending push notifications
+       redef fun post(user, text)
+       do
+               super
+
+               # Everyone gets the same response
+               var posts = list_posts(0, 16)
+               var response = new HttpResponse(400)
+               response.body = posts.to_json_string
+
+               for conn in push_connections do
+                       # Complete the answer to `conn`
+                       conn.respond response
+                       conn.close
+               end
+
+               # Clients need to open new connections
+               push_connections.clear
+       end
+end
index 490fc07..4b175c2 100644 (file)
@@ -21,6 +21,7 @@ import privileges
 
 import model
 import action
+import push
 
 redef class OptionContext
        var drop = new OptionUserAndGroup.for_dropping_privileges
@@ -29,6 +30,9 @@ redef class OptionContext
        init do add_option(drop, help)
 end
 
+# Address and port of the listening socket
+fun tnitter_interface: String do return "localhost:8080"
+
 # Avoid executing when running tests
 if "NIT_TESTING".environ == "true" then exit 0
 
@@ -42,14 +46,8 @@ if not opts.errors.is_empty or opts.help.value then
        exit 1
 end
 
-# If we can, we use port 80
-var interfac
-if sys.uid == 0 then # Are we root?
-       interfac = "localhost:80"
-else interfac = "localhost:8080"
-
 # Setup server
-var vh = new VirtualHost(interfac)
+var vh = new VirtualHost(tnitter_interface)
 var factory = new HttpFactory.and_libevent
 factory.config.virtual_hosts.add vh
 
@@ -58,8 +56,10 @@ var user_group = opts.drop.value
 if user_group != null then user_group.drop_privileges
 
 # Complete server config
-vh.routes.add new Route(null, new Tnitter)
+vh.routes.add new Route("/rest/", new TnitterREST)
+vh.routes.add new Route("/push/", new TnitterPush)
+vh.routes.add new Route(null, new TnitterWeb)
 
 # Run
-print "Launching server on http://{interfac} ..."
+print "Launching server on http://{tnitter_interface} ..."
 factory.run
diff --git a/contrib/tnitter/src/tnitter_app.nit b/contrib/tnitter/src/tnitter_app.nit
new file mode 100644 (file)
index 0000000..dcbfffe
--- /dev/null
@@ -0,0 +1,168 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Tnitter minimal portable app listing the latest Tnits
+#
+# This app use push notification to be updated in the second a new Tnit is posted.
+# So it begins by requesting a full list and the successive push request for updates.
+# If an error occurs, the full list is requested again after a short delay.
+#
+# This approach may still miss a few updates it they happen too close to one another.
+# To solve this, we could send an id for the latest known Tnit to the server.
+# Which could recognize if a client is not up to date.
+module tnitter_app is
+       app_name "Tnitter"
+       app_version(0, 1, git_revision)
+       app_namespace "net.xymus.tnitter"
+end
+
+import app::ui
+import app::http_request
+import app::data_store
+import android::aware
+import json::serialization
+
+import model
+
+# Delay in seconds before the next request after an error
+fun request_delay_on_error: Int do return 60
+
+redef class App
+       redef fun on_create
+       do
+               # Create the main window
+               window = new TnitterWindow
+               super
+       end
+end
+
+# Main window
+class TnitterWindow
+       super Window
+
+       private var layout = new VerticalLayout(parent=self)
+       private var list_posts = new ListLayout(parent=layout)
+       private var lbl_init = new Label(parent=list_posts, text="Awaiting connection to server")
+
+       # Request an initial full update
+       init do (new ListPostRequest(self, "rest/list?count=16", false)).start
+
+       # Request a full update after a delay
+       fun request_full_list_on_error
+       do
+               (new ListPostRequest(self, "rest/list?count=16", true)).start
+       end
+
+       # Open a push notification connection and thread
+       fun request_push_notification
+       do
+               (new ListPostRequest(self, "push/", false)).start
+       end
+
+       # Update the screen to show the new `posts`
+       fun apply_update(posts: Array[Post])
+       do
+               layout.remove list_posts
+               list_posts = new ListLayout(parent=layout)
+               for post in posts do
+                       var line = new VerticalLayout(parent=list_posts)
+                       var author = new LabelAuthor(parent=line, text="@"+post.user)
+                       var text = new Label(parent=line, text=post.text)
+               end
+       end
+end
+
+# Label to display the author's name
+#
+# By default, this view is identical a `Label`,
+# but if can be refined per platforms.
+class LabelAuthor super Label end
+
+# ---
+# Async RESTful actions
+
+# URI of the remote RESTful server
+fun tnitter_server_uri: String do return "http://localhost:8080"
+
+# `AsyncHttpRequest` with services to act on the windows of the app
+abstract class AsyncTnitterRequest
+       super AsyncHttpRequest
+
+       private var window: TnitterWindow
+
+       redef fun rest_server_uri do return tnitter_server_uri
+
+       redef var rest_action
+
+       # Should this request be delayed by `request_delay_on_error` seconds?
+       var delay: Bool
+
+       redef fun main
+       do
+               if delay then nanosleep(request_delay_on_error, 0)
+               return super
+       end
+end
+
+# Async request to list latest posts, either immediately or by push notification
+#
+# Implementation note:
+# This class could as well be merged with `AsyncTnitterRequest` or have two versions,
+# one for the immediate update and one for the push notification.
+# We chose this structure for simplicity of the example,
+# and as more services may be added in the future.
+# If these future services expect data of a different format,
+# they will need a different `on_load` but could still use `AsyncTnitterRequest`.
+class ListPostRequest
+       super AsyncTnitterRequest
+
+       redef fun on_load(posts)
+       do
+               # Deal with server-side errors
+               if posts isa Error then
+                       print_error "Server Error: '{posts.message}' from '{rest_server_uri / rest_action}'"
+                       return
+               end
+
+               # Type check
+               if not posts isa Array[Post] then
+                       print_error "Error: Got '{posts or else "null"}'"
+                       return
+               end
+
+               # Update UI and prepare for the next update
+               window.apply_update posts
+               window.request_push_notification
+       end
+
+       redef fun on_fail(error)
+       do
+               print "Warning: Request {rest_server_uri/rest_action} failed with {error}"
+               window.request_full_list_on_error
+       end
+end
+
+# ---
+# Services
+
+redef class Deserializer
+       redef fun deserialize_class(name)
+       do
+               # This is usually generated using `nitserial`,
+               # but for a single generic class it is easier to implement manually
+
+               if name == "Array[Post]" then return new Array[Post].from_deserializer(self)
+               return super
+       end
+end
diff --git a/contrib/tnitter/src/tnitter_app_android.nit b/contrib/tnitter/src/tnitter_app_android.nit
new file mode 100644 (file)
index 0000000..74eb1b6
--- /dev/null
@@ -0,0 +1,28 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Android version of the Tnitter app
+module tnitter_app_android is
+       android_api_target 15
+end
+
+import tnitter_app
+
+import android::ui
+import android::http_request
+import android::portrait
+
+redef class LabelAuthor
+       init do native.set_text_appearance(app.native_activity, android_r_style_text_appearance_large)
+end
index aaae0bd..b6a3460 100644 (file)
@@ -149,10 +149,16 @@ extern class NativeTextView in "Java" `{ android.widget.TextView `}
        fun text_size: Float in "Java" `{
                return self.getTextSize();
        `}
+
        fun text_size=(dpi: Float) in "Java" `{
                self.setTextSize(android.util.TypedValue.COMPLEX_UNIT_DIP, (float)dpi);
        `}
 
+       # Java implementation:  android.widget.TextView.setTextAppearance(android.content.Context, int)
+       fun set_text_appearance(arg0: NativeContext, arg1: Int) in "Java" `{
+               self.setTextAppearance(arg0, (int)arg1);
+       `}
+
        redef fun new_global_ref import sys, Sys.jni_env `{
                Sys sys = NativeTextView_sys(self);
                JNIEnv *env = Sys_jni_env(sys);
@@ -189,3 +195,800 @@ extern class NativeButton in "Java" `{ android.widget.Button `}
                return (*env)->NewGlobalRef(env, self);
        `}
 end
+
+# Java class: android.widget.Adapter
+extern class Android_widget_Adapter in "Java" `{ android.widget.Adapter `}
+       super JavaObject
+
+       # Java implementation:  android.widget.Adapter.registerDataSetObserver(android.database.DataSetObserver)
+       #fun register_data_set_observer(arg0: Android_database_DataSetObserver) in "Java" `{
+       #self.registerDataSetObserver(arg0);
+       #`}
+
+       # Java implementation:  android.widget.Adapter.unregisterDataSetObserver(android.database.DataSetObserver)
+       #fun unregister_data_set_observer(arg0: Android_database_DataSetObserver) in "Java" `{
+       #self.unregisterDataSetObserver(arg0);
+       #`}
+
+       # Java implementation: int android.widget.Adapter.getCount()
+       fun get_count: Int in "Java" `{
+               return self.getCount();
+       `}
+
+       # Java implementation: java.lang.Object android.widget.Adapter.getItem(int)
+       fun get_item(arg0: Int): JavaObject in "Java" `{
+               return self.getItem((int)arg0);
+       `}
+
+       # Java implementation: long android.widget.Adapter.getItemId(int)
+       fun get_item_id(arg0: Int): Int in "Java" `{
+               return self.getItemId((int)arg0);
+       `}
+
+       # Java implementation: boolean android.widget.Adapter.hasStableIds()
+       fun has_stable_ids: Bool in "Java" `{
+               return self.hasStableIds();
+       `}
+
+       # Java implementation: android.view.View android.widget.Adapter.getView(int, android.view.View, android.view.ViewGroup)
+       fun get_view(arg0: Int, arg1: NativeView, arg2: NativeViewGroup): NativeView in "Java" `{
+               return self.getView((int)arg0, arg1, arg2);
+       `}
+
+       # Java implementation: int android.widget.Adapter.getItemViewType(int)
+       fun get_item_view_type(arg0: Int): Int in "Java" `{
+               return self.getItemViewType((int)arg0);
+       `}
+
+       # Java implementation: int android.widget.Adapter.getViewTypeCount()
+       fun get_view_type_count: Int in "Java" `{
+               return self.getViewTypeCount();
+       `}
+
+       # Java implementation: boolean android.widget.Adapter.isEmpty()
+       fun is_empty: Bool in "Java" `{
+               return self.isEmpty();
+       `}
+
+       redef fun new_global_ref import sys, Sys.jni_env `{
+               Sys sys = Android_widget_Adapter_sys(self);
+               JNIEnv *env = Sys_jni_env(sys);
+               return (*env)->NewGlobalRef(env, self);
+       `}
+
+       redef fun pop_from_local_frame_with_env(jni_env) `{
+               return (*jni_env)->PopLocalFrame(jni_env, self);
+       `}
+end
+
+# Java getter: android.widget.Adapter.IGNORE_ITEM_VIEW_TYPE
+fun android_widget_adapter_ignore_item_view_type: Int in "Java" `{
+       return android.widget.Adapter.IGNORE_ITEM_VIEW_TYPE;
+`}
+
+# Java getter: android.widget.Adapter.NO_SELECTION
+fun android_widget_adapter_no_selection: Int in "Java" `{
+       return android.widget.Adapter.NO_SELECTION;
+`}
+
+# Java class: android.widget.ListAdapter
+extern class Android_widget_ListAdapter in "Java" `{ android.widget.ListAdapter `}
+       super Android_widget_Adapter
+
+       # Java implementation: boolean android.widget.ListAdapter.areAllItemsEnabled()
+       fun are_all_items_enabled: Bool in "Java" `{
+               return self.areAllItemsEnabled();
+       `}
+
+       # Java implementation: boolean android.widget.ListAdapter.isEnabled(int)
+       fun is_enabled(arg0: Int): Bool in "Java" `{
+               return self.isEnabled((int)arg0);
+       `}
+
+       redef fun new_global_ref import sys, Sys.jni_env `{
+               Sys sys = Android_widget_ListAdapter_sys(self);
+               JNIEnv *env = Sys_jni_env(sys);
+               return (*env)->NewGlobalRef(env, self);
+       `}
+
+       redef fun pop_from_local_frame_with_env(jni_env) `{
+               return (*jni_env)->PopLocalFrame(jni_env, self);
+       `}
+end
+
+# Java class: android.widget.SpinnerAdapter
+extern class Android_widget_SpinnerAdapter in "Java" `{ android.widget.SpinnerAdapter `}
+       super Android_widget_Adapter
+
+       # Java implementation: android.view.View android.widget.SpinnerAdapter.getDropDownView(int, android.view.View, android.view.ViewGroup)
+       fun get_drop_down_view(arg0: Int, arg1: NativeView, arg2: NativeViewGroup): NativeView in "Java" `{
+               return self.getDropDownView((int)arg0, arg1, arg2);
+       `}
+
+       redef fun new_global_ref import sys, Sys.jni_env `{
+               Sys sys = Android_widget_SpinnerAdapter_sys(self);
+               JNIEnv *env = Sys_jni_env(sys);
+               return (*env)->NewGlobalRef(env, self);
+       `}
+
+       redef fun pop_from_local_frame_with_env(jni_env) `{
+               return (*jni_env)->PopLocalFrame(jni_env, self);
+       `}
+end
+
+# Java class: android.widget.BaseAdapter
+extern class Android_widget_BaseAdapter in "Java" `{ android.widget.BaseAdapter `}
+       super Android_widget_ListAdapter
+       super Android_widget_SpinnerAdapter
+
+       # Java implementation:  android.widget.BaseAdapter.notifyDataSetChanged()
+       fun notify_data_set_changed in "Java" `{
+               self.notifyDataSetChanged();
+       `}
+
+       # Java implementation:  android.widget.BaseAdapter.notifyDataSetInvalidated()
+       fun notify_data_set_invalidated in "Java" `{
+               self.notifyDataSetInvalidated();
+       `}
+
+       # Java constructor: android.widget.BaseAdapter
+       #new  in "Java" `{
+               #return new android.widget.BaseAdapter();
+       #`}
+
+       redef fun new_global_ref import sys, Sys.jni_env `{
+               Sys sys = Android_widget_BaseAdapter_sys(self);
+               JNIEnv *env = Sys_jni_env(sys);
+               return (*env)->NewGlobalRef(env, self);
+       `}
+
+       redef fun pop_from_local_frame_with_env(jni_env) `{
+               return (*jni_env)->PopLocalFrame(jni_env, self);
+       `}
+end
+
+# Java class: android.widget.ArrayAdapter<T>
+extern class Android_widget_ArrayAdapter in "Java" `{ android.widget.ArrayAdapter `}
+       super Android_widget_BaseAdapter
+       #super Android_widget_Filterable
+
+       # Java implementation:  android.widget.ArrayAdapter<T>.add(java.lang.Object)
+       fun add(arg0: JavaObject) in "Java" `{
+               self.add(arg0);
+       `}
+
+       # Java implementation:  android.widget.ArrayAdapter<T>.addAll(java.util.Collection<java.lang.Object>)
+       #fun add_all_Collection(arg0: JavaCollection) in "Java" `{
+               #self.addAll(arg0);
+       #`}
+
+       # Java implementation:  android.widget.ArrayAdapter<T>.addAll(java.lang.Object)
+#      fun add_all_Object(arg0: JavaObject) in "Java" `{
+#              self.addAll(arg0);
+#      `}
+
+       # Java implementation:  android.widget.ArrayAdapter<T>.insert(java.lang.Object, int)
+       fun insert(arg0: JavaObject, arg1: Int) in "Java" `{
+               self.insert(arg0, (int)arg1);
+       `}
+
+       # Java implementation:  android.widget.ArrayAdapter<T>.remove(java.lang.Object)
+       fun remove(arg0: JavaObject) in "Java" `{
+               self.remove(arg0);
+       `}
+
+       # Java implementation:  android.widget.ArrayAdapter<T>.clear()
+       fun clear in "Java" `{
+               self.clear();
+       `}
+
+       # Java implementation:  android.widget.ArrayAdapter<T>.sort(java.util.Comparator<T>)
+       #fun sort(arg0: Java_util_Comparator) in "Java" `{
+               #self.sort(arg0);
+       #`}
+
+       # Java implementation:  android.widget.ArrayAdapter<T>.setNotifyOnChange(boolean)
+       fun set_notify_on_change(arg0: Bool) in "Java" `{
+               self.setNotifyOnChange(arg0);
+       `}
+
+       # Java implementation: android.content.Context android.widget.ArrayAdapter<T>.getContext()
+       #fun get_context: NativeContext in "Java" `{
+               #return self.getContext();
+       #`}
+
+       # Java implementation: int android.widget.ArrayAdapter<T>.getPosition(java.lang.Object)
+       fun get_position(arg0: JavaObject): Int in "Java" `{
+               return self.getPosition(arg0);
+       `}
+
+       # Java implementation:  android.widget.ArrayAdapter<T>.setDropDownViewResource(int)
+       fun set_drop_down_view_resource(arg0: Int) in "Java" `{
+               self.setDropDownViewResource((int)arg0);
+       `}
+
+       # Java constructor: android.widget.ArrayAdapter<T>
+       #new (a: NativeContext, b: Int) in "Java" `{
+               #return new android.widget.ArrayAdapter(a, (int)b);
+       #`}
+
+       # Java constructor: android.widget.ArrayAdapter<T>
+       new from_Context_int_int(a: NativeContext, b: Int, c: Int) in "Java" `{
+               return new android.widget.ArrayAdapter(a, (int)b, (int)c);
+       `}
+
+       # Java constructor: android.widget.ArrayAdapter<T>
+       new from_Context_int_ObjectArray(a: NativeContext, b: Int, c: JavaArray) in "Java" `{
+               return new android.widget.ArrayAdapter(a, (int)b, c);
+       `}
+
+       # Java constructor: android.widget.ArrayAdapter<T>
+       new from_Context_int_int_ObjectArray(a: NativeContext, b: Int, c: Int, d: JavaArray) in "Java" `{
+               return new android.widget.ArrayAdapter(a, (int)b, (int)c, d);
+       `}
+
+       # Java constructor: android.widget.ArrayAdapter<T>
+       #new from_Context_int_List(a: NativeContext, b: Int, c: JavaList) in "Java" `{
+               #return new android.widget.ArrayAdapter(a, (int)b, c);
+       #`}
+
+       # Java constructor: android.widget.ArrayAdapter<T>
+       #new from_Context_int_int_List(a: NativeContext, b: Int, c: Int, d: Java_util_List) in "Java" `{
+               #return new android.widget.ArrayAdapter(a, (int)b, (int)c, d);
+       #`}
+
+       redef fun new_global_ref import sys, Sys.jni_env `{
+               Sys sys = Android_widget_ArrayAdapter_sys(self);
+               JNIEnv *env = Sys_jni_env(sys);
+               return (*env)->NewGlobalRef(env, self);
+       `}
+
+       redef fun pop_from_local_frame_with_env(jni_env) `{
+               return (*jni_env)->PopLocalFrame(jni_env, self);
+       `}
+end
+
+# Java class: android.widget.AbsListView
+extern class Android_widget_AbsListView in "Java" `{ android.widget.AbsListView `}
+       #super Android_widget_AdapterView
+       #super Android_text_TextWatcher
+       #super Android_view_ViewTreeObserver_OnGlobalLayoutListener
+       #super Android_widget_Filter_FilterListener
+       #super Android_view_ViewTreeObserver_OnTouchModeChangeListener
+       super NativeView
+
+       # Java implementation:  android.widget.AbsListView.setAdapter(android.widget.Adapter)
+       fun set_adapter(arg0: Android_widget_ListAdapter) in "Java" `{
+               self.setAdapter(arg0);
+       `}
+
+       # Java implementation: int android.widget.AbsListView.getCheckedItemCount()
+       fun get_checked_item_count: Int in "Java" `{
+               return self.getCheckedItemCount();
+       `}
+
+       # Java implementation: boolean android.widget.AbsListView.isItemChecked(int)
+       fun is_item_checked(arg0: Int): Bool in "Java" `{
+               return self.isItemChecked((int)arg0);
+       `}
+
+       # Java implementation: int android.widget.AbsListView.getCheckedItemPosition()
+       fun get_checked_item_position: Int in "Java" `{
+               return self.getCheckedItemPosition();
+       `}
+
+       # Java implementation: android.util.SparseBooleanArray android.widget.AbsListView.getCheckedItemPositions()
+       #fun get_checked_item_positions: Android_util_SparseBooleanArray in "Java" `{
+               #return self.getCheckedItemPositions();
+       #`}
+
+       # Java implementation: long[] android.widget.AbsListView.getCheckedItemIds()
+       fun get_checked_item_ids: JavaLongArray in "Java" `{
+               return self.getCheckedItemIds();
+       `}
+
+       # Java implementation:  android.widget.AbsListView.clearChoices()
+       fun clear_choices in "Java" `{
+               self.clearChoices();
+       `}
+
+       # Java implementation:  android.widget.AbsListView.setItemChecked(int, boolean)
+       fun set_item_checked(arg0: Int, arg1: Bool) in "Java" `{
+               self.setItemChecked((int)arg0, arg1);
+       `}
+
+       # Java implementation: int android.widget.AbsListView.getChoiceMode()
+       fun get_choice_mode: Int in "Java" `{
+               return self.getChoiceMode();
+       `}
+
+       # Java implementation:  android.widget.AbsListView.setChoiceMode(int)
+       fun set_choice_mode(arg0: Int) in "Java" `{
+               self.setChoiceMode((int)arg0);
+       `}
+
+       # Java implementation:  android.widget.AbsListView.setMultiChoiceModeListener(android.widget.AbsListView$MultiChoiceModeListener)
+       #fun set_multi_choice_mode_listener(arg0: Android_widget_AbsListView_MultiChoiceModeListener) in "Java" `{
+               #self.setMultiChoiceModeListener(arg0);
+       #`}
+
+       # Java implementation:  android.widget.AbsListView.setFastScrollEnabled(boolean)
+       fun set_fast_scroll_enabled(arg0: Bool) in "Java" `{
+               self.setFastScrollEnabled(arg0);
+       `}
+
+       # Java implementation:  android.widget.AbsListView.setFastScrollAlwaysVisible(boolean)
+       fun set_fast_scroll_always_visible(arg0: Bool) in "Java" `{
+               self.setFastScrollAlwaysVisible(arg0);
+       `}
+
+       # Java implementation: boolean android.widget.AbsListView.isFastScrollAlwaysVisible()
+       fun is_fast_scroll_always_visible: Bool in "Java" `{
+               return self.isFastScrollAlwaysVisible();
+       `}
+
+       # Java implementation: boolean android.widget.AbsListView.isFastScrollEnabled()
+       fun is_fast_scroll_enabled: Bool in "Java" `{
+               return self.isFastScrollEnabled();
+       `}
+
+       # Java implementation:  android.widget.AbsListView.setSmoothScrollbarEnabled(boolean)
+       fun set_smooth_scrollbar_enabled(arg0: Bool) in "Java" `{
+               self.setSmoothScrollbarEnabled(arg0);
+       `}
+
+       # Java implementation: boolean android.widget.AbsListView.isSmoothScrollbarEnabled()
+       fun is_smooth_scrollbar_enabled: Bool in "Java" `{
+               return self.isSmoothScrollbarEnabled();
+       `}
+
+       # Java implementation:  android.widget.AbsListView.setOnScrollListener(android.widget.AbsListView$OnScrollListener)
+       #fun set_on_scroll_listener(arg0: Android_widget_AbsListView_OnScrollListener) in "Java" `{
+               #self.setOnScrollListener(arg0);
+       #`}
+
+       # Java implementation: boolean android.widget.AbsListView.isScrollingCacheEnabled()
+       fun is_scrolling_cache_enabled: Bool in "Java" `{
+               return self.isScrollingCacheEnabled();
+       `}
+
+       # Java implementation:  android.widget.AbsListView.setScrollingCacheEnabled(boolean)
+       fun set_scrolling_cache_enabled(arg0: Bool) in "Java" `{
+               self.setScrollingCacheEnabled(arg0);
+       `}
+
+       # Java implementation:  android.widget.AbsListView.setTextFilterEnabled(boolean)
+       fun set_text_filter_enabled(arg0: Bool) in "Java" `{
+               self.setTextFilterEnabled(arg0);
+       `}
+
+       # Java implementation: boolean android.widget.AbsListView.isTextFilterEnabled()
+       fun is_text_filter_enabled: Bool in "Java" `{
+               return self.isTextFilterEnabled();
+       `}
+
+       # Java implementation: boolean android.widget.AbsListView.isStackFromBottom()
+       fun is_stack_from_bottom: Bool in "Java" `{
+               return self.isStackFromBottom();
+       `}
+
+       # Java implementation:  android.widget.AbsListView.setStackFromBottom(boolean)
+       fun set_stack_from_bottom(arg0: Bool) in "Java" `{
+               self.setStackFromBottom(arg0);
+       `}
+
+       # Java implementation: android.os.Parcelable android.widget.AbsListView.onSaveInstanceState()
+       #fun on_save_instance_state: Android_os_Parcelable in "Java" `{
+               #return self.onSaveInstanceState();
+       #`}
+
+       # Java implementation:  android.widget.AbsListView.onRestoreInstanceState(android.os.Parcelable)
+       #fun on_restore_instance_state(arg0: Android_os_Parcelable) in "Java" `{
+               #self.onRestoreInstanceState(arg0);
+       #`}
+
+       # Java implementation:  android.widget.AbsListView.setFilterText(java.lang.String)
+       #fun set_filter_text(arg0: Java_lang_String) in "Java" `{
+               #self.setFilterText(arg0);
+       #`}
+
+       # Java implementation: java.lang.CharSequence android.widget.AbsListView.getTextFilter()
+       #fun get_text_filter: JavaCharSequence in "Java" `{
+               #return self.getTextFilter();
+       #`}
+
+       # Java implementation: int android.widget.AbsListView.getListPaddingTop()
+       fun get_list_padding_top: Int in "Java" `{
+               return self.getListPaddingTop();
+       `}
+
+       # Java implementation: int android.widget.AbsListView.getListPaddingBottom()
+       fun get_list_padding_bottom: Int in "Java" `{
+               return self.getListPaddingBottom();
+       `}
+
+       # Java implementation: int android.widget.AbsListView.getListPaddingLeft()
+       fun get_list_padding_left: Int in "Java" `{
+               return self.getListPaddingLeft();
+       `}
+
+       # Java implementation: int android.widget.AbsListView.getListPaddingRight()
+       fun get_list_padding_right: Int in "Java" `{
+               return self.getListPaddingRight();
+       `}
+
+       # Java implementation:  android.widget.AbsListView.setDrawSelectorOnTop(boolean)
+       fun set_draw_selector_on_top(arg0: Bool) in "Java" `{
+               self.setDrawSelectorOnTop(arg0);
+       `}
+
+       # Java implementation:  android.widget.AbsListView.setSelector(int)
+       fun set_selector_int(arg0: Int) in "Java" `{
+               self.setSelector((int)arg0);
+       `}
+
+       # Java implementation:  android.widget.AbsListView.setSelector(android.graphics.drawable.Drawable)
+       #fun set_selector_Drawable(arg0: Android_graphics_drawable_Drawable) in "Java" `{
+               #self.setSelector(arg0);
+       #`}
+
+       # Java implementation: android.graphics.drawable.Drawable android.widget.AbsListView.getSelector()
+       #fun get_selector: Android_graphics_drawable_Drawable in "Java" `{
+               #return self.getSelector();
+       #`}
+
+       # Java implementation:  android.widget.AbsListView.setScrollIndicators(android.view.View, android.view.View)
+       fun set_scroll_indicators(arg0: NativeView, arg1: NativeView) in "Java" `{
+               self.setScrollIndicators(arg0, arg1);
+       `}
+
+       # Java implementation: boolean android.widget.AbsListView.verifyDrawable(android.graphics.drawable.Drawable)
+       #fun verify_drawable(arg0: Android_graphics_drawable_Drawable): Bool in "Java" `{
+               #return self.verifyDrawable(arg0);
+       #`}
+
+       # Java implementation: int android.widget.AbsListView.pointToPosition(int, int)
+       fun point_to_position(arg0: Int, arg1: Int): Int in "Java" `{
+               return self.pointToPosition((int)arg0, (int)arg1);
+       `}
+
+       # Java implementation: long android.widget.AbsListView.pointToRowId(int, int)
+       fun point_to_row_id(arg0: Int, arg1: Int): Int in "Java" `{
+               return self.pointToRowId((int)arg0, (int)arg1);
+       `}
+
+       # Java implementation:  android.widget.AbsListView.setFriction(float)
+       fun set_friction(arg0: Float) in "Java" `{
+               self.setFriction((float)arg0);
+       `}
+
+       # Java implementation:  android.widget.AbsListView.setVelocityScale(float)
+       fun set_velocity_scale(arg0: Float) in "Java" `{
+               self.setVelocityScale((float)arg0);
+       `}
+
+       # Java implementation:  android.widget.AbsListView.smoothScrollToPosition(int)
+       fun smooth_scroll_to_position_int(arg0: Int) in "Java" `{
+               self.smoothScrollToPosition((int)arg0);
+       `}
+
+       # Java implementation:  android.widget.AbsListView.smoothScrollToPosition(int, int)
+       fun smooth_scroll_to_position_int_int(arg0: Int, arg1: Int) in "Java" `{
+               self.smoothScrollToPosition((int)arg0, (int)arg1);
+       `}
+
+       # Java implementation:  android.widget.AbsListView.smoothScrollToPositionFromTop(int, int, int)
+       fun smooth_scroll_to_position_from_top_int_int_int(arg0: Int, arg1: Int, arg2: Int) in "Java" `{
+               self.smoothScrollToPositionFromTop((int)arg0, (int)arg1, (int)arg2);
+       `}
+
+       # Java implementation:  android.widget.AbsListView.smoothScrollToPositionFromTop(int, int)
+       fun smooth_scroll_to_position_from_top_int_int(arg0: Int, arg1: Int) in "Java" `{
+               self.smoothScrollToPositionFromTop((int)arg0, (int)arg1);
+       `}
+
+       # Java implementation:  android.widget.AbsListView.smoothScrollBy(int, int)
+       fun smooth_scroll_by(arg0: Int, arg1: Int) in "Java" `{
+               self.smoothScrollBy((int)arg0, (int)arg1);
+       `}
+
+       # Java implementation:  android.widget.AbsListView.invalidateViews()
+       fun invalidate_views in "Java" `{
+               self.invalidateViews();
+       `}
+
+       # Java implementation:  android.widget.AbsListView.clearTextFilter()
+       fun clear_text_filter in "Java" `{
+               self.clearTextFilter();
+       `}
+
+       # Java implementation: boolean android.widget.AbsListView.hasTextFilter()
+       fun has_text_filter: Bool in "Java" `{
+               return self.hasTextFilter();
+       `}
+
+       # Java implementation:  android.widget.AbsListView.setTranscriptMode(int)
+       fun set_transcript_mode(arg0: Int) in "Java" `{
+               self.setTranscriptMode((int)arg0);
+       `}
+
+       # Java implementation: int android.widget.AbsListView.getTranscriptMode()
+       fun get_transcript_mode: Int in "Java" `{
+               return self.getTranscriptMode();
+       `}
+
+       # Java implementation:  android.widget.AbsListView.setCacheColorHint(int)
+       fun set_cache_color_hint(arg0: Int) in "Java" `{
+               self.setCacheColorHint((int)arg0);
+       `}
+
+       # Java implementation: int android.widget.AbsListView.getCacheColorHint()
+       fun get_cache_color_hint: Int in "Java" `{
+               return self.getCacheColorHint();
+       `}
+
+       # Java implementation:  android.widget.AbsListView.reclaimViews(java.util.List<android.view.View>)
+       #fun reclaim_views(arg0: Java_util_List) in "Java" `{
+               #self.reclaimViews(arg0);
+       #`}
+
+       # Java implementation:  android.widget.AbsListView.setRemoteViewsAdapter(android.content.Intent)
+       #fun set_remote_views_adapter(arg0: Android_content_Intent) in "Java" `{
+               #self.setRemoteViewsAdapter(arg0);
+       #`}
+
+       # Java implementation:  android.widget.AbsListView.deferNotifyDataSetChanged()
+       fun defer_notify_data_set_changed in "Java" `{
+               self.deferNotifyDataSetChanged();
+       `}
+
+       # Java implementation: boolean android.widget.AbsListView.onRemoteAdapterConnected()
+       fun on_remote_adapter_connected: Bool in "Java" `{
+               return self.onRemoteAdapterConnected();
+       `}
+
+       # Java implementation:  android.widget.AbsListView.onRemoteAdapterDisconnected()
+       fun on_remote_adapter_disconnected in "Java" `{
+               self.onRemoteAdapterDisconnected();
+       `}
+
+       # Java implementation:  android.widget.AbsListView.setRecyclerListener(android.widget.AbsListView$RecyclerListener)
+       #fun set_recycler_listener(arg0: Android_widget_AbsListView_RecyclerListener) in "Java" `{
+               #self.setRecyclerListener(arg0);
+       #`}
+
+       # Java constructor: android.widget.AbsListView
+       #new (a: NativeContext) in "Java" `{
+               #return new android.widget.AbsListView(a);
+       #`}
+
+       # Java constructor: android.widget.AbsListView
+       #new from_Context_AttributeSet(a: NativeView, b: Android_util_AttributeSet) in "Java" `{
+               #return new android.widget.AbsListView(a, b);
+       #`}
+
+       # Java constructor: android.widget.AbsListView
+       #new from_Context_AttributeSet_int(a: NativeView, b: Android_util_AttributeSet, c: Int) in "Java" `{
+               #return new android.widget.AbsListView(a, b, (int)c);
+       #`}
+
+       redef fun new_global_ref import sys, Sys.jni_env `{
+               Sys sys = Android_widget_AbsListView_sys(self);
+               JNIEnv *env = Sys_jni_env(sys);
+               return (*env)->NewGlobalRef(env, self);
+       `}
+
+       redef fun pop_from_local_frame_with_env(jni_env) `{
+               return (*jni_env)->PopLocalFrame(jni_env, self);
+       `}
+end
+
+# Java class: android.widget.ListView
+extern class Android_widget_ListView in "Java" `{ android.widget.ListView `}
+       super Android_widget_AbsListView
+
+       # Java implementation: int android.widget.ListView.getMaxScrollAmount()
+       fun get_max_scroll_amount: Int in "Java" `{
+               return self.getMaxScrollAmount();
+       `}
+
+       # Java implementation:  android.widget.ListView.addHeaderView(android.view.View, java.lang.Object, boolean)
+       fun add_header_view_View_Object_boolean(arg0: NativeView, arg1: JavaObject, arg2: Bool) in "Java" `{
+               self.addHeaderView(arg0, arg1, arg2);
+       `}
+
+       # Java implementation:  android.widget.ListView.addHeaderView(android.view.View)
+       fun add_header_view_View(arg0: NativeView) in "Java" `{
+               self.addHeaderView(arg0);
+       `}
+
+       # Java implementation: int android.widget.ListView.getHeaderViewsCount()
+       fun get_header_views_count: Int in "Java" `{
+               return self.getHeaderViewsCount();
+       `}
+
+       # Java implementation: boolean android.widget.ListView.removeHeaderView(android.view.View)
+       fun remove_header_view(arg0: NativeView): Bool in "Java" `{
+               return self.removeHeaderView(arg0);
+       `}
+
+       # Java implementation:  android.widget.ListView.addFooterView(android.view.View, java.lang.Object, boolean)
+       fun add_footer_view_View_Object_boolean(arg0: NativeView, arg1: JavaObject, arg2: Bool) in "Java" `{
+               self.addFooterView(arg0, arg1, arg2);
+       `}
+
+       # Java implementation:  android.widget.ListView.addFooterView(android.view.View)
+       fun add_footer_view_View(arg0: NativeView) in "Java" `{
+               self.addFooterView(arg0);
+       `}
+
+       # Java implementation: int android.widget.ListView.getFooterViewsCount()
+       fun get_footer_views_count: Int in "Java" `{
+               return self.getFooterViewsCount();
+       `}
+
+       # Java implementation: boolean android.widget.ListView.removeFooterView(android.view.View)
+       fun remove_footer_view(arg0: NativeView): Bool in "Java" `{
+               return self.removeFooterView(arg0);
+       `}
+
+       # Java implementation:  android.widget.ListView.smoothScrollByOffset(int)
+       fun smooth_scroll_by_offset(arg0: Int) in "Java" `{
+               self.smoothScrollByOffset((int)arg0);
+       `}
+
+       # Java implementation:  android.widget.ListView.setSelectionFromTop(int, int)
+       fun set_selection_from_top(arg0: Int, arg1: Int) in "Java" `{
+               self.setSelectionFromTop((int)arg0, (int)arg1);
+       `}
+
+       # Java implementation:  android.widget.ListView.setSelectionAfterHeaderView()
+       fun set_selection_after_header_view in "Java" `{
+               self.setSelectionAfterHeaderView();
+       `}
+
+       # Java implementation:  android.widget.ListView.setItemsCanFocus(boolean)
+       fun set_items_can_focus(arg0: Bool) in "Java" `{
+               self.setItemsCanFocus(arg0);
+       `}
+
+       # Java implementation: boolean android.widget.ListView.getItemsCanFocus()
+       fun get_items_can_focus: Bool in "Java" `{
+               return self.getItemsCanFocus();
+       `}
+
+       # Java implementation: android.graphics.drawable.Drawable android.widget.ListView.getDivider()
+       #fun get_divider: Android_graphics_drawable_Drawable in "Java" `{
+               #return self.getDivider();
+       #`}
+
+       # Java implementation:  android.widget.ListView.setDivider(android.graphics.drawable.Drawable)
+       #fun set_divider(arg0: Android_graphics_drawable_Drawable) in "Java" `{
+               #self.setDivider(arg0);
+       #`}
+
+       # Java implementation: int android.widget.ListView.getDividerHeight()
+       fun get_divider_height: Int in "Java" `{
+               return self.getDividerHeight();
+       `}
+
+       # Java implementation:  android.widget.ListView.setDividerHeight(int)
+       fun set_divider_height(arg0: Int) in "Java" `{
+               self.setDividerHeight((int)arg0);
+       `}
+
+       # Java implementation:  android.widget.ListView.setHeaderDividersEnabled(boolean)
+       fun set_header_dividers_enabled(arg0: Bool) in "Java" `{
+               self.setHeaderDividersEnabled(arg0);
+       `}
+
+       # Java implementation:  android.widget.ListView.setFooterDividersEnabled(boolean)
+       fun set_footer_dividers_enabled(arg0: Bool) in "Java" `{
+               self.setFooterDividersEnabled(arg0);
+       `}
+
+       # Java implementation:  android.widget.ListView.setOverscrollHeader(android.graphics.drawable.Drawable)
+       #fun set_overscroll_header(arg0: Android_graphics_drawable_Drawable) in "Java" `{
+               #self.setOverscrollHeader(arg0);
+       #`}
+
+       # Java implementation: android.graphics.drawable.Drawable android.widget.ListView.getOverscrollHeader()
+       #fun get_overscroll_header: Android_graphics_drawable_Drawable in "Java" `{
+               #return self.getOverscrollHeader();
+       #`}
+
+       # Java implementation:  android.widget.ListView.setOverscrollFooter(android.graphics.drawable.Drawable)
+       #fun set_overscroll_footer(arg0: Android_graphics_drawable_Drawable) in "Java" `{
+               #self.setOverscrollFooter(arg0);
+       #`}
+
+       # Java implementation: android.graphics.drawable.Drawable android.widget.ListView.getOverscrollFooter()
+       #fun get_overscroll_footer: Android_graphics_drawable_Drawable in "Java" `{
+               #return self.getOverscrollFooter();
+       #`}
+
+       # Java implementation: long[] android.widget.ListView.getCheckItemIds()
+       fun get_check_item_ids: JavaLongArray in "Java" `{
+               return self.getCheckItemIds();
+       `}
+
+       # Java constructor: android.widget.ListView
+       new(a: NativeContext) in "Java" `{
+               return new android.widget.ListView(a);
+       `}
+
+       # Java constructor: android.widget.ListView
+       #new from_Context_AttributeSet(a: NativeContext, b: Android_util_AttributeSet) in "Java" `{
+               #return new android.widget.ListView(a, b);
+       #`}
+
+       # Java constructor: android.widget.ListView
+       #new from_Context_AttributeSet_int(a: NativeContext, b: Android_util_AttributeSet, c: Int) in "Java" `{
+               #return new android.widget.ListView(a, b, (int)c);
+       #`}
+
+       redef fun new_global_ref import sys, Sys.jni_env `{
+               Sys sys = Android_widget_ListView_sys(self);
+               JNIEnv *env = Sys_jni_env(sys);
+               return (*env)->NewGlobalRef(env, self);
+       `}
+
+       redef fun pop_from_local_frame_with_env(jni_env) `{
+               return (*jni_env)->PopLocalFrame(jni_env, self);
+       `}
+end
+
+# Java getter: android.R$layout.simple_list_item_1
+fun android_r_layout_simple_list_item_1: Int in "Java" `{
+       return android.R.layout.simple_list_item_1;
+`}
+
+# Java getter: android.R$layout.simple_list_item_2
+fun android_r_layout_simple_list_item_2: Int in "Java" `{
+       return android.R.layout.simple_list_item_2;
+`}
+
+# Java getter: android.R$layout.simple_list_item_activated_1
+fun android_r_layout_simple_list_item_activated_1: Int in "Java" `{
+       return android.R.layout.simple_list_item_activated_1;
+`}
+
+# Java getter: android.R$layout.simple_list_item_activated_2
+fun android_r_layout_simple_list_item_activated_2: Int in "Java" `{
+       return android.R.layout.simple_list_item_activated_2;
+`}
+
+# Java getter: android.R$layout.simple_list_item_checked
+fun android_r_layout_simple_list_item_checked: Int in "Java" `{
+       return android.R.layout.simple_list_item_checked;
+`}
+
+# Java getter: android.R$layout.simple_list_item_multiple_choice
+fun android_r_layout_simple_list_item_multiple_choice: Int in "Java" `{
+       return android.R.layout.simple_list_item_multiple_choice;
+`}
+
+# Java getter: android.R$layout.simple_list_item_single_choice
+fun android_r_layout_simple_list_item_single_choice: Int in "Java" `{
+       return android.R.layout.simple_list_item_single_choice;
+`}
+
+# Java getter: android.R$layout.simple_selectable_list_item
+fun android_r_layout_simple_selectable_list_item: Int in "Java" `{
+       return android.R.layout.simple_selectable_list_item;
+`}
+
+# Java getter: android.R$style.TextAppearance_Large
+fun android_r_style_text_appearance_large: Int in "Java" `{
+       return android.R.style.TextAppearance_Large;
+`}
+
+# Java getter: android.R$style.TextAppearance_Medium
+fun android_r_style_text_appearance_medium: Int in "Java" `{
+       return android.R.style.TextAppearance_Medium;
+`}
+
+# Java getter: android.R$style.TextAppearance_Small
+fun android_r_style_text_appearance_small: Int in "Java" `{
+       return android.R.style.TextAppearance_Small;
+`}
index 75e1a12..656e209 100644 (file)
@@ -96,6 +96,50 @@ redef class VerticalLayout
        end
 end
 
+redef class ListLayout
+       redef type NATIVE: Android_widget_ListView
+
+       redef var native do
+               var layout = new Android_widget_ListView(app.native_activity)
+               layout = layout.new_global_ref
+               return layout
+       end
+
+       private var adapter: Android_widget_ArrayAdapter do
+               var adapter = new Android_widget_ArrayAdapter(app.native_activity,
+                       android_r_layout_simple_list_item_1, self)
+               native.set_adapter adapter
+               return adapter
+       end
+
+       redef fun add(item)
+       do
+               super
+               if item isa View then adapter.add item.native
+       end
+
+       private fun create_view(position: Int): NativeView
+       do
+               var ctrl = items[position]
+               assert ctrl isa View
+               return ctrl.native
+       end
+end
+
+redef class Android_widget_ArrayAdapter
+       private new (context: NativeContext, res: Int, sender: ListLayout)
+       import ListLayout.create_view in "Java" `{
+               final int final_sender_object = sender;
+
+               return new android.widget.ArrayAdapter(context, (int)res) {
+                               @Override
+                               public android.view.View getView(int position, android.view.View convertView, android.view.ViewGroup parent) {
+                                       return ListLayout_create_view(final_sender_object, position);
+                               }
+                       };
+       `}
+end
+
 redef class TextView
        redef type NATIVE: NativeTextView
 
@@ -117,6 +161,8 @@ end
 redef class Label
        redef type NATIVE: NativeTextView
        redef var native do return (new NativeTextView(app.native_activity)).new_global_ref
+
+       init do native.set_text_appearance(app.native_activity, android_r_style_text_appearance_medium)
 end
 
 redef class TextInput
index 01ef5ca..239f9c9 100644 (file)
@@ -106,7 +106,7 @@ end
 class CompositeControl
        super Control
 
-       private var items = new HashSet[Control]
+       protected var items = new Array[Control]
 
        # Add `item` as a child of `self`
        protected fun add(item: Control) do items.add item
@@ -197,3 +197,9 @@ end
 class VerticalLayout
        super Layout
 end
+
+# Scrollable list of views in a simple list
+class ListLayout
+       super View
+       super CompositeControl
+end
index c8200d4..21dc181 100644 (file)
@@ -16,7 +16,7 @@
 module ui
 
 import app::ui
-import gtk
+import gtk::v3_10
 
 import data_store
 
@@ -122,6 +122,13 @@ redef class VerticalLayout
        end
 end
 
+redef class ListLayout
+       redef type NATIVE: GtkListBox
+       redef var native = new GtkListBox
+
+       init do native.selection_mode = new GtkSelectionMode.none
+end
+
 redef class Button
        redef type NATIVE: GtkButton
        redef var native = new GtkButton
index 89b9e0b..e0a4a10 100644 (file)
@@ -112,7 +112,7 @@ class OpportunityMasterHeader
        end
 end
 
-redef class Tnitter
+redef class TnitterWeb
        redef var header: String = (new MasterHeader("tnitter", true)).to_s
 end
 
@@ -162,10 +162,14 @@ var user_group = new UserGroup("nitcorn", "nitcorn")
 if sys.uid == 0 then user_group.drop_privileges
 
 # Tnitter is available at `tnitter.xymus.net` and `xymus.net/tnitter/`
-var tnitter = new Tnitter
+var tnitter = new TnitterWeb
 default_vh.routes.add new Route("/tnitter/", tnitter)
+
+tnitter_vh.routes.add new Route("/rest/", new TnitterREST)
+tnitter_vh.routes.add new Route("/push/", new TnitterPush)
 tnitter_vh.routes.add new Route(null, tnitter)
 
+
 # Pep/8 Analysis is only a file server. It is available at `pep8.xymus.net`
 # and through the global/default file server at `xymus.net/pep8/`
 #
index 5558d66..10caaef 100644 (file)
@@ -107,6 +107,7 @@ abstract class Deserializer
        # All refinement should look for a precise `class_name` and call super
        # on unsupported classes.
        protected fun deserialize_class(class_name: String): nullable Object do
+               if class_name == "Error" then return new Error.from_deserializer(self)
                return deserialize_class_intern(class_name)
        end