Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TIMOB-17964] Android 5.0: Add support for Toolbar #9147

Merged
merged 20 commits into from Aug 23, 2017

Conversation

ypbnv
Copy link
Contributor

@ypbnv ypbnv commented Jun 16, 2017

JIRA: https://jira.appcelerator.org/browse/TIMOB-17964

Needs to be merged first:

Description:

  • Added support for Toolbar - as a separate view and as a custom ActionBar.
  • Extracted "makeImageSource" in a new ResourceHelper class to avoid code repetition.

Having a toolbar as an ActionBar requires using a theme without a default one:

  • Theme.Titanium
  • Theme.AppCompat.Translucent.NoTitleBar
  • Theme.AppCompat.Translucent.NoTitleBar.Fullscreen
  • Theme.AppCompat.NoTitleBar
  • Theme.AppCompat.NoTitleBar.Fullscreen

Very basic test case

var window = Ti.UI.createWindow();

var toolbar = Ti.UI.createToolbar({
    width: Ti.UI.FILL,
    extendBackground: true,
    top: 0
});

var button = Ti.UI.createButton({
    title: 'Button',
    right: 10,
    top: 0
});

button.addEventListener('click', function(e) {
    Ti.API.info('BackgroundColor is ' + toolbar.getBackgroundColor());
    Ti.API.info('Title is ' + toolbar.getTitle());
    Ti.API.info('Subtitle is ' + toolbar.getSubtitle());
    Ti.API.info('Logo is ' + JSON.stringify(toolbar.getLogo()));
    Ti.API.info('Overflow icon is ' + JSON.stringify(toolbar.getOverflowIcon()));
    Ti.API.info('Navigation icon is ' + JSON.stringify(toolbar.getNavigationIcon()));
  
    toolbar.showOverflowMenu();
  
    setTimeout(function() {
        Ti.API.info('Is overflow menu showed? - ' + toolbar.isOverflowMenuShowing());
      
        toolbar.hideOverflowMenu();
        toolbar.setLogo('logo2.jpg');
        toolbar.collapseActionView();
        toolbar.dismissPopupMenus();
        toolbar.setBackgroundColor('red');
        toolbar.setTitle('New Title');
        toolbar.setTitleTextColor('orange');
        toolbar.setSubtitle('New Subtitle');
        toolbar.setSubtitleTextColor('blue');
      
        Ti.API.info('Content inset end is ' + toolbar.getContentInsetEnd());
        Ti.API.info('Content inset end with actions is ' + toolbar.getContentInsetEndWithActions());
        Ti.API.info('Content inset left is ' + toolbar.getContentInsetLeft());
        Ti.API.info('Content inset right is ' + toolbar.getContentInsetRight());
        Ti.API.info('Content inset start is ' + toolbar.getContentInsetStart());
        Ti.API.info('Content inset start with navigation is ' + toolbar.getContentInsetStartWithNavigation());
    }, 3000);
});

toolbar.setItems([button]);
window.add(toolbar);

window.activity.onCreateOptionsMenu = function(e) {
    var menu = e.menu;
    
    var menuItem = menu.add({
        title: 'Item 1',
        showAsAction: Ti.Android.SHOW_AS_ACTION_NEVER
    });
    
    menuItem.addEventListener('click', function(e) {
        Ti.API.debug('Item 1 was clicked');
    });
    
    var menuItem2 = menu.add({
        title: 'Item 2',
        showAsAction: Ti.Android.SHOW_AS_ACTION_NEVER
    });
    
    var menuItem3 = menu.add({
        title: 'Item 3',
        showAsAction: Ti.Android.SHOW_AS_ACTION_NEVER
    });
    
    var menuItem4 = menu.add({
        title: 'Item 4',
        showAsAction: Ti.Android.SHOW_AS_ACTION_NEVER
    });
};

window.activity.supportToolbar = toolbar;

window.addEventListener('open', function() {
    toolbar.setBackgroundColor('blue');
    toolbar.setTitle('Title');
    toolbar.setTitleTextColor('red');
    toolbar.setSubtitle('Subtitle');
    toolbar.setSubtitleTextColor('green');
    toolbar.setLogo('logo.png');
    toolbar.setNavigationIcon('logo.png');
    toolbar.setOverflowIcon('logo.png');
    //toolbar.setContentInsetEndWithActions(10);
    //toolbar.setContentInsetStartWithNavigation(11);
    //toolbar.setContentInsetsAbsolute(12,12);
    toolbar.setContentInsetsRelative(13, 13);
});

window.open();

Alloy test:

