lib & contrib: update imports
[nit.git] / contrib / tnitter / src / tnitter_app.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Tnitter minimal portable app listing the latest Tnits
16 #
17 # This app use push notification to be updated in the second a new Tnit is posted.
18 # So it begins by requesting a full list and the successive push request for updates.
19 # If an error occurs, the full list is requested again after a short delay.
20 #
21 # This approach may still miss a few updates it they happen too close to one another.
22 # To solve this, we could send an id for the latest known Tnit to the server.
23 # Which could recognize if a client is not up to date.
24 module tnitter_app is
25 app_name "Tnitter"
26 app_version(0, 1, git_revision)
27 app_namespace "net.xymus.tnitter"
28 end
29
30 import app::ui
31 import app::http_request
32 import app::data_store
33 import android::aware
34 import json
35
36 import model
37
38 # Delay in seconds before the next request after an error
39 fun request_delay_on_error: Float do return 60.0
40
41 redef class App
42 redef fun on_create
43 do
44 # Create the main window
45 push_window new TnitterWindow
46 super
47 end
48 end
49
50 # Main window
51 class TnitterWindow
52 super Window
53
54 private var layout = new VerticalLayout(parent=self)
55 private var list_posts = new ListLayout(parent=layout)
56 private var lbl_init = new Label(parent=list_posts, text="Awaiting connection to server")
57
58 # Request an initial full update
59 init do (new ListPostRequest(self, "rest/list?count=16", false)).start
60
61 # Request a full update after a delay
62 fun request_full_list_on_error
63 do
64 (new ListPostRequest(self, "rest/list?count=16", true)).start
65 end
66
67 # Open a push notification connection and thread
68 fun request_push_notification
69 do
70 (new ListPostRequest(self, "push/", false)).start
71 end
72
73 # Update the screen to show the new `posts`
74 fun apply_update(posts: Array[Post])
75 do
76 list_posts.clear
77 for post in posts do
78 var line = new VerticalLayout(parent=list_posts)
79 var author = new LabelAuthor(parent=line, text="@"+post.user)
80 var text = new Label(parent=line, text=post.text)
81 end
82 end
83 end
84
85 # Label to display the author's name
86 #
87 # By default, this view is identical a `Label`,
88 # but if can be refined per platforms.
89 class LabelAuthor super Label end
90
91 # ---
92 # Async RESTful actions
93
94 # URI of the remote RESTful server
95 fun tnitter_server_uri: String do return "http://localhost:8080"
96
97 # `AsyncHttpRequest` with services to act on the windows of the app
98 abstract class AsyncTnitterRequest
99 super AsyncHttpRequest
100
101 private var window: TnitterWindow
102
103 redef fun uri_root do return tnitter_server_uri
104
105 redef var uri_tail
106
107 # Should this request be delayed by `request_delay_on_error` seconds?
108 fun after_error(value: Bool) is autoinit do if value then delay = request_delay_on_error
109 end
110
111 # Async request to list latest posts, either immediately or by push notification
112 #
113 # Implementation note:
114 # This class could as well be merged with `AsyncTnitterRequest` or have two versions,
115 # one for the immediate update and one for the push notification.
116 # We chose this structure for simplicity of the example,
117 # and as more services may be added in the future.
118 # If these future services expect data of a different format,
119 # they will need a different `on_load` but could still use `AsyncTnitterRequest`.
120 class ListPostRequest
121 super AsyncTnitterRequest
122
123 redef fun on_load(posts, status)
124 do
125 # Deal with server-side errors
126 if posts isa Error then
127 print_error "Server Error: '{posts.message}' from '{uri}'"
128 return
129 end
130
131 # Type check
132 if not posts isa Array[Post] then
133 print_error "Error: Got '{posts or else "null"}'"
134 return
135 end
136
137 # Update UI and prepare for the next update
138 window.apply_update posts
139 window.request_push_notification
140 end
141
142 redef fun on_fail(error)
143 do
144 print "Warning: Request {uri} failed with {error}"
145 window.request_full_list_on_error
146 end
147 end
148
149 # ---
150 # Services
151
152 redef class Deserializer
153 redef fun deserialize_class(name)
154 do
155 # This is usually generated using `nitserial`,
156 # but for a single generic class it is easier to implement manually
157
158 if name == "Array[Post]" then return new Array[Post].from_deserializer(self)
159 return super
160 end
161 end