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-24510] Android: Support for custom quick settings tiles. #9310

Merged
merged 25 commits into from Nov 10, 2017

Conversation

ypbnv
Copy link
Contributor

@ypbnv ypbnv commented Aug 14, 2017

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

Description:
Add support for custom quick settings tiles in Android N and above:
https://developer.android.com/about/versions/nougat/android-7.0.html#tile_api

Due to that being handled by a service, usage is very similar to JS file services. A file needs to be created in proper folder for Classic/Alloy projects and added in the element of tiapp.xml. For every tile you want to have you must create a new service. This service is of special type - 'quicksettings' providing two new attributes:

tiapp.xml:
<service url="quicksettingstileservice.js" label='Label' icon='logo.png' type="quicksettings"/>

Label and icon for the tile. By default they are using the application name and icon.

quicksettingstileservice.js

// Grab a reference to the service and it's intent
var service = Ti.Android.currentService;
service.addEventListener('tileadded', function () {
	Ti.API.info('On Tile Added!');
});
service.addEventListener('tileremoved', function () {
	Ti.API.info('On Tile Removed!');
});
service.addEventListener('startlistening', function () {
	Ti.API.info('Started Listening');
});
service.addEventListener('stoplistening', function () {
	Ti.API.info('Stopped listening');
});

// tile's event listeners
service.addEventListener('tiledialogoptionselected', function (e){Ti.API.info('Option selected:' + e.itemIndex)});
service.addEventListener('tiledialogcancelled', function (){Ti.API.info('Tile_DIALOG cancelled')});
service.addEventListener('tiledialogpositive', function (){Ti.API.info('Tile_DIALOG positive')});
service.addEventListener('tiledialogneutral', function (){Ti.API.info('Tile_DIALOG neutral')});
service.addEventListener('tiledialognegative', function (){Ti.API.info('Tile_DIALOG negative')});

service.addEventListener('click', function () {
	Ti.API.info('On tile clicked!');
        // device state listeners
	Ti.API.info('Is device locked - ' + service.isLocked());
	Ti.API.info('Is device secure - ' + service.isSecure());

        //tile state updating
	/*if (service.getState() == Ti.Android.TILE_STATE_ACTIVE) {
		service.setState(Ti.Android.TILE_STATE_INACTIVE);
		service.setLabel('Inactive');
		service.setIcon('inactive.png');
	} else {
		service.setState(Ti.Android.TILE_STATE_ACTIVE);
		service.setLabel('Active');
		service.setIcon('active.png');
	}
	service.updateTile();
	Ti.API.info(service.getState());
	Ti.API.info(service.getLabel());*/

        //alert dialog creating
	/*var opts = {
		message: 'Lorem ipsum',
		options: ['Confirm', 'Help', 'Cancel'],
		title: 'Delete File?',
		selectedIndex: 1,
		buttonNames: ['Yes', 'Cancel', 'No']
	};
	service.showDialog(opts);*/

        //launching an intent
	/*var intent = Ti.Android.createIntent({
    	action: Ti.Android.ACTION_GET_CONTENT,
    	type: "vnd.android.cursor.item/phone"
	});
	service.startActivityAndCollapse(intent);*/

        //unlock and run JS code
       /*service.unlockAndRun('Ti.API.info("JS run test")');*/
});

Documentation is on the way.