<Alloy>
    <Window title="My Test App" backgroundColor="gray" customToolbar="toolbar">
        <Toolbar
                 id="toolbar"
                 title="MyMenu"
                 subtitle="Subtitle"
                 width="Ti.UI.FILL"
                 top=0
                 barColor="#639851"
                 displayHomeAsUp="true"
                 homeButtonEnabled="true"
                 overflowIcon="logo.png">
             <Items>
                <Button id="ok" title="OK"/>
                <Button id="cancel" title="Cancel"/>
            </Items>
        </Toolbar>
        <Menu>
            <MenuItem id="item1" title="Settings" showAsAction="Ti.Android.SHOW_AS_ACTION_NEVER"/>
            <MenuItem id="item2" title="Search" showAsAction="Ti.Android.SHOW_AS_ACTION_NEVER"/>
        </Menu> 
    </Window>
</Alloy>

Documentation is on the way.

Files for the test case:
logo
logo2

Initial commit - adds Toolbar proxy with basic functionality.
@ypbnv ypbnv added this to the 6.2.0 milestone Jun 16, 2017
@ypbnv ypbnv requested a review from jquick-axway June 16, 2017 14:12
@ypbnv ypbnv added feature and removed improvement labels Jun 16, 2017
summary: Sets a toolbar instance to be used as an ActionBar.
parameters:
- name: toolbar
type: Titanium.UI.Android.Toolbar
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setter/getter are generated by the apidoc-generator automatically, you don't need to specify it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't described that as I should. I will add a more detailed description.
The Activity class does not handle property changes. So this method is used if you want to set a Toolbar as ActionBar after the instance is created. So technically currently this is not a setter for the supportToolbar (maybe I should assign it the toolbar reference).

@@ -435,6 +441,10 @@ properties:
type: Number
permission: write-only

- name: supportToolbar
summary: Toolbar instance that serves as ActionBar
type: Titanium.UI.Android.Toolbar
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also add the since: "6.2.0" key :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad. Will add it.

- name: getContentInsetEnd
summary: Returns the margin at the toolbar's content end.
returns:
type: Number
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: No manual setter / getter needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't added the content insets as properties, so these are directly setting/getting values to/from the native view. I wanted to keep the properties the same as the iOS ones (except borders) and just have Android only methods.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what you mean. iOS does not expose the property, and the getter / setter are generated by the build-script, so when you explicitly write them, they will likely be generated twice.

Example:

This line in the yml-files of Ti.UI.View will produce this property, but also the getter and setter of it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I got that.
There is no property "contentInsetEnd" in the Toolbar.yml. This is because there is no such property in the ToolbarProxy in Android.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alrighty. Just thought that people would still like the contentInsetEnd property, but let's keep it then.

