From: Alexis Laferrière Date: Tue, 10 Nov 2015 13:41:54 +0000 (-0500) Subject: contrib/tnitter: intro simple app X-Git-Tag: v0.8~88^2~7 X-Git-Url: http://nitlanguage.org contrib/tnitter: intro simple app Signed-off-by: Alexis Laferrière --- diff --git a/contrib/tnitter/Makefile b/contrib/tnitter/Makefile index 5bb713b..ded78ca 100644 --- a/contrib/tnitter/Makefile +++ b/contrib/tnitter/Makefile @@ -1,3 +1,18 @@ -all: +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 + +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) + 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) + mkdir -p bin/ + ../../bin/nitc -o bin/tnitter.apk src/tnitter_app_android.nit --release -D tnitter_server_uri=http://tnitter.xymus.net diff --git a/contrib/tnitter/src/tnitter_app.nit b/contrib/tnitter/src/tnitter_app.nit new file mode 100644 index 0000000..dcbfffe --- /dev/null +++ b/contrib/tnitter/src/tnitter_app.nit @@ -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 index 0000000..74eb1b6 --- /dev/null +++ b/contrib/tnitter/src/tnitter_app_android.nit @@ -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