builder.setMessage(krollDict.getString(TiC.PROPERTY_MESSAGE));
}
if (krollDict.containsKey(TiC.PROPERTY_BUTTON_NAMES))
{
Copy link
Contributor

Choose a reason for hiding this comment

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

fix {

builder.setPositiveButton(text, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
fireEvent(TiC.EVENT_TILE_DIALOG_POSITIVE,null);
Copy link
Contributor

Choose a reason for hiding this comment

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

spacing of ,

same below

if (service.type == 'quicksettings') {
s = {};
var serviceName = this.appid + '.' + service.classname;
var icon = '@drawable/' + this.tiapp.icon.replace(/((\.9)?\.(png|jpg))$/, '');
Copy link
Contributor

Choose a reason for hiding this comment

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

var icon = '@drawable/' + (service.label || this.tiapp).icon.replace(/((\.9)?\.(png|jpg))$/, '');

}
});
var label = this.tiapp.name;
Copy link
Contributor

Choose a reason for hiding this comment

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

var label = service.label || this.tiapp.name;

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

Optimize default label and icon code in build script.
@ypbnv
Copy link
Contributor Author

ypbnv commented Aug 14, 2017

@garymathews Updated.

@@ -3381,6 +3385,28 @@ AndroidBuilder.prototype.generateTheme = function generateTheme(next) {
next();
};

function serviceParser(serviceNode, result) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't like that you're passing in an object to modify as a side-effect of calling the method. I prefer to explicitly alter a copy of an object and return the modified copy as return value. Calling methods with the intention of causing side-effect modifications on the arguments is non-obvious.

label: label
});
const doc = new DOMParser().parseFromString(serviceXML, 'text/xml');
serviceParser(doc.firstChild, s);
Copy link
Contributor

Choose a reason for hiding this comment

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

How about serviceParser doesn't get passed in s, since it's an empty object, and instead just creates an empty object to start from and returns the built object, with we then assign to s here?
s = serviceParser(doc.firstChild);

It would mean that line 3629 would need to change to let s = {};.

@ypbnv
Copy link
Contributor Author

ypbnv commented Sep 13, 2017

@sgtcoolguy Updated the PR.

service.setLabel('Active');
service.setIcon('active.png');
}
service.updateTile();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Indentation is messed here.

summary: Prompts the user to unlock the device and runs the JS code
parameters:
- name: jsCode
summary: JavaScript code to be evaluated
Copy link
Collaborator

Choose a reason for hiding this comment

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

Missing . at the end. Please also fix the other occurrences above.

Creates and shows a default Alert dialog with the provided options dictionary.

Note: Alert dialog only supports one of the following: message/options. If you pass both - only `message` will
be shown.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Break the line after message/options. already.

- name: tiledialogneutral
summary: Dispatched when the neutral (index 1) button has been clicked.

- name: tiledialognegative
Copy link
Collaborator

Choose a reason for hiding this comment

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

While I do understand that we also used lowercase-only event naming-conventions, I'd like to discuss to move away from that. We should have camel-cased event-driven event-names, like tileRemoved and tileDialogOptionSelected. It would also look better on Alloy, e.g. on="tileRemoved". Any downsides?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you are talking about using camel-cased event-names everywhere (not only for this service) we may have some trouble with deprecated event-names which are camel-cased. I had a case where the runtime does not behave consistently when two events with the same name (one lowercased, one camel-cased) are dispatched at the same time.

@ypbnv
Copy link
Contributor Author

ypbnv commented Sep 13, 2017

@hansemannn Updated docs formatting.

@lokeshchdhry
Copy link
Contributor

FR Passed.

custom quick settings tiles works as expected. Properties, methods etc works successfully on android N & above.

Studio Ver: 4.10.0.201709271713
SDK Ver: 7.0.0 local build
OS Ver: 10.12.3
Xcode Ver: Xcode 8.3.3
Appc NPM: 4.2.10
Appc CLI: 6.3.0
Ti CLI Ver: 5.0.14
Alloy Ver: 1.10.7
Node Ver: 7.10.1
Java Ver: 1.8.0_101
Devices: ⇨ google Nexus 6P --- Android 8.0.0
⇨ google pixel --- Android 7.1.1

@build
Copy link
Contributor

build commented Nov 9, 2017

Fails
🚫

🔬 There are library changes, but no changes to the unit tests. That's OK as long as you're refactoring existing code, but will require an admin to merge this PR. Please see README.md#unit-tests for docs on unit testing.

Generated by 🚫 dangerJS

@lokeshchdhry
Copy link
Contributor

@ypbnv, If possible can you please add some unit tests. Jenkins is blocking merging because of it.

@ypbnv
Copy link
Contributor Author

ypbnv commented Nov 10, 2017

@lokeshchdhry No unit tests can be added for this feature.
@sgtcoolguy Could you merge it, please?

@sgtcoolguy sgtcoolguy merged commit 50f7a4c into tidev:master Nov 10, 2017
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