@@ -93,7 +83,7 @@ public void setBigLargeIcon(Object icon)
@Kroll.method @Kroll.setProperty
public void setBigPicture(Object picture)
{
TiDrawableReference source = makeImageSource(picture);
TiDrawableReference source = ResourceHelper.getInstance().makeImageSource(this,picture);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember to always space your arguments (this, picture)

@@ -0,0 +1,28 @@
package ti.modules.titanium.filesystem;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should go in org.appcelerator.titanium.util with our other helpers?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That will create a circular dependency between 'titanium' and 'titanium-filesystem' modules.

@Kroll.method
public void setSupportActionBar(TiToolbarProxy tiToolbarProxy) {
TiBaseActivity activity = (TiBaseActivity) getWrappedActivity();
if (activity != null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brackets

if (activity != null) {
    activity.setSupportActionBar((Toolbar)tiToolbarProxy.getToolbarInstance());
}

- name: setSupportActionBar
summary: Sets a toolbar instance to be used as an ActionBar.
description: |
This methos is used if you want to add a Toolbar as an ActionBar after the Activity has been created.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo, methos

Copy link
Contributor

@garymathews garymathews left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CR: FAIL

@ypbnv
Copy link
Contributor Author

ypbnv commented Jun 23, 2017

@garymathews Fixed typo and code.

Copy link
Contributor

@jquick-axway jquick-axway left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question:
Since you're using "android.support.v7.widget.Toolbar" from the support library, this may mean that your new Toolbar feature is supported on Android 4.0 (API Level 14), which is the minimum API Level Titanium supports. Google's docs kind of suggest this here...
https://developer.android.com/training/appbar/setting-up.html

Can you please verify this please?

If this is true, then your doc changes no longer need to state that Android 5.0 is the min version needed anymore.

@@ -0,0 +1,28 @@
package ti.modules.titanium.filesystem;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the "ResourceHelper.java" file really needed? In all the places you call ResourceHelper.makeImageResource(), you can call the TiDrawableReference.fromObject() method and it'll provide you the same results, plus it'll support some additional image sources such as TiBlob and resource ID.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's way better. I didn't realize I could use it.

@@ -53,7 +56,9 @@
protected ActionBarProxy actionBarProxy;

private KrollFunction resultCallback;


public TiToolbarProxy toolbarProxy;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new "toolbarProxy" member variable doesn't appear to be accessed anywhere.

Also, I would prefer that all member variables be private or protected (preferably private) so that we can control how they're assigned.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

*/
public TiToolbar(TiViewProxy proxy) {
super(proxy);
toolbar = new Toolbar(TiApplication.getAppCurrentActivity());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that you're passing the getAppCurrentActivity() context to the Toolbar. Are you sure this is safe? Odds are this is going to be the previous activity in the stack, not the activity the toolbar will be assigned to. And I believe there is a possibility where this method can return null as well, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess proxy.getActivity() should guarantee us that the proper one is passed?

* @param proxies View proxies to be used
*/
public void setItems(TiViewProxy[] proxies) {
for (int i=0; i < proxies.length; i++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should check if the "proxies" argument is null.

Guard for passing the correct activity during creation.
Guard for null parameter.
Remove redundant variable.
@ypbnv
Copy link
Contributor Author

ypbnv commented Jun 29, 2017

@jquick-axway Updated the PR.
As for the minimum version I suppose you refer to this line
osver: {android: {min: "21"}}
in Toolbar.yml ?
If that's the case the requirement applies only for 'extendBackground' property because it depends on making the StatusBar transparent. And that is supported only below API 21 (I may be wrong here).

Otherwise the Toolbar is working fine on every version down to API 16. Which is the minimum Android version required for SDK 6.0.0+, according to the compatabilty matrix. I haven't tested it on API 15 and 14.

A toolbar can be positioned everywhere in the the layout as a [View](Titanium.UI.View). It can also be used as an ActionBar
for [activities](Titanium.Android.Activity). This allows a toolbar to inherit [ActionBar's](Titanium.Android.ActionBar) methods,
properties and events and provide a better customization of this design element. For example you can provide your own images
to be used as navigation button icon, nverflow menu icon and logo. In order to do that an application must be using Theme.AppCompat.NoTitleBar.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: Change "nverflow" to "overflow".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, the following needs a comma. Please change...
In order to do that an application...
...to...
In order to do that, an application...

- name: extendBackground
summary: If `true`, the background of the toolbar extends upwards by the height of StatusBar for current device.
description: |
This property is Numberended to be used when the toolbar is used as an ActionBar. It allows the user to specify
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean by "Numberended".

Also, the "extendBackground" Java code appears to auto-size and auto-position the toolbar. So, was this description intended for the "translucent" property?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Numberended" happens when you have written "intended" and you replace all "int" to "Number" to match the documentation format. : )

'extendBackground' shouldn't be doing that - it adds padding at the top equal to the StatusBar height and makes the latter's background transparent. It will do it wherever you place your toolbar and even if it is not passed as an ActionBar. Can you provide me a snippet where that happens?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding the "extendBackground", I was more confused about the wording in the documentation. I haven't actually tested it.

summary: Returns the source of the navigation image in the format it was set.

- name: getOverflowIcon
summary: Returns the source of the overflow menu image in the format it was set.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps the "return:" documentation for getLogo(), getNavigationIcon(), and getOverflowIcon() should be set to...
[String, Titanium.Blob, Titanium.Filesystem.File]

...like how it is for their setters.

Also, perhaps it would be better to document these as properties.

Copy link
Contributor Author

@ypbnv ypbnv Jun 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we document them as properties that would mean that you can access them directly and the widget is listening for their change. This is not the case with the Logo, OverflowIcon and NavigationIcon. They can be changed only through these methods. We can change them to be properties (also the content inset ones) if that will be more convenient from user standpoint.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They don't have to be properties, but ideally they should be accepted as dictionary field settings in your createToolbar() function. The reason is because this is how an Alloy XML file would pass a "Toolbar" element's attributes to the createToolbar() function. And I believe most of a create function's dictionary fields are expected to be the object's properties as well.

@hansemannn, do you have have any advise regarding this?

* @param value Boolean value to set. True makes the Toolbar's background extend. False - default behaviour.
*/
private void handleBackgroundExtended(boolean value) {
if (value) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling handleBackgroundExtended(false) will do nothing. So, once you've set it to true, it's locked-in forever. This was the intent, right?

If so, then I would get rid of the "value" argument.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I have missed that.

@jquick-axway
Copy link
Contributor

We should also document an example on how to set up a Toolbar via Alloy and XML, like how we document it for ActionBar. This may be important for developers who wish to migrate their ActionBar Alloy app settings to the new Toolbar way of doing things.
http://docs.appcelerator.com/platform/latest/#!/api/Titanium.Android.ActionBar

@ypbnv
Copy link
Contributor Author

ypbnv commented Jul 4, 2017

@jquick-axway Alloy itself will need some changes to support the Toolbar on Android. I did some adjustments, but they may not be relevant if we decide to move the widget in Ti.UI for both platforms.

Guard for missing translucency value.
@ypbnv
Copy link
Contributor Author

ypbnv commented Aug 9, 2017

@hansemannn, @jquick-axway, @garymathews Updated the PR.
Alloy PR for the changed namespace: tidev/alloy#841
Maybe the best approach for update in the docs would be after we are done on the iOS too - merge the current iOS documentation, describe platform specific properties and add new methods/properties?

@hansemannn
Copy link
Collaborator

Now that we have the Alloy PR up, I can quickly do the iOS part and do the docs there, adding the Android platform there as well. Not sure when QE can validate this PR, but the iOS one will be very straight-forward (this week'ish).

@lokeshchdhry
Copy link
Contributor

lokeshchdhry commented Aug 10, 2017

FR Passed.

Toolbar, its properties, methods & events from view seem to work as expected.

You need to have custom theme with no actionbar in the app:

builtin_themes.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
	<style name="NoActionBar" parent="Theme.AppCompat.NoTitleBar"/>
</resources>

tiappp.xml:

<android xmlns:android="http://schemas.android.com/apk/res/android">
	    <manifest>
	        <application android:theme="@style/NoActionBar"/>
	    </manifest>
</android>

Studio Ver: 4.9.1.201707200100
SDK Ver: 7.0.0 local build
OS Ver: 10.12.3
Xcode Ver: Xcode 8.3.3
Appc NPM: 4.2.9
Appc CLI: 6.2.3
Ti CLI Ver: 5.0.14
Alloy Ver: 1.9.13
Node Ver: 6.10.1
Java Ver: 1.8.0_101
Devices: ⇨ google Pixel --- Android 7.1.1
⇨ google Nexus 5 --- Android 6.0.1

@hansemannn hansemannn modified the milestones: 7.0.0, 6.2.0 Aug 15, 2017
int width = widthDimension != null ? widthDimension.getAsPixels(toolbar):Toolbar.LayoutParams.WRAP_CONTENT;
TiDimension heightDimension = source.getLayoutParams().optionHeight;
int height = heightDimension != null ? heightDimension.getAsPixels(toolbar):Toolbar.LayoutParams.WRAP_CONTENT;
res.setLayoutParams(new Toolbar.LayoutParams(width,height));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

space after ,

Copy link
Contributor

@garymathews garymathews left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above, also create a 6.2.0 backport

*/
public void setLogo(Object object) {
if (!TiApplication.isUIThread()) {
TiMessenger.sendBlockingMainMessage(mainHandler.obtainMessage(TOOLBAR_SET_LOGO),object);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

space after ,

there are other instances of this, please check

}

/**
* Handler for translucency change
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor, fix spacing after * here

@@ -21,9 +22,11 @@
import android.app.Activity;
import android.content.Intent;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;;
import android.support.v7.app.AppCompatActivity;
import org.appcelerator.titanium.view.TiUIView;;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

two ;; at end of line

Remove unused import.
Copy link
Contributor

@garymathews garymathews left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CR: PASS
👍

@ypbnv
Copy link
Contributor Author

ypbnv commented Aug 23, 2017

@garymathews, @jquick-axway, @hansemannn Update:
I added the changes from Joshua's latest feedback.
Merged the documentation in the proper place and added two cross-platform examples.
Extracted a few more Android properties as Titanium properties.
I have tested the Alloy implementation with the code from this PR: tidev/alloy#841

and it seems to be working fine on Android.

@lokeshchdhry lokeshchdhry merged commit f60ed61 into tidev:master Aug 23, 2017
@lokeshchdhry
Copy link
Contributor

lokeshchdhry commented Aug 23, 2017

@ypbnv , @garymathews - I merged this by mistake before tidev/alloy#841.
Do you want to revert this ?

@ypbnv
Copy link
Contributor Author

ypbnv commented Aug 24, 2017

@lokeshchdhry It should not create issues, but won't be available for testing through Alloy before the changes from tidev/alloy#841 are applied. Classic Titanium will be fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants