# This file is part of NIT (http://www.nitlanguage.org).
#
-# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
-#
# 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
# limitations under the License.
# Views and services to use the Android native user interface
-module ui
+module ui is
+ # `adjustPan` allows to use EditText in a ListLayout
+ android_manifest_activity """android:windowSoftInputMode="adjustPan""""
+end
+
+# Implementation note:
+#
+# We cannot rely on `Activity::on_restore_instance_state` to implement
+# `on_restore_state` is it only invoked if there is a bundled state,
+# and we don't use the Android bundled state.
import native_ui
+import log
+import nit_activity
+
+import app::ui
+private import data_store
+import assets
+
+redef class Control
+ # The Android element used to implement `self`
+ fun native: NATIVE is abstract
-# An event from the `app.nit` framework
-interface AppEvent
- # Reaction to this event
- fun react do end
+ # Type of `native`
+ type NATIVE: JavaObject
end
-# A control click event
-class ClickEvent
- super AppEvent
+redef class NativeActivity
- # Sender of this event
- var sender: Button
+ private fun remove_title_bar in "Java" `{
+ self.requestWindowFeature(android.view.Window.FEATURE_NO_TITLE);
+ `}
- redef fun react do sender.click self
-end
+ # Insert a single layout as the root of the activity window
+ private fun insert_root_layout(root_layout_id: Int)
+ in "Java" `{
+ android.widget.FrameLayout layout = new android.widget.FrameLayout(self);
+ layout.setId((int)root_layout_id);
+ self.setContentView(layout);
+ `}
-# Receiver of events not handled directly by the sender
-interface EventCatcher
- fun catch_event(event: AppEvent) do end
+ # Replace the currently visible fragment, if any, with `native_fragment`
+ private fun show_fragment(root_layout_id: Int, native_fragment: Android_app_Fragment)
+ in "Java" `{
+ android.app.FragmentTransaction transaction = self.getFragmentManager().beginTransaction();
+ transaction.replace((int)root_layout_id, native_fragment);
+ transaction.commit();
+ `}
end
redef class App
- super EventCatcher
-end
+ redef fun on_create
+ do
+ app.native_activity.remove_title_bar
+ native_activity.insert_root_layout(root_layout_id)
+ super
+ end
+
+ # Identifier of the container holding the fragments
+ private var root_layout_id = 0xFFFF
-# An `Object` that raises events
-abstract class Eventful
- var event_catcher: EventCatcher = app is lazy, writable
+ redef fun window=(window)
+ do
+ native_activity.show_fragment(root_layout_id, window.native)
+ super
+ end
end
-#
-## Nity classes and services
-#
+redef class Activity
+ redef fun on_back_pressed
+ do
+ var window = app.window
+ if window.enable_back_button then
+ window.on_back_button
+ return true
+ end
-# An Android control with text
-abstract class TextView
- super Finalizable
- super Eventful
+ return false
+ end
+end
- # Native Java variant to this Nity class
- type NATIVE: NativeTextView
+# On Android, a window is implemented with the fragment `native`
+redef class Window
+ redef var native = (new Android_app_Fragment(self)).new_global_ref
- # The native Java object encapsulated by `self`
- var native: NATIVE is noinit
+ redef type NATIVE: Android_app_Fragment
- # Get the text of this view
- fun text: String
+ # Root high-level view of this window
+ var view: nullable View = null
+
+ redef fun add(item)
do
- var jstr = native.text
- var str = jstr.to_s
- jstr.delete_local_ref
- return str
+ if item isa View then view = item
+ super
end
- # Set the text of this view
- fun text=(value: Text)
+ private fun on_create_fragment: NativeView
do
- var jstr = value.to_s.to_java_string
- native.text = jstr
- jstr.delete_local_ref
+ on_create
+
+ var view = view
+ assert view != null else print_error "{class_name} needs a `view` after `Window::on_create` returns"
+ return view.native
end
+end
- # Get whether this view is enabled or not
- fun enabled: Bool do return native.enabled
+redef class View
+ redef type NATIVE: NativeView
- # Set if this view is enabled
- fun enabled=(val: Bool) do native.enabled = val
+ redef fun enabled=(enabled) do native.enabled = enabled or else true
+ redef fun enabled do return native.enabled
+end
- # Set the size of the text in this view at `dpi`
- fun text_size=(dpi: Numeric) do native.text_size = dpi.to_f
+redef class Layout
+ redef type NATIVE: NativeViewGroup
- private var finalized = false
- redef fun finalize
+ redef fun add(item)
do
- if not finalized then
- native.delete_global_ref
- finalized = true
- end
+ super
+
+ assert item isa View
+
+ # FIXME abstract the use either homogeneous or weight to balance views size in a layout
+ native.add_view_with_weight(item.native, 1.0)
+ end
+
+ redef fun remove(item)
+ do
+ super
+ if item isa View then native.remove_view item.native
+ end
+end
+
+redef class HorizontalLayout
+ redef var native do
+ var layout = new NativeLinearLayout(app.native_activity)
+ layout = layout.new_global_ref
+ layout.set_horizontal
+ return layout
end
end
-# An Android button
-class Button
- super TextView
+redef class VerticalLayout
+ redef var native do
+ var layout = new NativeLinearLayout(app.native_activity)
+ layout = layout.new_global_ref
+ layout.set_vertical
+ return layout
+ end
+end
- redef type NATIVE: NativeButton
+redef class ListLayout
+ redef type NATIVE: Android_widget_ListView
- init
+ 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.new_global_ref
+ end
+
+ redef fun add(item)
do
- var native = new NativeButton(app.native_activity, self)
- self.native = native.new_global_ref
+ super
+ if item isa View then adapter.add item.native
end
- # Click event
- #
- # By default, this method calls `app.catch_event`. It can be specialized
- # with custom behavior or the receiver of `catch_event` can be changed
- # with `event_catcher=`.
- fun click(event: AppEvent) do event_catcher.catch_event(event)
+ 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
+
+ redef fun text do return native.text.to_s
+ redef fun text=(value) do
+ if value == null then value = ""
+ native.text = value.to_java_string
+ end
+
+ redef fun size=(size) do set_size_native(app.native_activity, native, size or else 1.0)
+
+ private fun set_size_native(context: NativeContext, view: NativeTextView, size: Float)
+ in "Java" `{
+ int s;
+ if (size == 1.0d)
+ s = android.R.style.TextAppearance_Medium;
+ else if (size < 1.0d)
+ s = android.R.style.TextAppearance_Small;
+ else // if (size > 1.0d)
+ s = android.R.style.TextAppearance_Large;
- private fun click_from_native do click(new ClickEvent(self))
+ view.setTextAppearance(context, s);
+ `}
+
+ redef fun align=(align) do set_align_native(native, align or else 0.0)
+
+ private fun set_align_native(view: NativeTextView, align: Float)
+ in "Java" `{
+ int g;
+ if (align == 0.5d)
+ g = android.view.Gravity.CENTER_HORIZONTAL;
+ else if (align < 0.5d)
+ g = android.view.Gravity.LEFT;
+ else // if (align > 0.5d)
+ g = android.view.Gravity.RIGHT;
+
+ view.setGravity(g);
+ `}
end
-# An Android editable text field
-class EditText
- super TextView
+redef class Label
+ redef type NATIVE: NativeTextView
+ redef var native do return (new NativeTextView(app.native_activity)).new_global_ref
+end
+
+redef class CheckBox
+ redef type NATIVE: Android_widget_CompoundButton
+ redef var native do return (new Android_widget_CheckBox(app.native_activity)).new_global_ref
+ init do set_callback_on_toggle(native)
+
+ redef fun is_checked do return native.is_checked
+ redef fun is_checked=(value) do native.set_checked(value)
+
+ private fun on_toggle do notify_observers new ToggleEvent(self)
+
+ private fun set_callback_on_toggle(view: NATIVE)
+ import on_toggle in "Java" `{
+ final int final_sender_object = self;
+ CheckBox_incr_ref(final_sender_object);
+
+ view.setOnCheckedChangeListener(
+ new android.widget.CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(android.widget.CompoundButton buttonView, boolean isChecked) {
+ CheckBox_on_toggle(final_sender_object);
+ }
+ });
+ `}
+end
+redef class TextInput
redef type NATIVE: NativeEditText
+ redef var native = (new NativeEditText(app.native_activity)).new_global_ref
- init
+ redef fun is_password=(value)
do
- var native = new NativeEditText(app.activities.first.native)
- self.native = native.new_global_ref
+ native.is_password = value or else false
+ super
end
end
+redef class NativeEditText
+
+ # Configure this view to hide passwords
+ fun is_password=(value: Bool) in "Java" `{
+ if (value) {
+ self.setInputType(android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ self.setTransformationMethod(android.text.method.PasswordTransformationMethod.getInstance());
+ } else {
+ self.setInputType(android.text.InputType.TYPE_CLASS_TEXT);
+ self.setTransformationMethod(null);
+ }
+ `}
+end
+
+redef class Button
+ super Finalizable
+
+ redef type NATIVE: NativeButton
+ redef var native = (new NativeButton(app.native_activity, self)).new_global_ref
+
+ private fun on_click do notify_observers new ButtonPressEvent(self)
+
+ redef fun finalize do native.delete_global_ref
+end
+
redef class NativeButton
- new (context: NativeActivity, sender_object: Object)
- import Button.click_from_native in "Java" `{
+ private new (context: NativeActivity, sender_object: Button)
+ import Button.on_click in "Java" `{
final int final_sender_object = sender_object;
+ Button_incr_ref(final_sender_object);
return new android.widget.Button(context){
@Override
public boolean onTouchEvent(android.view.MotionEvent event) {
if(event.getAction() == android.view.MotionEvent.ACTION_DOWN) {
- Button_click_from_native(final_sender_object);
+ Button_on_click(final_sender_object);
return true;
}
return false;
};
`}
end
+
+redef class Android_app_Fragment
+ private new (nit_window: Window)
+ import Window.on_create_fragment in "Java" `{
+ final int final_nit_window = nit_window;
+
+ return new android.app.Fragment(){
+ @Override
+ public android.view.View onCreateView(android.view.LayoutInflater inflater,
+ android.view.ViewGroup container, android.os.Bundle state) {
+
+ return Window_on_create_fragment(final_nit_window);
+ }
+ };
+ `}
+end