diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..929b0b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +ios/Build +.DS_Store \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..33dcf44 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2010-2013 Mads Møller + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md old mode 100644 new mode 100755 index c4095b3..404a877 --- a/README.md +++ b/README.md @@ -1 +1,58 @@ -NappDrawer +# Napp Drawer Module [![gitTio](http://gitt.io/badge.svg)](http://gitt.io/component/dk.napp.drawer) [![NPM](https://img.shields.io/npm/v/ti-module-dk.napp.drawer.svg?style=flat-square)](https://www.npmjs.com/package/ti-module-dk.napp.drawer) + +Cross Platform Example (iOS / Android) + +## Description + +The Napp Drawer module extends the Appcelerator Titanium Mobile framework. +The module is licensed under the MIT license. + +There are **some minor platform parity differences** in this module. iOS version uses Windows whereas Android uses views. The android version still uses the same properties even though its views rather than windows, e.g. `isAnyWindowOpen()` is the exact same method call on each platform. + +## Quick Start + +### Get it + +Download the latest distribution ZIP-file and consult the [Titanium Documentation](http://docs.appcelerator.com/titanium/latest/#!/guide/Using_a_Module) on how install it + +Or simply use the [gitTio CLI](http://gitt.io/cli) [![gitTio](http://gitt.io/badge.svg)](http://gitt.io/component/dk.napp.drawer) + +`$ gittio install dk.napp.drawer` + +Or NPM [![NPM](https://img.shields.io/npm/v/ti-module-dk.napp.drawer.svg?style=flat-square)](https://www.npmjs.com/package/ti-module-dk.napp.drawer) + +`$ npm i --save ti-module-dk.napp.drawer` + +## Community Driven + +I encourage everyone to send Pull Requests - keeping this module flying with new features. + +## Author + +**Mads Møller** +web: http://www.napp.dk +email: mm@napp.dk +twitter: @nappdev + + +## License + + Copyright (c) 2010-2013 Mads Møller + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. diff --git a/android/.classpath b/android/.classpath new file mode 100755 index 0000000..12f865d --- /dev/null +++ b/android/.classpath @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/android/.gitignore b/android/.gitignore new file mode 100755 index 0000000..4d5b059 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,5 @@ +tmp +bin +build +.apt_generated +libs diff --git a/android/.settings/com.aptana.editor.common.prefs b/android/.settings/com.aptana.editor.common.prefs new file mode 100755 index 0000000..ac2f456 --- /dev/null +++ b/android/.settings/com.aptana.editor.common.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +selectUserAgents=com.appcelerator.titanium.mobile.module.nature\:android diff --git a/android/.settings/org.eclipse.jdt.apt.core.prefs b/android/.settings/org.eclipse.jdt.apt.core.prefs new file mode 100755 index 0000000..668dd70 --- /dev/null +++ b/android/.settings/org.eclipse.jdt.apt.core.prefs @@ -0,0 +1,7 @@ +#Thu Sep 02 15:18:34 CDT 2010 +eclipse.preferences.version=1 +org.eclipse.jdt.apt.aptEnabled=true +org.eclipse.jdt.apt.genSrcDir=.apt_generated +org.eclipse.jdt.apt.reconcileEnabled=true + +org.eclipse.jdt.apt.processorOptions/kroll.jsonFile=nappslide.json diff --git a/android/.settings/org.eclipse.jdt.core.prefs b/android/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 0000000..2595d34 --- /dev/null +++ b/android/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,3 @@ +#Thu Sep 02 15:18:34 CDT 2010 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.processAnnotations=enabled diff --git a/android/README.md b/android/README.md new file mode 100755 index 0000000..556edb5 --- /dev/null +++ b/android/README.md @@ -0,0 +1,236 @@ +# Napp Drawer Module + +## Description + +The Napp Drawer module extends the Appcelerator Titanium Mobile framework. +The module is licensed under the MIT license. + +Thanks to *Jeremy Feinstein* for his work with this repo: https://github.com/jfeinstein10/SlidingMenu and a special thanks to *Martin Guillon* for helping me out. + + +### NOTICE: The below API method calls are often called something with window. The is only to keep parity with the iOS version of this module. Please remember that this Android version uses views and not windows. + + +## Get the module + +**Find the newest version in the dist folder** + +## Referencing the module in your Ti mobile application + +Simply add the following lines to your `tiapp.xml` file: + + + dk.napp.drawer + + + +## Reference + +For more detailed code examples take a look into the example app. + +For Alloy projects, use the [nl.fokkezb.drawer](https://github.com/fokkezb/nl.fokkezb.drawer) widget. + +### centerWindow, leftWindow, rightWindow + +Napp Drawer does not require you to use the 3 views. You can also use either the combo of center/left or center/right for your desired needs. + +```javascript +var NappDrawerModule = require('dk.napp.drawer'); +var mainWindow = NappDrawerModule.createDrawer({ + fullscreen:false, + leftWindow: leftMenuView, + centerWindow: centerView, + rightWindow: rightMenuView, + fading: 0.2, // 0-1 + parallaxAmount: 0.2, //0-1 + shadowWidth:"40dp", + leftDrawerWidth: "200dp", + rightDrawerWidth: "200dp", + animationMode: NappDrawerModule.ANIMATION_NONE, + closeDrawerGestureMode: NappDrawerModule.CLOSE_MODE_MARGIN, + openDrawerGestureMode: NappDrawerModule.OPEN_MODE_ALL, + orientationModes: [Ti.UI.PORTRAIT, Ti.UI.UPSIDE_PORTRAIT] +}); +``` + +## API Properties + +### CenterWindow, leftWindow, rightWindow + +A method that allow change of a view. + +```javascript +var newView = Ti.UI.createView({ + backgroundColor: "#FF0000" +}; +mainWindow.setCenterWindow(newView); +``` + +### LeftDrawerWidth, rightDrawerWidth + +Update the width with the drawer side view. + +```javascript +mainWindow.setLeftDrawerWidth("160dp"); +``` + + +### OpenDrawerGestureMode + +Set gesture support for opening the drawer through a mask. + +```javascript +mainWindow.setOpenDrawerGestureMode(NappDrawerModule.OPEN_MODE_NONE); +``` + +| input (constant) | Description | +| ----- | ----------- | +| OPEN_MODE_NONE | The user can not open the drawer by any swipe gestures. | +| OPEN_MODE_MARGIN | The user can open the drawer by a swipe gesture on the screen's margin. | +| OPEN_MODE_ALL | The user can open the drawer by a swipe gesture anywhere on the screen. | + + + +### CloseDrawerGestureMode + +Set gesture support for closing the drawer through a mask. + +```javascript +mainWindow.setCloseDrawerGestureMode(NappDrawerModule.CLOSE_MODE_NONE); +``` + +| input (constant) | Description | +| ----- | ----------- | +| CLOSE_MODE_NONE | The user can not close the drawer by any swipe gestures. | +| CLOSE_MODE_MARGIN | The user can close the drawer by a swipe gesture on the screen's margin. | +| CLOSE_MODE_ALL | The user can close the drawer by a swipe gesture anywhere on the screen. | + +*Note that these gestures may impact touches sent to the child view controllers, so be sure to use these appropriately for your application.* + +### AnimationMode + +Set the overall animation of the side views when opening and closing the drawer. + +```javascript +mainWindow.setAnimationMode(NappDrawerModule.ANIMATION_SLIDEUP); +``` + +| input (constant) | Description | +| ----- | ----------- | +| ANIMATION_NONE | No animation | +| ANIMATION_SLIDEUP | The side view will slide upwards while appearing on the screen. | +| ANIMATION_ZOOM | Zoom animation. | +| ANIMATION_SCALE | The side view will scale. | + + +### ShadowWidth + +How big should the shadow be. Use the "dp" notation to support different resolution on android. + +```javascript +mainWindow.setShadowWidth("40dp"); +``` + + +### ParallaxAmount + +Parallax is the amount of parallax between the centerView and a sideView animation. Set a value between 0 and 1. Its a very cool effect. Play with the slider in the example code to see the effect! + +```javascript +mainWindow.setParallaxAmount(0.3); +``` + +### Fading + +Use this property if you want the sideView drawer to fade in and out while entering/leaving the screen. The fade degree is between 0.0 and 1.0. + +```javascript +mainWindow.setFading(0.3); +``` + +### OrientationModes + +Use this property to restrict the drawer to a certain set of orientations. You can use any of Titanium's orientation constants defined in Ti.UI. (LANDSCAPE_LEFT, LANDSCAPE_RIGHT, PORTRAIT, UPSIDE_PORTRAIT). + +```javascript +mainWindow.setOrientationModes([Ti.UI.PORTRAIT, Ti.UI.UPSIDE_PORTRAIT]); +``` + +## API Methods + +### toggleLeftWindow, toggleRightWindow + +`toggleLeftWindow()` and `toggleRightWindow()` are used to toggle each visibility of either the left or right view. + +```javascript +mainWindow.toggleLeftWindow(); +``` + + +### isAnyWindowOpen, isLeftWindowOpen, isRightWindowOpen + +Check if a side or any views is opened in the drawer. + +```javascript +mainWindow. isAnyWindowOpen(); +``` + +## Changelog + +* v1.1.6 + * Updated Ti min-sdk to 4.1.0.GA + +* v1.1.5 + * Fix #159 - useArrowAnimation not working when there is no listener for didChangeOffset + * Fix #162 - Lollipop Bottom Nav Bar Overlays App on Android 5.0 and up + * Updated Ti min-sdk to 4.0.0.GA + +* v1.1.4 + * Burger To Arrow animation icon. (Android L style) + +* v1.1.3 + * Fix #71 - IllegalStateException when recreating activity in Android + +* v1.1.2 + * Added focus/blur events for Android + +* v1.1.1 + * Bugfix for ShadowWidth. Issue #51 + * Updated Ti min-sdk to 3.1.3.GA + +* v1.1 + * Added variable names to resemble iOS version more - for openDrawerGestureMode and closeDrawerGestureMode + +* v1.0 + * init + + +## Author + +**Mads Møller** +web: http://www.napp.dk +email: mm@napp.dk +twitter: @nappdev + + +## License + + Copyright (c) 2010-2013 Mads Møller + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. diff --git a/android/assets/README b/android/assets/README new file mode 100755 index 0000000..23130c3 --- /dev/null +++ b/android/assets/README @@ -0,0 +1,6 @@ +Place your assets like PNG files in this directory and they will be packaged with your module. + +If you create a file named dk.napp.slidemenu.js in this directory, it will be +compiled and used as your module. This allows you to run pure Javascript +modules that are pre-compiled. + diff --git a/android/build.properties b/android/build.properties new file mode 100755 index 0000000..a77e3c2 --- /dev/null +++ b/android/build.properties @@ -0,0 +1,3 @@ +titanium.platform=/Users/marcbender/Library/Application Support/Titanium/mobilesdk/osx/7.1.1.GA/android +android.platform=/Users/marcbender/Library/android-sdk-macosx/platforms/android-23 +module.ndkbuild=true diff --git a/android/build.xml b/android/build.xml new file mode 100755 index 0000000..0b15f5b --- /dev/null +++ b/android/build.xml @@ -0,0 +1,10 @@ + + + Ant build script for Titanium Android module nappslide + + + + + + + diff --git a/android/dist/dk.napp.drawer-android-2.0.3.zip b/android/dist/dk.napp.drawer-android-2.0.3.zip new file mode 100644 index 0000000..c20c596 Binary files /dev/null and b/android/dist/dk.napp.drawer-android-2.0.3.zip differ diff --git a/android/dist/nappslide.jar b/android/dist/nappslide.jar new file mode 100644 index 0000000..ac70f83 Binary files /dev/null and b/android/dist/nappslide.jar differ diff --git a/android/documentation/index.md b/android/documentation/index.md new file mode 100755 index 0000000..0a790af --- /dev/null +++ b/android/documentation/index.md @@ -0,0 +1,39 @@ +# nappslide Module + +## Description + +TODO: Enter your module description here + +## Accessing the nappslide Module + +To access this module from JavaScript, you would do the following: + + var nappslide = require("dk.napp.slidemenu"); + +The nappslide variable is a reference to the Module object. + +## Reference + +TODO: If your module has an API, you should document +the reference here. + +### ___PROJECTNAMEASIDENTIFIER__.function + +TODO: This is an example of a module function. + +### ___PROJECTNAMEASIDENTIFIER__.property + +TODO: This is an example of a module property. + +## Usage + +TODO: Enter your usage example here + +## Author + +TODO: Enter your author name, email and other contact +details you want to share here. + +## License + +TODO: Enter your license/legal information here. diff --git a/android/example/app.js b/android/example/app.js new file mode 100755 index 0000000..a121938 --- /dev/null +++ b/android/example/app.js @@ -0,0 +1,259 @@ +var leftMenuView = Ti.UI.createView({ + backgroundColor: 'white', + width: Ti.UI.FILL, + height: Ti.UI.FILL +}); + +var centerView = Ti.UI.createView({ + backgroundColor: 'white', + width: Ti.UI.FILL, + height: Ti.UI.FILL +}); + +var rightMenuView = Ti.UI.createView({ + backgroundColor: '#ddd', + width: Ti.UI.FILL, + height: Ti.UI.FILL +}); + +// create a menu +var leftTableView = Ti.UI.createTableView({ + font: { + fontSize: 12 + }, + rowHeight: 40, + data: [{ + title: 'Toggle Left View' + }, + { + title: 'Change Center Windowr' + }, + { + title: 'Default Window' + } + ] +}); + +leftMenuView.add(leftTableView); +leftTableView.addEventListener('click', function(e) { + Ti.API.info('isAnyWindowOpen: ' + drawer.isAnyWindowOpen()); + switch (e.index) { + case 0: + drawer.toggleLeftWindow(); //animate back to center + alert('You clicked ' + e.rowData.title + '. Implement menu structure.. '); + break; + case 1: + drawer.setCenterWindow(Ti.UI.createView({ + backgroundColor: 'red' + })); + drawer.toggleLeftWindow(); //animate back to center + break; + case 2: + drawer.setCenterWindow(centerView); + drawer.toggleLeftWindow(); //animate back to center + break; + } +}); + +// Action Bar - FAKE example +var actionBar = Ti.UI.createView({ + top: 0, + height: '44dp', + backgroundColor: '#ca2127' +}); +var leftToolbarBtn = Ti.UI.createButton({ + title: 'Left', + left: '6dp', + backgroundColor: 'transparent', + color: '#FFF' +}); +leftToolbarBtn.addEventListener('click', function() { + drawer.toggleLeftWindow(); +}); +var rightToolbarBtn = Ti.UI.createButton({ + title: 'Right', + right: '6dp', + backgroundColor: 'transparent', + color: '#FFF' +}); +rightToolbarBtn.addEventListener('click', function() { + drawer.toggleRightWindow(); +}); +var centerLabel = Ti.UI.createLabel({ + text: 'NappDrawer', + font: { + fontSize: '14dp', + fontWeight: 'bold' + }, + color: '#FFF' +}); +actionBar.add(leftToolbarBtn); +actionBar.add(rightToolbarBtn); +actionBar.add(centerLabel); +centerView.add(actionBar); + +// create interface +var scrollView = Ti.UI.createScrollView({ + layout: 'vertical', + left: 0, + right: 0, + top: '44dp', + contentHeight: 'auto', + contentWidth: '100%', + showVerticalScrollIndicator: true, + showHorizontalScrollIndicator: false +}); + +var slider = Ti.UI.createSlider({ + top: '20dp', + width: '280dp', + min: 0, + max: 1, + value: 0.2 +}); +var label = Ti.UI.createLabel({ + text: 'Parallax: ' + slider.value, + color: '#000', + top: '15dp' +}); +slider.addEventListener('touchend', function(e) { + label.setText('Parallax: ' + e.source.value); + drawer.setParallaxAmount(e.source.value); +}); +scrollView.add(label); +scrollView.add(slider); + +var gestureModeBtn = Ti.UI.createButton({ + title: 'Gesture Mode: ALL', + toggled: true, + top: 10 +}); + +gestureModeBtn.addEventListener('click', function(e) { + if (!e.source.toggled) { + var mode = 'ALL'; + drawer.setOpenDrawerGestureMode(NappDrawerModule.OPEN_MODE_ALL); + } else { + var mode = 'NONE'; + drawer.setOpenDrawerGestureMode(NappDrawerModule.OPEN_MODE_NONE); + } + gestureModeBtn.setTitle('Gesture Mode: ' + mode); + e.source.toggled = !e.source.toggled; + +}); +scrollView.add(gestureModeBtn); + +function updateSlider(value) { + slider.value = value; + slider.fireEvent('touchend', { + source: { + value: value + } + }); +} + +// animation mode +var animationMode = 0; +var animationModeBtn = Ti.UI.createButton({ + title: 'Animation Mode: NONE', + top: 10 +}); + +var aniModeText; + +animationModeBtn.addEventListener('click', function(e) { + if (animationMode == 3) { + animationMode = 0; + } else { + animationMode++; + } + switch (animationMode) { + case 0: + drawer.setAnimationMode(NappDrawerModule.ANIMATION_NONE); + updateSlider(0.2); + aniModeText = 'NONE'; + break; + case 1: + drawer.setAnimationMode(NappDrawerModule.ANIMATION_SLIDEUP); + updateSlider(0); + aniModeText = 'SLIDEUP'; + break; + case 2: + drawer.setAnimationMode(NappDrawerModule.ANIMATION_ZOOM); + updateSlider(0); + aniModeText = 'ZOOM'; + break; + case 3: + drawer.setAnimationMode(NappDrawerModule.ANIMATION_SCALE); + updateSlider(0); + aniModeText = 'SCALE'; + break; + } + animationModeBtn.setTitle('Animation Mode: ' + aniModeText); +}); + +scrollView.add(animationModeBtn); +centerView.add(scrollView); + +// CREATE THE MODULE +var NappDrawerModule = require('dk.napp.drawer'); +var drawer = NappDrawerModule.createDrawer({ + fullscreen: false, + leftWindow: leftMenuView, + centerWindow: centerView, + rightWindow: rightMenuView, + fading: 0.2, // 0-1 + parallaxAmount: 0.2, //0-1 + shadowWidth: '40dp', + leftDrawerWidth: '200dp', + rightDrawerWidth: '200dp', + animationMode: NappDrawerModule.ANIMATION_NONE, + closeDrawerGestureMode: NappDrawerModule.CLOSE_MODE_MARGIN, + openDrawerGestureMode: NappDrawerModule.OPEN_MODE_ALL, + orientationModes: [Ti.UI.PORTRAIT, Ti.UI.UPSIDE_PORTRAIT] +}); + + +drawer.addEventListener('didChangeOffset', function(e) { + //Ti.API.info('didChangeOffset: ' + e.offset); +}); + +drawer.addEventListener('windowDidOpen', function(e) { + if (e.window == NappDrawerModule.LEFT_WINDOW) { + Ti.API.info('windowDidOpen - LEFT DRAWER'); + } else if (e.window == NappDrawerModule.RIGHT_WINDOW) { + Ti.API.info('windowDidOpen - RIGHT DRAWER'); + } +}); +drawer.addEventListener('windowDidClose', function(e) { + Ti.API.info('windowDidClose'); +}); + +// Action Bar - REAL example +drawer.addEventListener('open', onNavDrawerWinOpen); + +function onNavDrawerWinOpen(evt) { + this.removeEventListener('open', onNavDrawerWinOpen); + + if (this.getActivity()) { + // need to explicitly use getXYZ methods + var actionBar = this.getActivity().getActionBar(); + + if (actionBar) { + // Now we can do stuff to the actionbar + actionBar.setTitle('NappDrawer Example'); + + // show an angle bracket next to the home icon, + // indicating to users that the home icon is tappable + actionBar.setDisplayHomeAsUp(true); + + // toggle the left window when the home icon is selected + actionBar.setOnHomeIconItemSelected(function() { + drawer.toggleLeftWindow(); + }); + } + } +} + +// lets open it +drawer.open(); diff --git a/android/hooks/README b/android/hooks/README new file mode 100755 index 0000000..66b10a8 --- /dev/null +++ b/android/hooks/README @@ -0,0 +1 @@ +These files are not yet supported as of 1.4.0 but will be in a near future release. diff --git a/android/hooks/add.py b/android/hooks/add.py new file mode 100755 index 0000000..04e1c1d --- /dev/null +++ b/android/hooks/add.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# +# This is the module project add hook that will be +# called when your module is added to a project +# +import os, sys + +def dequote(s): + if s[0:1] == '"': + return s[1:-1] + return s + +def main(args,argc): + # You will get the following command line arguments + # in the following order: + # + # project_dir = the full path to the project root directory + # project_type = the type of project (desktop, mobile, ipad) + # project_name = the name of the project + # + project_dir = dequote(os.path.expanduser(args[1])) + project_type = dequote(args[2]) + project_name = dequote(args[3]) + + # TODO: write your add hook here (optional) + + + # exit + sys.exit(0) + + + +if __name__ == '__main__': + main(sys.argv,len(sys.argv)) + diff --git a/android/hooks/install.py b/android/hooks/install.py new file mode 100755 index 0000000..b423fe9 --- /dev/null +++ b/android/hooks/install.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +# +# This is the module install hook that will be +# called when your module is first installed +# +import os, sys + +def main(args,argc): + + # TODO: write your install hook here (optional) + + # exit + sys.exit(0) + + + +if __name__ == '__main__': + main(sys.argv,len(sys.argv)) + diff --git a/android/hooks/remove.py b/android/hooks/remove.py new file mode 100755 index 0000000..f92a234 --- /dev/null +++ b/android/hooks/remove.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# +# This is the module project remove hook that will be +# called when your module is remove from a project +# +import os, sys + +def dequote(s): + if s[0:1] == '"': + return s[1:-1] + return s + +def main(args,argc): + # You will get the following command line arguments + # in the following order: + # + # project_dir = the full path to the project root directory + # project_type = the type of project (desktop, mobile, ipad) + # project_name = the name of the project + # + project_dir = dequote(os.path.expanduser(args[1])) + project_type = dequote(args[2]) + project_name = dequote(args[3]) + + # TODO: write your remove hook here (optional) + + # exit + sys.exit(0) + + + +if __name__ == '__main__': + main(sys.argv,len(sys.argv)) + diff --git a/android/hooks/uninstall.py b/android/hooks/uninstall.py new file mode 100755 index 0000000..a7ffd91 --- /dev/null +++ b/android/hooks/uninstall.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +# +# This is the module uninstall hook that will be +# called when your module is uninstalled +# +import os, sys + +def main(args,argc): + + # TODO: write your uninstall hook here (optional) + + # exit + sys.exit(0) + + +if __name__ == '__main__': + main(sys.argv,len(sys.argv)) + diff --git a/android/java-sources.txt b/android/java-sources.txt new file mode 100644 index 0000000..0c31379 --- /dev/null +++ b/android/java-sources.txt @@ -0,0 +1,16 @@ +"/Users/marcbender/Downloads/NappDrawer-master/android/src/chrisrenke/drawerarrowdrawable/DrawerArrowDrawable.java" +"/Users/marcbender/Downloads/NappDrawer-master/android/src/com/slidingmenu/lib/CanvasTransformerBuilder.java" +"/Users/marcbender/Downloads/NappDrawer-master/android/src/com/slidingmenu/lib/CustomViewAbove.java" +"/Users/marcbender/Downloads/NappDrawer-master/android/src/com/slidingmenu/lib/CustomViewBehind.java" +"/Users/marcbender/Downloads/NappDrawer-master/android/src/com/slidingmenu/lib/SlidingMenu.java" +"/Users/marcbender/Downloads/NappDrawer-master/android/src/dk/napp/drawer/Drawer.java" +"/Users/marcbender/Downloads/NappDrawer-master/android/src/dk/napp/drawer/DrawerProxy.java" +"/Users/marcbender/Downloads/NappDrawer-master/android/src/dk/napp/drawer/NappdrawerModule.java" +"/Users/marcbender/Downloads/NappDrawer-master/android/src/dk/napp/drawer/RHelper.java" +"/Users/marcbender/Downloads/NappDrawer-master/android/build/generated/java/dk/napp/drawer/NappdrawerBootstrap.java" +"/Users/marcbender/Downloads/NappDrawer-master/android/build/generated/r/android/support/compat/R.java" +"/Users/marcbender/Downloads/NappDrawer-master/android/build/generated/r/android/support/design/R.java" +"/Users/marcbender/Downloads/NappDrawer-master/android/build/generated/r/android/support/v7/appcompat/R.java" +"/Users/marcbender/Downloads/NappDrawer-master/android/build/generated/r/android/support/v7/cardview/R.java" +"/Users/marcbender/Downloads/NappDrawer-master/android/build/generated/r/dk/napp/drawer/R.java" +"/Users/marcbender/Downloads/NappDrawer-master/android/build/generated/r/ti/modules/titanium/ui/R.java" \ No newline at end of file diff --git a/android/lib/README b/android/lib/README new file mode 100755 index 0000000..a54313f --- /dev/null +++ b/android/lib/README @@ -0,0 +1,2 @@ +You can place any .jar dependencies in this directory and they will be included +when your module is being compiled. \ No newline at end of file diff --git a/android/manifest b/android/manifest new file mode 100755 index 0000000..65854ea --- /dev/null +++ b/android/manifest @@ -0,0 +1,18 @@ +# +# this is your module manifest and used by Titanium +# during compilation, packaging, distribution, etc. +# +version: 2.0.3 +apiversion:4 +description: Napp Drawer +author: Mads Moller +license: MIT +copyright: Napp ApS +architectures: arm64-v8a armeabi-v7a x86 + +# these should not be edited +name: nappslide +moduleid: dk.napp.drawer +guid: 0f157d47-db5c-4891-8197-c851e8d329e0 +platform: android +minsdk: 7.1.1 diff --git a/android/platform/README b/android/platform/README new file mode 100755 index 0000000..7ac991c --- /dev/null +++ b/android/platform/README @@ -0,0 +1,3 @@ +You can place platform-specific files here in sub-folders named "android" and/or "iphone", just as you can with normal Titanium Mobile SDK projects. Any folders and files you place here will be merged with the platform-specific files in a Titanium Mobile project that uses this module. + +When a Titanium Mobile project that uses this module is built, the files from this platform/ folder will be treated the same as files (if any) from the Titanium Mobile project's platform/ folder. diff --git a/android/platform/android/res/values/attrs.xml b/android/platform/android/res/values/attrs.xml new file mode 100755 index 0000000..d5ee00f --- /dev/null +++ b/android/platform/android/res/values/attrs.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/platform/android/res/values/ids.xml b/android/platform/android/res/values/ids.xml new file mode 100755 index 0000000..eac70c9 --- /dev/null +++ b/android/platform/android/res/values/ids.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/src/chrisrenke/drawerarrowdrawable/DrawerArrowDrawable.java b/android/src/chrisrenke/drawerarrowdrawable/DrawerArrowDrawable.java new file mode 100755 index 0000000..8c41de0 --- /dev/null +++ b/android/src/chrisrenke/drawerarrowdrawable/DrawerArrowDrawable.java @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2014 Chris Renke + * + * 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. + */ +package chrisrenke.drawerarrowdrawable; + +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PathMeasure; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.view.View; + +import static android.graphics.Color.BLACK; +import static android.graphics.Paint.ANTI_ALIAS_FLAG; +import static android.graphics.Paint.Cap; +import static android.graphics.Paint.Cap.BUTT; +import static android.graphics.Paint.Cap.ROUND; +import static android.graphics.Paint.SUBPIXEL_TEXT_FLAG; +import static android.graphics.Paint.Style.STROKE; +import static android.graphics.PixelFormat.TRANSLUCENT; +import static java.lang.Math.sqrt; + +/** A drawable that rotates between a drawer icon and a back arrow based on parameter. */ +public class DrawerArrowDrawable extends Drawable { + + /** + * Joins two {@link Path}s as if they were one where the first 50% of the path is {@code + * PathFirst} and the second 50% of the path is {@code pathSecond}. + */ + private static class JoinedPath { + + private final PathMeasure measureFirst; + private final PathMeasure measureSecond; + private final float lengthFirst; + private final float lengthSecond; + + private JoinedPath(Path pathFirst, Path pathSecond) { + measureFirst = new PathMeasure(pathFirst, false); + measureSecond = new PathMeasure(pathSecond, false); + lengthFirst = measureFirst.getLength(); + lengthSecond = measureSecond.getLength(); + } + + /** + * Returns a point on this curve at the given {@code parameter}. + * For {@code parameter} values less than .5f, the first path will drive the point. + * For {@code parameter} values greater than .5f, the second path will drive the point. + * For {@code parameter} equal to .5f, the point will be the point where the two + * internal paths connect. + */ + private void getPointOnLine(float parameter, float[] coords) { + if (parameter <= .5f) { + parameter *= 2; + measureFirst.getPosTan(lengthFirst * parameter, coords, null); + } else { + parameter -= .5f; + parameter *= 2; + measureSecond.getPosTan(lengthSecond * parameter, coords, null); + } + } + } + + /** Draws a line between two {@link JoinedPath}s at distance {@code parameter} along each path. */ + private class BridgingLine { + + private final JoinedPath pathA; + private final JoinedPath pathB; + + private BridgingLine(JoinedPath pathA, JoinedPath pathB) { + this.pathA = pathA; + this.pathB = pathB; + } + + /** + * Draw a line between the points defined on the paths backing {@code measureA} and + * {@code measureB} at the current parameter. + */ + private void draw(Canvas canvas) { + pathA.getPointOnLine(parameter, coordsA); + pathB.getPointOnLine(parameter, coordsB); + if (rounded) insetPointsForRoundCaps(); + canvas.drawLine(coordsA[0], coordsA[1], coordsB[0], coordsB[1], linePaint); + } + + /** + * Insets the end points of the current line to account for the protruding + * ends drawn for {@link Cap#ROUND} style lines. + */ + private void insetPointsForRoundCaps() { + vX = coordsB[0] - coordsA[0]; + vY = coordsB[1] - coordsA[1]; + + magnitude = (float) sqrt((vX * vX + vY * vY)); + paramA = (magnitude - halfStrokeWidthPixel) / magnitude; + paramB = halfStrokeWidthPixel / magnitude; + + coordsA[0] = coordsB[0] - (vX * paramA); + coordsA[1] = coordsB[1] - (vY * paramA); + coordsB[0] = coordsB[0] - (vX * paramB); + coordsB[1] = coordsB[1] - (vY * paramB); + } + } + + /** Paths were generated at a 3px/dp density; this is the scale factor for different densities. */ + private final static float PATH_GEN_DENSITY = 3; + + /** Paths were generated with at this size for {@link DrawerArrowDrawable#PATH_GEN_DENSITY}. */ + private final static float DIMEN_DP = 23.5f; + + /** + * Paths were generated targeting this stroke width to form the arrowhead properly, modification + * may cause the arrow to not for nicely. + */ + private final static float STROKE_WIDTH_DP = 2; + + private BridgingLine topLine; + private BridgingLine middleLine; + private BridgingLine bottomLine; + + private final Rect bounds; + private final float halfStrokeWidthPixel; + private final Paint linePaint; + private final boolean rounded; + + private boolean flip; + private float parameter; + + // Helper fields during drawing calculations. + private float vX, vY, magnitude, paramA, paramB; + private final float coordsA[] = { 0f, 0f }; + private final float coordsB[] = { 0f, 0f }; + + public DrawerArrowDrawable(Resources resources) { + this(resources, false); + } + + public DrawerArrowDrawable(Resources resources, boolean rounded) { + this.rounded = rounded; + float density = resources.getDisplayMetrics().density; + float strokeWidthPixel = STROKE_WIDTH_DP * density; + halfStrokeWidthPixel = strokeWidthPixel / 2; + + linePaint = new Paint(SUBPIXEL_TEXT_FLAG | ANTI_ALIAS_FLAG); + linePaint.setStrokeCap(rounded ? ROUND : BUTT); + linePaint.setColor(BLACK); + linePaint.setStyle(STROKE); + linePaint.setStrokeWidth(strokeWidthPixel); + + int dimen = (int) (DIMEN_DP * density); + + int paddingLeft = (int) (12.25f * density); + int paddingRight = (int) (12.25f * density); + + bounds = new Rect(0-paddingLeft, 0, dimen+paddingRight, dimen); + + Path first, second; + JoinedPath joinedA, joinedB; + + // Top + first = new Path(); + first.moveTo(5.042f, 20f); + first.rCubicTo(8.125f, -16.317f, 39.753f, -27.851f, 55.49f, -2.765f); + second = new Path(); + second.moveTo(60.531f, 17.235f); + second.rCubicTo(11.301f, 18.015f, -3.699f, 46.083f, -23.725f, 43.456f); + scalePath(first, density, paddingLeft); + scalePath(second, density, paddingLeft); + joinedA = new JoinedPath(first, second); + + first = new Path(); + first.moveTo(64.959f, 20f); + first.rCubicTo(4.457f, 16.75f, 1.512f, 37.982f, -22.557f, 42.699f); + second = new Path(); + second.moveTo(42.402f, 62.699f); + second.cubicTo(18.333f, 67.418f, 8.807f, 45.646f, 8.807f, 32.823f); + scalePath(first, density, paddingLeft); + scalePath(second, density, paddingLeft); + joinedB = new JoinedPath(first, second); + topLine = new BridgingLine(joinedA, joinedB); + + // Middle + first = new Path(); + first.moveTo(5.042f, 35f); + first.cubicTo(5.042f, 20.333f, 18.625f, 6.791f, 35f, 6.791f); + second = new Path(); + second.moveTo(35f, 6.791f); + second.rCubicTo(16.083f, 0f, 26.853f, 16.702f, 26.853f, 28.209f); + scalePath(first, density, paddingLeft); + scalePath(second, density, paddingLeft); + joinedA = new JoinedPath(first, second); + + first = new Path(); + first.moveTo(64.959f, 35f); + first.rCubicTo(0f, 10.926f, -8.709f, 26.416f, -29.958f, 26.416f); + second = new Path(); + second.moveTo(35f, 61.416f); + second.rCubicTo(-7.5f, 0f, -23.946f, -8.211f, -23.946f, -26.416f); + scalePath(first, density, paddingLeft); + scalePath(second, density, paddingLeft); + joinedB = new JoinedPath(first, second); + middleLine = new BridgingLine(joinedA, joinedB); + + // Bottom + first = new Path(); + first.moveTo(5.042f, 50f); + first.cubicTo(2.5f, 43.312f, 0.013f, 26.546f, 9.475f, 17.346f); + second = new Path(); + second.moveTo(9.475f, 17.346f); + second.rCubicTo(9.462f, -9.2f, 24.188f, -10.353f, 27.326f, -8.245f); + scalePath(first, density, paddingLeft); + scalePath(second, density, paddingLeft); + joinedA = new JoinedPath(first, second); + + first = new Path(); + first.moveTo(64.959f, 50f); + first.rCubicTo(-7.021f, 10.08f, -20.584f, 19.699f, -37.361f, 12.74f); + second = new Path(); + second.moveTo(27.598f, 62.699f); + second.rCubicTo(-15.723f, -6.521f, -18.8f, -23.543f, -18.8f, -25.642f); + scalePath(first, density, paddingLeft); + scalePath(second, density, paddingLeft); + joinedB = new JoinedPath(first, second); + bottomLine = new BridgingLine(joinedA, joinedB); + } + + @Override public int getIntrinsicHeight() { + return bounds.height(); + } + + @Override public int getIntrinsicWidth() { + return bounds.width(); + } + + @Override public void draw(Canvas canvas) { + if (flip) { + canvas.save(); + canvas.scale(1f, -1f, getIntrinsicWidth() / 2, getIntrinsicHeight() / 2); + } + + topLine.draw(canvas); + middleLine.draw(canvas); + bottomLine.draw(canvas); + if (flip) canvas.restore(); + } + + @Override public void setAlpha(int alpha) { + linePaint.setAlpha(alpha); + invalidateSelf(); + } + + @Override public void setColorFilter(ColorFilter cf) { + linePaint.setColorFilter(cf); + invalidateSelf(); + } + + @Override public int getOpacity() { + return TRANSLUCENT; + } + + public void setStrokeColor(int color) { + linePaint.setColor(color); + invalidateSelf(); + } + + /** + * Sets the rotation of this drawable based on {@code parameter} between 0 and 1. Usually driven + * via {@link DrawerListener#onDrawerSlide(View, float)}'s {@code slideOffset} parameter. + */ + public void setParameter(float parameter) { + if (parameter > 1 || parameter < 0) { + throw new IllegalArgumentException("Value must be between 1 and zero inclusive!"); + } + this.parameter = parameter; + invalidateSelf(); + } + + /** + * When false, rotates from 3 o'clock to 9 o'clock between a drawer icon and a back arrow. + * When true, rotates from 9 o'clock to 3 o'clock between a back arrow and a drawer icon. + */ + public void setFlip(boolean flip) { + this.flip = flip; + invalidateSelf(); + } + + /** + * Scales the paths to the given screen density. If the density matches the + * {@link DrawerArrowDrawable#PATH_GEN_DENSITY}, no scaling needs to be done. + */ + private static void scalePath(Path path, float density, int paddingLeft) { + path.offset(paddingLeft, 0); + if (density == PATH_GEN_DENSITY) return; + Matrix scaleMatrix = new Matrix(); + scaleMatrix.setScale(density / PATH_GEN_DENSITY, density / PATH_GEN_DENSITY, 0, 0); + path.transform(scaleMatrix); + } +} \ No newline at end of file diff --git a/android/src/com/slidingmenu/lib/CanvasTransformerBuilder.java b/android/src/com/slidingmenu/lib/CanvasTransformerBuilder.java new file mode 100755 index 0000000..22dbc2a --- /dev/null +++ b/android/src/com/slidingmenu/lib/CanvasTransformerBuilder.java @@ -0,0 +1,95 @@ +package com.slidingmenu.lib; + +import android.graphics.Canvas; +import android.view.animation.Interpolator; + +import com.slidingmenu.lib.SlidingMenu.CanvasTransformer; + +public class CanvasTransformerBuilder { + + private CanvasTransformer mTrans; + + private static Interpolator lin = new Interpolator() { + public float getInterpolation(float t) { + return t; + } + }; + + private void initTransformer() { + if (mTrans == null) + mTrans = new CanvasTransformer() { + public void transformCanvas(Canvas canvas, float percentOpen) { } + }; + } + + public CanvasTransformer zoom(final int openedX, final int closedX, + final int openedY, final int closedY, + final int px, final int py) { + return zoom(openedX, closedX, openedY, closedY, px, py, lin); + } + + public CanvasTransformer zoom(final int openedX, final int closedX, + final int openedY, final int closedY, + final int px, final int py, final Interpolator interp) { + initTransformer(); + mTrans = new CanvasTransformer() { + public void transformCanvas(Canvas canvas, float percentOpen) { + mTrans.transformCanvas(canvas, percentOpen); + float f = interp.getInterpolation(percentOpen); + canvas.scale((openedX - closedX) * f + closedX, + (openedY - closedY) * f + closedY, px, py); + } + }; + return mTrans; + } + + public CanvasTransformer rotate(final int openedDeg, final int closedDeg, + final int px, final int py) { + return rotate(openedDeg, closedDeg, px, py, lin); + } + + public CanvasTransformer rotate(final int openedDeg, final int closedDeg, + final int px, final int py, final Interpolator interp) { + initTransformer(); + mTrans = new CanvasTransformer() { + public void transformCanvas(Canvas canvas, float percentOpen) { + mTrans.transformCanvas(canvas, percentOpen); + float f = interp.getInterpolation(percentOpen); + canvas.rotate((openedDeg - closedDeg) * f + closedDeg, + px, py); + } + }; + return mTrans; + } + + public CanvasTransformer translate(final int openedX, final int closedX, + final int openedY, final int closedY) { + return translate(openedX, closedX, openedY, closedY, lin); + } + + public CanvasTransformer translate(final int openedX, final int closedX, + final int openedY, final int closedY, final Interpolator interp) { + initTransformer(); + mTrans = new CanvasTransformer() { + public void transformCanvas(Canvas canvas, float percentOpen) { + mTrans.transformCanvas(canvas, percentOpen); + float f = interp.getInterpolation(percentOpen); + canvas.translate((openedX - closedX) * f + closedX, + (openedY - closedY) * f + closedY); + } + }; + return mTrans; + } + + public CanvasTransformer concatTransformer(final CanvasTransformer t) { + initTransformer(); + mTrans = new CanvasTransformer() { + public void transformCanvas(Canvas canvas, float percentOpen) { + mTrans.transformCanvas(canvas, percentOpen); + t.transformCanvas(canvas, percentOpen); + } + }; + return mTrans; + } + +} diff --git a/android/src/com/slidingmenu/lib/CustomViewAbove.java b/android/src/com/slidingmenu/lib/CustomViewAbove.java new file mode 100755 index 0000000..3c03def --- /dev/null +++ b/android/src/com/slidingmenu/lib/CustomViewAbove.java @@ -0,0 +1,1011 @@ +package com.slidingmenu.lib; + +import java.util.ArrayList; +import java.util.List; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.os.Build; +import android.support.v4.view.KeyEventCompat; +import android.support.v4.view.MotionEventCompat; +import android.support.v4.view.VelocityTrackerCompat; +import android.support.v4.view.ViewCompat; +import android.support.v4.view.ViewConfigurationCompat; +import android.util.AttributeSet; +import android.util.Log; +import android.view.FocusFinder; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.SoundEffectConstants; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.animation.Interpolator; +import android.widget.Scroller; + +import com.slidingmenu.lib.SlidingMenu.OnClosedListener; +import com.slidingmenu.lib.SlidingMenu.OnOpenedListener; + +public class CustomViewAbove extends ViewGroup { + + private static final String TAG = "CustomViewAbove"; + private static final boolean DEBUG = false; + + private static final boolean USE_CACHE = false; + + private static final int MAX_SETTLE_DURATION = 600; // ms + private static final int MIN_DISTANCE_FOR_FLING = 25; // dips + + private static final Interpolator sInterpolator = new Interpolator() { + public float getInterpolation(float t) { + t -= 1.0f; + return t * t * t * t * t + 1.0f; + } + }; + + private View mContent; + + private int mCurItem; + private Scroller mScroller; + + private boolean mScrollingCacheEnabled; + + private boolean mScrolling; + + private boolean mIsBeingDragged; + private boolean mIsUnableToDrag; + private int mTouchSlop; + private float mInitialMotionX; + /** + * Position of the last motion event. + */ + private float mLastMotionX; + private float mLastMotionY; + /** + * ID of the active pointer. This is used to retain consistency during + * drags/flings if multiple pointers are used. + */ + protected int mActivePointerId = INVALID_POINTER; + /** + * Sentinel value for no current active pointer. + * Used by {@link #mActivePointerId}. + */ + private static final int INVALID_POINTER = -1; + + /** + * Determines speed during touch scrolling + */ + protected VelocityTracker mVelocityTracker; + private int mMinimumVelocity; + protected int mMaximumVelocity; + private int mFlingDistance; + + private CustomViewBehind mViewBehind; + // private int mMode; + private boolean mEnabled = true; + + private OnPageChangeListener mOnPageChangeListener; + private OnPageChangeListener mInternalPageChangeListener; + + // private OnCloseListener mCloseListener; + // private OnOpenListener mOpenListener; + private OnClosedListener mClosedListener; + private OnOpenedListener mOpenedListener; + + private List mIgnoredViews = new ArrayList(); + + // private int mScrollState = SCROLL_STATE_IDLE; + + /** + * Callback interface for responding to changing state of the selected page. + */ + public interface OnPageChangeListener { + + /** + * This method will be invoked when the current page is scrolled, either as part + * of a programmatically initiated smooth scroll or a user initiated touch scroll. + * + * @param position Position index of the first page currently being displayed. + * Page position+1 will be visible if positionOffset is nonzero. + * @param positionOffset Value from [0, 1) indicating the offset from the page at position. + * @param positionOffsetPixels Value in pixels indicating the offset from position. + */ + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels); + + /** + * This method will be invoked when a new page becomes selected. Animation is not + * necessarily complete. + * + * @param position Position index of the new selected page. + */ + public void onPageSelected(int position); + + } + + /** + * Simple implementation of the {@link OnPageChangeListener} interface with stub + * implementations of each method. Extend this if you do not intend to override + * every method of {@link OnPageChangeListener}. + */ + public static class SimpleOnPageChangeListener implements OnPageChangeListener { + + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + // This space for rent + } + + public void onPageSelected(int position) { + // This space for rent + } + + public void onPageScrollStateChanged(int state) { + // This space for rent + } + + } + + public CustomViewAbove(Context context) { + this(context, null); + } + + public CustomViewAbove(Context context, AttributeSet attrs) { + super(context, attrs); + initCustomViewAbove(); + } + + void initCustomViewAbove() { + setWillNotDraw(false); + setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); + setFocusable(true); + final Context context = getContext(); + mScroller = new Scroller(context, sInterpolator); + final ViewConfiguration configuration = ViewConfiguration.get(context); + mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); + mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); + mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); + setInternalPageChangeListener(new SimpleOnPageChangeListener() { + public void onPageSelected(int position) { + if (mViewBehind != null) { + switch (position) { + case 0: + case 2: + mViewBehind.setChildrenEnabled(true); + break; + case 1: + mViewBehind.setChildrenEnabled(false); + break; + } + } + } + }); + + final float density = context.getResources().getDisplayMetrics().density; + mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density); + } + + /** + * Set the currently selected page. If the CustomViewPager has already been through its first + * layout there will be a smooth animated transition between the current item and the + * specified item. + * + * @param item Item index to select + */ + public void setCurrentItem(int item) { + setCurrentItemInternal(item, true, false); + } + + /** + * Set the currently selected page. + * + * @param item Item index to select + * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately + */ + public void setCurrentItem(int item, boolean smoothScroll) { + setCurrentItemInternal(item, smoothScroll, false); + } + + public int getCurrentItem() { + return mCurItem; + } + + void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) { + setCurrentItemInternal(item, smoothScroll, always, 0); + } + + void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) { + if (!always && mCurItem == item) { + setScrollingCacheEnabled(false); + return; + } + + item = mViewBehind.getMenuPage(item); + + final boolean dispatchSelected = mCurItem != item; + mCurItem = item; + final int destX = getDestScrollX(mCurItem); + if (dispatchSelected && mOnPageChangeListener != null) { + mOnPageChangeListener.onPageSelected(item); + } + if (dispatchSelected && mInternalPageChangeListener != null) { + mInternalPageChangeListener.onPageSelected(item); + } + if (smoothScroll) { + smoothScrollTo(destX, 0, velocity); + } else { + completeScroll(); + scrollTo(destX, 0); + } + } + + /** + * Set a listener that will be invoked whenever the page changes or is incrementally + * scrolled. See {@link OnPageChangeListener}. + * + * @param listener Listener to set + */ + public void setOnPageChangeListener(OnPageChangeListener listener) { + mOnPageChangeListener = listener; + } + /* + public void setOnOpenListener(OnOpenListener l) { + mOpenListener = l; + } + + public void setOnCloseListener(OnCloseListener l) { + mCloseListener = l; + } + */ + public void setOnOpenedListener(OnOpenedListener l) { + mOpenedListener = l; + } + + public void setOnClosedListener(OnClosedListener l) { + mClosedListener = l; + } + + /** + * Set a separate OnPageChangeListener for internal use by the support library. + * + * @param listener Listener to set + * @return The old listener that was set, if any. + */ + OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) { + OnPageChangeListener oldListener = mInternalPageChangeListener; + mInternalPageChangeListener = listener; + return oldListener; + } + + public void addIgnoredView(View v) { + if (!mIgnoredViews.contains(v)) { + mIgnoredViews.add(v); + } + } + + public void removeIgnoredView(View v) { + mIgnoredViews.remove(v); + } + + public void clearIgnoredViews() { + mIgnoredViews.clear(); + } + + // We want the duration of the page snap animation to be influenced by the distance that + // the screen has to travel, however, we don't want this duration to be effected in a + // purely linear fashion. Instead, we use this method to moderate the effect that the distance + // of travel has on the overall snap duration. + float distanceInfluenceForSnapDuration(float f) { + f -= 0.5f; // center the values about 0. + f *= 0.3f * Math.PI / 2.0f; + return (float) Math.sin(f); + } + + public int getDestScrollX(int page) { + switch (page) { + case 0: + case 2: + return mViewBehind.getMenuLeft(mContent, page); + case 1: + return mContent.getLeft(); + } + return 0; + } + + private int getLeftBound() { + return mViewBehind.getAbsLeftBound(mContent); + } + + private int getRightBound() { + return mViewBehind.getAbsRightBound(mContent); + } + + public int getContentLeft() { + return mContent.getLeft() + mContent.getPaddingLeft(); + } + + public boolean isMenuOpen() { + return mCurItem == 0 || mCurItem == 2; + } + + private boolean isInIgnoredView(MotionEvent ev) { + Rect rect = new Rect(); + for (View v : mIgnoredViews) { + v.getHitRect(rect); + if (rect.contains((int)ev.getX(), (int)ev.getY())) return true; + } + return false; + } + + public int getBehindWidth() { + if (mViewBehind == null) { + return 0; + } else { + return mViewBehind.getBehindWidth(); + } + } + + public int getChildWidth(int i) { + switch (i) { + case 0: + return getBehindWidth(); + case 1: + return mContent.getWidth(); + default: + return 0; + } + } + + public boolean isSlidingEnabled() { + return mEnabled; + } + + public void setSlidingEnabled(boolean b) { + mEnabled = b; + } + + /** + * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. + * + * @param x the number of pixels to scroll by on the X axis + * @param y the number of pixels to scroll by on the Y axis + */ + void smoothScrollTo(int x, int y) { + smoothScrollTo(x, y, 0); + } + + /** + * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. + * + * @param x the number of pixels to scroll by on the X axis + * @param y the number of pixels to scroll by on the Y axis + * @param velocity the velocity associated with a fling, if applicable. (0 otherwise) + */ + void smoothScrollTo(int x, int y, int velocity) { + if (getChildCount() == 0) { + // Nothing to do. + setScrollingCacheEnabled(false); + return; + } + int sx = getScrollX(); + int sy = getScrollY(); + int dx = x - sx; + int dy = y - sy; + if (dx == 0 && dy == 0) { + completeScroll(); + if (isMenuOpen()) { + if (mOpenedListener != null) + mOpenedListener.onOpened(); + } else { + if (mClosedListener != null) + mClosedListener.onClosed(); + } + return; + } + + setScrollingCacheEnabled(true); + mScrolling = true; + + final int width = getBehindWidth(); + final int halfWidth = width / 2; + final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width); + final float distance = halfWidth + halfWidth * + distanceInfluenceForSnapDuration(distanceRatio); + + int duration = 0; + velocity = Math.abs(velocity); + if (velocity > 0) { + duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); + } else { + final float pageDelta = (float) Math.abs(dx) / width; + duration = (int) ((pageDelta + 1) * 100); + duration = MAX_SETTLE_DURATION; + } + duration = Math.min(duration, MAX_SETTLE_DURATION); + + mScroller.startScroll(sx, sy, dx, dy, duration); + invalidate(); + } + + public void setContent(View v) { + if (mContent != null) + this.removeView(mContent); + mContent = v; + if(v != null){ + addView(mContent); + } + } + + public View getContent() { + return mContent; + } + + public void setCustomViewBehind(CustomViewBehind cvb) { + mViewBehind = cvb; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + + int width = getDefaultSize(0, widthMeasureSpec); + int height = getDefaultSize(0, heightMeasureSpec); + setMeasuredDimension(width, height); + + final int contentWidth = getChildMeasureSpec(widthMeasureSpec, 0, width); + final int contentHeight = getChildMeasureSpec(heightMeasureSpec, 0, height); + mContent.measure(contentWidth, contentHeight); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + // Make sure scroll position is set correctly. + if (w != oldw) { + // [ChrisJ] - This fixes the onConfiguration change for orientation issue.. + // maybe worth having a look why the recomputeScroll pos is screwing + // up? + completeScroll(); + scrollTo(getDestScrollX(mCurItem), getScrollY()); + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + final int width = r - l; + final int height = b - t; + mContent.layout(0, 0, width, height); + } + + public void setAboveOffset(int i) { + // RelativeLayout.LayoutParams params = ((RelativeLayout.LayoutParams)mContent.getLayoutParams()); + // params.setMargins(i, params.topMargin, params.rightMargin, params.bottomMargin); + mContent.setPadding(i, mContent.getPaddingTop(), + mContent.getPaddingRight(), mContent.getPaddingBottom()); + } + + + @Override + public void computeScroll() { + if (!mScroller.isFinished()) { + if (mScroller.computeScrollOffset()) { + int oldX = getScrollX(); + int oldY = getScrollY(); + int x = mScroller.getCurrX(); + int y = mScroller.getCurrY(); + + if (oldX != x || oldY != y) { + scrollTo(x, y); + pageScrolled(x); + } + + // Keep on drawing until the animation has finished. + invalidate(); + return; + } + } + + // Done with scroll, clean up state. + completeScroll(); + } + + private void pageScrolled(int xpos) { + final int widthWithMargin = getWidth(); + final int position = xpos / widthWithMargin; + final int offsetPixels = xpos % widthWithMargin; + final float offset = (float) offsetPixels / widthWithMargin; + + onPageScrolled(position, offset, offsetPixels); + } + + /** + * This method will be invoked when the current page is scrolled, either as part + * of a programmatically initiated smooth scroll or a user initiated touch scroll. + * If you override this method you must call through to the superclass implementation + * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled + * returns. + * + * @param position Position index of the first page currently being displayed. + * Page position+1 will be visible if positionOffset is nonzero. + * @param offset Value from [0, 1) indicating the offset from the page at position. + * @param offsetPixels Value in pixels indicating the offset from position. + */ + protected void onPageScrolled(int position, float offset, int offsetPixels) { + if (mOnPageChangeListener != null) { + mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels); + } + if (mInternalPageChangeListener != null) { + mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels); + } + } + + private void completeScroll() { + boolean needPopulate = mScrolling; + if (needPopulate) { + // Done with scroll, no longer want to cache view drawing. + setScrollingCacheEnabled(false); + mScroller.abortAnimation(); + int oldX = getScrollX(); + int oldY = getScrollY(); + int x = mScroller.getCurrX(); + int y = mScroller.getCurrY(); + if (oldX != x || oldY != y) { + scrollTo(x, y); + } + if (isMenuOpen()) { + if (mOpenedListener != null) + mOpenedListener.onOpened(); + } else { + if (mClosedListener != null) + mClosedListener.onClosed(); + } + } + mScrolling = false; + } + + protected int mTouchMode = SlidingMenu.TOUCHMODE_MARGIN; + + public void setTouchMode(int i) { + mTouchMode = i; + } + + public int getTouchMode() { + return mTouchMode; + } + + private boolean thisTouchAllowed(MotionEvent ev) { + int x = (int) (ev.getX() + mScrollX); + if (isMenuOpen()) { + return mViewBehind.menuOpenTouchAllowed(mContent, mCurItem, x); + } else { + switch (mTouchMode) { + case SlidingMenu.TOUCHMODE_FULLSCREEN: + return !isInIgnoredView(ev); + case SlidingMenu.TOUCHMODE_NONE: + return false; + case SlidingMenu.TOUCHMODE_MARGIN: + return mViewBehind.marginTouchAllowed(mContent, x); + } + } + return false; + } + + private boolean thisSlideAllowed(float dx) { + boolean allowed = false; + if (isMenuOpen()) { + allowed = mViewBehind.menuOpenSlideAllowed(dx); + } else { + allowed = mViewBehind.menuClosedSlideAllowed(dx); + } + if (DEBUG) + Log.v(TAG, "this slide allowed " + allowed + " dx: " + dx); + return allowed; + } + + private int getPointerIndex(MotionEvent ev, int id) { + int activePointerIndex = MotionEventCompat.findPointerIndex(ev, id); + if (activePointerIndex == -1) + mActivePointerId = INVALID_POINTER; + return activePointerIndex; + } + + private boolean mQuickReturn = false; + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + + if (!mEnabled) + return false; + + final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; + + if (DEBUG) + if (action == MotionEvent.ACTION_DOWN) + Log.v(TAG, "Received ACTION_DOWN"); + + if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP + || (action != MotionEvent.ACTION_DOWN && mIsUnableToDrag)) { + endDrag(); + return false; + } + + switch (action) { + case MotionEvent.ACTION_MOVE: + determineDrag(ev); + break; + case MotionEvent.ACTION_DOWN: + int index = MotionEventCompat.getActionIndex(ev); + mActivePointerId = MotionEventCompat.getPointerId(ev, index); + if (mActivePointerId == INVALID_POINTER) + break; + mLastMotionX = mInitialMotionX = MotionEventCompat.getX(ev, index); + mLastMotionY = MotionEventCompat.getY(ev, index); + if (thisTouchAllowed(ev)) { + mIsBeingDragged = false; + mIsUnableToDrag = false; + if (isMenuOpen() && mViewBehind.menuTouchInQuickReturn(mContent, mCurItem, ev.getX() + mScrollX)) { + mQuickReturn = true; + } + } else { + mIsUnableToDrag = true; + } + break; + case MotionEventCompat.ACTION_POINTER_UP: + onSecondaryPointerUp(ev); + break; + } + + if (!mIsBeingDragged) { + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(ev); + } + return mIsBeingDragged || mQuickReturn; + } + + + @Override + public boolean onTouchEvent(MotionEvent ev) { + + if (!mEnabled) + return false; + + if (!mIsBeingDragged && !thisTouchAllowed(ev)) + return false; + + // if (!mIsBeingDragged && !mQuickReturn) + // return false; + + final int action = ev.getAction(); + + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(ev); + + switch (action & MotionEventCompat.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + /* + * If being flinged and user touches, stop the fling. isFinished + * will be false if being flinged. + */ + completeScroll(); + + // Remember where the motion event started + int index = MotionEventCompat.getActionIndex(ev); + mActivePointerId = MotionEventCompat.getPointerId(ev, index); + mLastMotionX = mInitialMotionX = ev.getX(); + break; + case MotionEvent.ACTION_MOVE: + if (!mIsBeingDragged) { + determineDrag(ev); + if (mIsUnableToDrag) + return false; + } + if (mIsBeingDragged) { + // Scroll to follow the motion event + final int activePointerIndex = getPointerIndex(ev, mActivePointerId); + if (mActivePointerId == INVALID_POINTER) + break; + final float x = MotionEventCompat.getX(ev, activePointerIndex); + final float deltaX = mLastMotionX - x; + mLastMotionX = x; + float oldScrollX = getScrollX(); + float scrollX = oldScrollX + deltaX; + final float leftBound = getLeftBound(); + final float rightBound = getRightBound(); + if (scrollX < leftBound) { + scrollX = leftBound; + } else if (scrollX > rightBound) { + scrollX = rightBound; + } + // Don't lose the rounded component + mLastMotionX += scrollX - (int) scrollX; + scrollTo((int) scrollX, getScrollY()); + pageScrolled((int) scrollX); + } + break; + case MotionEvent.ACTION_UP: + if (mIsBeingDragged) { + final VelocityTracker velocityTracker = mVelocityTracker; + velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + int initialVelocity = (int) VelocityTrackerCompat.getXVelocity( + velocityTracker, mActivePointerId); + final int scrollX = getScrollX(); + // final int widthWithMargin = getWidth(); + // final float pageOffset = (float) (scrollX % widthWithMargin) / widthWithMargin; + // TODO test this. should get better flinging behavior + final float pageOffset = (float) (scrollX - getDestScrollX(mCurItem)) / getBehindWidth(); + final int activePointerIndex = getPointerIndex(ev, mActivePointerId); + if (mActivePointerId != INVALID_POINTER) { + final float x = MotionEventCompat.getX(ev, activePointerIndex); + final int totalDelta = (int) (x - mInitialMotionX); + int nextPage = determineTargetPage(pageOffset, initialVelocity, totalDelta); + setCurrentItemInternal(nextPage, true, true, initialVelocity); + } else { + setCurrentItemInternal(mCurItem, true, true, initialVelocity); + } + mActivePointerId = INVALID_POINTER; + endDrag(); + } else if (mQuickReturn && mViewBehind.menuTouchInQuickReturn(mContent, mCurItem, ev.getX() + mScrollX)) { + // close the menu + setCurrentItem(1); + endDrag(); + } + break; + case MotionEvent.ACTION_CANCEL: + if (mIsBeingDragged) { + setCurrentItemInternal(mCurItem, true, true); + mActivePointerId = INVALID_POINTER; + endDrag(); + } + break; + case MotionEventCompat.ACTION_POINTER_DOWN: { + final int indexx = MotionEventCompat.getActionIndex(ev); + mLastMotionX = MotionEventCompat.getX(ev, indexx); + mActivePointerId = MotionEventCompat.getPointerId(ev, indexx); + break; + } + case MotionEventCompat.ACTION_POINTER_UP: + onSecondaryPointerUp(ev); + int pointerIndex = getPointerIndex(ev, mActivePointerId); + if (mActivePointerId == INVALID_POINTER) + break; + mLastMotionX = MotionEventCompat.getX(ev, pointerIndex); + break; + } + return true; + } + + private void determineDrag(MotionEvent ev) { + final int activePointerId = mActivePointerId; + final int pointerIndex = getPointerIndex(ev, activePointerId); + if (activePointerId == INVALID_POINTER) + return; + final float x = MotionEventCompat.getX(ev, pointerIndex); + final float dx = x - mLastMotionX; + final float xDiff = Math.abs(dx); + final float y = MotionEventCompat.getY(ev, pointerIndex); + final float dy = y - mLastMotionY; + final float yDiff = Math.abs(dy); + if (xDiff > (isMenuOpen()?mTouchSlop/2:mTouchSlop) && xDiff > yDiff && thisSlideAllowed(dx)) { + startDrag(); + mLastMotionX = x; + mLastMotionY = y; + setScrollingCacheEnabled(true); + // TODO add back in touch slop check + } else if (xDiff > mTouchSlop) { + mIsUnableToDrag = true; + } + } + + @Override + public void scrollTo(int x, int y) { + super.scrollTo(x, y); + mScrollX = x; + mViewBehind.scrollBehindTo(mContent, x, y); + + // TODO NAPP: fix this + //((SlidingMenu)getParent()).manageLayers(getPercentOpen()); + } + + private int determineTargetPage(float pageOffset, int velocity, int deltaX) { + int targetPage = mCurItem; + if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) { + if (velocity > 0 && deltaX > 0) { + targetPage -= 1; + } else if (velocity < 0 && deltaX < 0){ + targetPage += 1; + } + } else { + targetPage = (int) Math.round(mCurItem + pageOffset); + } + return targetPage; + } + + protected float getPercentOpen() { + return Math.abs(mScrollX-mContent.getLeft()) / getBehindWidth(); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + // Draw the margin drawable if needed. + mViewBehind.drawShadow(mContent, canvas); + mViewBehind.drawFade(mContent, canvas, getPercentOpen()); + mViewBehind.drawSelector(mContent, canvas, getPercentOpen()); + } + + // variables for drawing + private float mScrollX = 0.0f; + + private void onSecondaryPointerUp(MotionEvent ev) { + if (DEBUG) Log.v(TAG, "onSecondaryPointerUp called"); + final int pointerIndex = MotionEventCompat.getActionIndex(ev); + final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); + if (pointerId == mActivePointerId) { + // This was our active pointer going up. Choose a new + // active pointer and adjust accordingly. + final int newPointerIndex = pointerIndex == 0 ? 1 : 0; + mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex); + mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); + if (mVelocityTracker != null) { + mVelocityTracker.clear(); + } + } + } + + private void startDrag() { + mIsBeingDragged = true; + mQuickReturn = false; + } + + private void endDrag() { + mQuickReturn = false; + mIsBeingDragged = false; + mIsUnableToDrag = false; + mActivePointerId = INVALID_POINTER; + + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + } + + private void setScrollingCacheEnabled(boolean enabled) { + if (mScrollingCacheEnabled != enabled) { + mScrollingCacheEnabled = enabled; + if (USE_CACHE) { + final int size = getChildCount(); + for (int i = 0; i < size; ++i) { + final View child = getChildAt(i); + if (child.getVisibility() != GONE) { + child.setDrawingCacheEnabled(enabled); + } + } + } + } + } + + /** + * Tests scrollability within child views of v given a delta of dx. + * + * @param v View to test for horizontal scrollability + * @param checkV Whether the view v passed should itself be checked for scrollability (true), + * or just its children (false). + * @param dx Delta scrolled in pixels + * @param x X coordinate of the active touch point + * @param y Y coordinate of the active touch point + * @return true if child views of v can be scrolled by delta of dx. + */ + protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { + if (v instanceof ViewGroup) { + final ViewGroup group = (ViewGroup) v; + final int scrollX = v.getScrollX(); + final int scrollY = v.getScrollY(); + final int count = group.getChildCount(); + // Count backwards - let topmost views consume scroll distance first. + for (int i = count - 1; i >= 0; i--) { + final View child = group.getChildAt(i); + if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && + y + scrollY >= child.getTop() && y + scrollY < child.getBottom() && + canScroll(child, true, dx, x + scrollX - child.getLeft(), + y + scrollY - child.getTop())) { + return true; + } + } + } + + return checkV && ViewCompat.canScrollHorizontally(v, -dx); + } + + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + // Let the focused view and/or our descendants get the key first + return super.dispatchKeyEvent(event) || executeKeyEvent(event); + } + + /** + * You can call this function yourself to have the scroll view perform + * scrolling from a key event, just as if the event had been dispatched to + * it by the view hierarchy. + * + * @param event The key event to execute. + * @return Return true if the event was handled, else false. + */ + public boolean executeKeyEvent(KeyEvent event) { + boolean handled = false; + if (event.getAction() == KeyEvent.ACTION_DOWN) { + switch (event.getKeyCode()) { + case KeyEvent.KEYCODE_DPAD_LEFT: + handled = arrowScroll(FOCUS_LEFT); + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + handled = arrowScroll(FOCUS_RIGHT); + break; + case KeyEvent.KEYCODE_TAB: + if (Build.VERSION.SDK_INT >= 11) { + // The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD + // before Android 3.0. Ignore the tab key on those devices. + if (KeyEventCompat.hasNoModifiers(event)) { + handled = arrowScroll(FOCUS_FORWARD); + } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) { + handled = arrowScroll(FOCUS_BACKWARD); + } + } + break; + } + } + return handled; + } + + public boolean arrowScroll(int direction) { + View currentFocused = findFocus(); + if (currentFocused == this) currentFocused = null; + + boolean handled = false; + + View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, + direction); + if (nextFocused != null && nextFocused != currentFocused) { + if (direction == View.FOCUS_LEFT) { + handled = nextFocused.requestFocus(); + } else if (direction == View.FOCUS_RIGHT) { + // If there is nothing to the right, or this is causing us to + // jump to the left, then what we really want to do is page right. + if (currentFocused != null && nextFocused.getLeft() <= currentFocused.getLeft()) { + handled = pageRight(); + } else { + handled = nextFocused.requestFocus(); + } + } + } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) { + // Trying to move left and nothing there; try to page. + handled = pageLeft(); + } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) { + // Trying to move right and nothing there; try to page. + handled = pageRight(); + } + if (handled) { + playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); + } + return handled; + } + + boolean pageLeft() { + if (mCurItem > 0) { + setCurrentItem(mCurItem-1, true); + return true; + } + return false; + } + + boolean pageRight() { + if (mCurItem < 1) { + setCurrentItem(mCurItem+1, true); + return true; + } + return false; + } + +} diff --git a/android/src/com/slidingmenu/lib/CustomViewBehind.java b/android/src/com/slidingmenu/lib/CustomViewBehind.java new file mode 100755 index 0000000..8311240 --- /dev/null +++ b/android/src/com/slidingmenu/lib/CustomViewBehind.java @@ -0,0 +1,468 @@ +package com.slidingmenu.lib; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.Log; +import android.util.TypedValue; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; + +import com.slidingmenu.lib.SlidingMenu.CanvasTransformer; + +import dk.napp.drawer.RHelper; + +public class CustomViewBehind extends ViewGroup { + + private static final String TAG = "CustomViewBehind"; + + private static final int MARGIN_THRESHOLD = 48; // dips + private int mTouchMode = SlidingMenu.TOUCHMODE_MARGIN; + + private CustomViewAbove mViewAbove; + + private View mContent; + private View mSecondaryContent; + private int mMarginThreshold; + private int mWidthOffset; + + private int mSecondaryWidthOffset; + + private CanvasTransformer mTransformer; + private boolean mChildrenEnabled; + + public CustomViewBehind(Context context) { + this(context, null); + } + + public CustomViewBehind(Context context, AttributeSet attrs) { + super(context, attrs); + mMarginThreshold = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + MARGIN_THRESHOLD, getResources().getDisplayMetrics()); + } + + public void setCustomViewAbove(CustomViewAbove customViewAbove) { + mViewAbove = customViewAbove; + } + + public void setCanvasTransformer(CanvasTransformer t) { + mTransformer = t; + } + + public void setWidthOffset(int i) { + mWidthOffset = i; + requestLayout(); + } + + public void setSecondaryWidthOffset(int offset) { + mSecondaryWidthOffset = offset; + requestLayout(); + } + + public void setMarginThreshold(int marginThreshold) { + mMarginThreshold = marginThreshold; + } + + public int getMarginThreshold() { + return mMarginThreshold; + } + + public int getBehindWidth() { + return mContent.getWidth(); + } + + public int getSecondaryBehindWidth() { + return mSecondaryContent.getWidth(); + } + + public void setContent(View v) { + if (mContent != null) + removeView(mContent); + mContent = v; + if(v != null){ + if (mContent.getParent() != null) { + ((ViewGroup) mContent.getParent()).removeView(mContent); + } + addView(mContent); + } + } + + public View getContent() { + return mContent; + } + + /** + * Sets the secondary (right) menu for use when setMode is called with SlidingMenu.LEFT_RIGHT. + * @param v the right menu + */ + public void setSecondaryContent(View v) { + if (mSecondaryContent != null) + removeView(mSecondaryContent); + mSecondaryContent = v; + if(v != null){ + if (mSecondaryContent.getParent() != null) { + ((ViewGroup) mSecondaryContent.getParent()).removeView(mSecondaryContent); + } + addView(mSecondaryContent); + } + } + + public View getSecondaryContent() { + return mSecondaryContent; + } + + public void setChildrenEnabled(boolean enabled) { + mChildrenEnabled = enabled; + } + + @Override + public void scrollTo(int x, int y) { + super.scrollTo(x, y); + if (mTransformer != null) + invalidate(); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent e) { + return !mChildrenEnabled; + } + + @Override + public boolean onTouchEvent(MotionEvent e) { + return !mChildrenEnabled; + } + + @Override + protected void dispatchDraw(Canvas canvas) { + if (mTransformer != null) { + canvas.save(); + mTransformer.transformCanvas(canvas, mViewAbove.getPercentOpen()); + super.dispatchDraw(canvas); + canvas.restore(); + } else + super.dispatchDraw(canvas); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + final int width = r - l; + final int height = b - t; + mContent.layout(0, 0, width-mWidthOffset, height); + if (mSecondaryContent != null) + mSecondaryContent.layout(0, 0, width - mSecondaryWidthOffset, height); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = getDefaultSize(0, widthMeasureSpec); + int height = getDefaultSize(0, heightMeasureSpec); + setMeasuredDimension(width, height); + final int contentWidth = getChildMeasureSpec(widthMeasureSpec, 0, width + - mWidthOffset); + final int contentHeight = getChildMeasureSpec(heightMeasureSpec, 0, + height); + mContent.measure(contentWidth, contentHeight); + if (mSecondaryContent != null) { + final int contentSecondaryWidth = getChildMeasureSpec( + widthMeasureSpec, 0, width - mSecondaryWidthOffset); + mSecondaryContent.measure(contentSecondaryWidth, contentHeight); + } + } + + private int mMode; + private boolean mFadeEnabled; + private final Paint mFadePaint = new Paint(); + private float mScrollScale; + private Drawable mShadowDrawable; + private Drawable mSecondaryShadowDrawable; + private int mShadowWidth; + private float mFadeDegree; + + public void setMode(int mode) { + if (mode == SlidingMenu.LEFT || mode == SlidingMenu.RIGHT) { + if (mContent != null) + mContent.setVisibility(View.VISIBLE); + if (mSecondaryContent != null) + mSecondaryContent.setVisibility(View.INVISIBLE); + } + mMode = mode; + } + + public int getMode() { + return mMode; + } + + public void setScrollScale(float scrollScale) { + mScrollScale = scrollScale; + } + + public float getScrollScale() { + return mScrollScale; + } + + public void setShadowDrawable(Drawable shadow) { + mShadowDrawable = shadow; + invalidate(); + } + + public void setSecondaryShadowDrawable(Drawable shadow) { + mSecondaryShadowDrawable = shadow; + invalidate(); + } + + public void setShadowWidth(int width) { + mShadowWidth = width; + invalidate(); + } + + public void setFadeEnabled(boolean b) { + mFadeEnabled = b; + } + + public void setFadeDegree(float degree) { + if (degree > 1.0f || degree < 0.0f) + throw new IllegalStateException("The BehindFadeDegree must be between 0.0f and 1.0f"); + mFadeDegree = degree; + } + + public int getMenuPage(int page) { + page = (page > 1) ? 2 : ((page < 1) ? 0 : page); + if (mMode == SlidingMenu.LEFT && page > 1) { + return 0; + } else if (mMode == SlidingMenu.RIGHT && page < 1) { + return 2; + } else { + return page; + } + } + + public void scrollBehindTo(View content, int x, int y) { + int vis = View.VISIBLE; + if (mMode == SlidingMenu.LEFT) { + if (x >= content.getLeft()) vis = View.INVISIBLE; + scrollTo((int)((x + getBehindWidth())*mScrollScale), y); + } else if (mMode == SlidingMenu.RIGHT) { + if (x <= content.getLeft()) vis = View.INVISIBLE; + scrollTo((int)(getSecondaryBehindWidth() - getWidth() + + (x-getSecondaryBehindWidth())*mScrollScale), y); + } else if (mMode == SlidingMenu.LEFT_RIGHT) { + mContent.setVisibility(x >= content.getLeft() ? View.INVISIBLE : View.VISIBLE); + mSecondaryContent.setVisibility(x <= content.getLeft() ? View.INVISIBLE : View.VISIBLE); + vis = x == 0 ? View.INVISIBLE : View.VISIBLE; + if (x <= content.getLeft()) { + scrollTo((int)((x + getBehindWidth())*mScrollScale), y); + } else { + scrollTo((int)(getSecondaryBehindWidth() - getWidth() + + (x-getSecondaryBehindWidth())*mScrollScale), y); + } + } + if (vis == View.INVISIBLE) + Log.v(TAG, "behind INVISIBLE"); + setVisibility(vis); + } + + public int getMenuLeft(View content, int page) { + if (mMode == SlidingMenu.LEFT) { + switch (page) { + case 0: + return content.getLeft() - getBehindWidth(); + case 2: + return content.getLeft(); + } + } else if (mMode == SlidingMenu.RIGHT) { + switch (page) { + case 0: + return content.getLeft(); + case 2: + return content.getLeft() + getSecondaryBehindWidth(); + } + } else if (mMode == SlidingMenu.LEFT_RIGHT) { + switch (page) { + case 0: + return content.getLeft() - getBehindWidth(); + case 2: + return content.getLeft() + getSecondaryBehindWidth(); + } + } + return content.getLeft(); + } + + public int getAbsLeftBound(View content) { + if (mMode == SlidingMenu.LEFT || mMode == SlidingMenu.LEFT_RIGHT) { + return content.getLeft() - getBehindWidth(); + } else if (mMode == SlidingMenu.RIGHT) { + return content.getLeft(); + } + return 0; + } + + public int getAbsRightBound(View content) { + if (mMode == SlidingMenu.LEFT) { + return content.getLeft(); + } else if (mMode == SlidingMenu.RIGHT || mMode == SlidingMenu.LEFT_RIGHT) { + return content.getLeft() + getBehindWidth(); + } + return 0; + } + + public boolean marginTouchAllowed(View content, int x) { + int left = content.getLeft(); + int right = content.getRight(); + if (mMode == SlidingMenu.LEFT) { + return (x >= left && x <= mMarginThreshold + left); + } else if (mMode == SlidingMenu.RIGHT) { + return (x <= right && x >= right - mMarginThreshold); + } else if (mMode == SlidingMenu.LEFT_RIGHT) { + return (x >= left && x <= mMarginThreshold + left) || + (x <= right && x >= right - mMarginThreshold); + } + return false; + } + + public void setTouchMode(int i) { + mTouchMode = i; + } + + public boolean menuOpenTouchAllowed(View content, int currPage, float x) { + switch (mTouchMode) { + case SlidingMenu.TOUCHMODE_FULLSCREEN: + return true; + case SlidingMenu.TOUCHMODE_MARGIN: + return menuTouchInQuickReturn(content, currPage, x); + } + return false; + } + + public boolean menuTouchInQuickReturn(View content, int currPage, float x) { + if (mMode == SlidingMenu.LEFT || (mMode == SlidingMenu.LEFT_RIGHT && currPage == 0)) { + return x >= content.getLeft(); + } else if (mMode == SlidingMenu.RIGHT || (mMode == SlidingMenu.LEFT_RIGHT && currPage == 2)) { + return x <= content.getRight(); + } + return false; + } + + public boolean menuClosedSlideAllowed(float dx) { + if (mMode == SlidingMenu.LEFT) { + return dx > 0; + } else if (mMode == SlidingMenu.RIGHT) { + return dx < 0; + } else if (mMode == SlidingMenu.LEFT_RIGHT) { + return true; + } + return false; + } + + public boolean menuOpenSlideAllowed(float dx) { + if (mMode == SlidingMenu.LEFT) { + return dx < 0; + } else if (mMode == SlidingMenu.RIGHT) { + return dx > 0; + } else if (mMode == SlidingMenu.LEFT_RIGHT) { + return true; + } + return false; + } + + public void drawShadow(View content, Canvas canvas) { + if (mShadowDrawable == null || mShadowWidth <= 0) return; + int left = 0; + if (mMode == SlidingMenu.LEFT) { + left = content.getLeft() - mShadowWidth; + } else if (mMode == SlidingMenu.RIGHT) { + left = content.getRight(); + } else if (mMode == SlidingMenu.LEFT_RIGHT) { + if (mSecondaryShadowDrawable != null) { + left = content.getRight(); + mSecondaryShadowDrawable.setBounds(left, 0, left + mShadowWidth, getHeight()); + mSecondaryShadowDrawable.draw(canvas); + } + left = content.getLeft() - mShadowWidth; + } + mShadowDrawable.setBounds(left, 0, left + mShadowWidth, getHeight()); + mShadowDrawable.draw(canvas); + } + + public void drawFade(View content, Canvas canvas, float openPercent) { + if (!mFadeEnabled) return; + final int alpha = (int) (mFadeDegree * 255 * Math.abs(1-openPercent)); + mFadePaint.setColor(Color.argb(alpha, 0, 0, 0)); + int left = 0; + int right = 0; + if (mMode == SlidingMenu.LEFT) { + left = content.getLeft() - getBehindWidth(); + right = content.getLeft(); + } else if (mMode == SlidingMenu.RIGHT) { + left = content.getRight(); + right = content.getRight() + getSecondaryBehindWidth(); + } else if (mMode == SlidingMenu.LEFT_RIGHT) { + left = content.getLeft() - getSecondaryBehindWidth(); + right = content.getLeft(); + canvas.drawRect(left, 0, right, getHeight(), mFadePaint); + left = content.getRight(); + right = content.getRight() + getSecondaryBehindWidth(); + } + canvas.drawRect(left, 0, right, getHeight(), mFadePaint); + } + + private boolean mSelectorEnabled = true; + private Bitmap mSelectorDrawable; + private View mSelectedView; + + public void drawSelector(View content, Canvas canvas, float openPercent) { + if (!mSelectorEnabled) return; + if (mSelectorDrawable != null && mSelectedView != null) { + String tag = (String) mSelectedView.getTag(RHelper.getId("selected_view")); + if (tag.equals(TAG+"SelectedView")) { + canvas.save(); + int left, right, offset; + offset = (int) (mSelectorDrawable.getWidth() * openPercent); + if (mMode == SlidingMenu.LEFT) { + right = content.getLeft(); + left = right - offset; + canvas.clipRect(left, 0, right, getHeight()); + canvas.drawBitmap(mSelectorDrawable, left, getSelectorTop(), null); + } else if (mMode == SlidingMenu.RIGHT) { + left = content.getRight(); + right = left + offset; + canvas.clipRect(left, 0, right, getHeight()); + canvas.drawBitmap(mSelectorDrawable, right - mSelectorDrawable.getWidth(), getSelectorTop(), null); + } + canvas.restore(); + } + } + } + + public void setSelectorEnabled(boolean b) { + mSelectorEnabled = b; + } + + public void setSelectedView(View v) { + if (mSelectedView != null) { + mSelectedView.setTag(RHelper.getId("selected_view"), null); + mSelectedView = null; + } + if (v != null && v.getParent() != null) { + mSelectedView = v; + mSelectedView.setTag(RHelper.getId("selected_view"), TAG+"SelectedView"); + invalidate(); + } + } + + private int getSelectorTop() { + int y = mSelectedView.getTop(); + y += (mSelectedView.getHeight() - mSelectorDrawable.getHeight()) / 2; + return y; + } + + public void setSelectorBitmap(Bitmap b) { + mSelectorDrawable = b; + refreshDrawableState(); + } + +} diff --git a/android/src/com/slidingmenu/lib/SlidingMenu.java b/android/src/com/slidingmenu/lib/SlidingMenu.java new file mode 100755 index 0000000..0deac44 --- /dev/null +++ b/android/src/com/slidingmenu/lib/SlidingMenu.java @@ -0,0 +1,1089 @@ +package com.slidingmenu.lib; + +import java.lang.reflect.Method; + + + + +//import android.annotation.SuppressLint; +//import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Display; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.RelativeLayout; + +import com.slidingmenu.lib.CustomViewAbove.OnPageChangeListener; + +public class SlidingMenu extends RelativeLayout { + + private static final String TAG = "SlidingMenu"; + + public static final int SLIDING_WINDOW = 0; + public static final int SLIDING_CONTENT = 1; + private boolean mActionbarOverlay = false; + + /** Constant value for use with setTouchModeAbove(). Allows the SlidingMenu to be opened with a swipe + * gesture on the screen's margin + */ + public static final int TOUCHMODE_MARGIN = 0; + + /** Constant value for use with setTouchModeAbove(). Allows the SlidingMenu to be opened with a swipe + * gesture anywhere on the screen + */ + public static final int TOUCHMODE_FULLSCREEN = 1; + + /** Constant value for use with setTouchModeAbove(). Denies the SlidingMenu to be opened with a swipe + * gesture + */ + public static final int TOUCHMODE_NONE = 2; + + /** Constant value for use with setMode(). Puts the menu to the left of the content. + */ + public static final int LEFT = 0; + + /** Constant value for use with setMode(). Puts the menu to the right of the content. + */ + public static final int RIGHT = 1; + + /** Constant value for use with setMode(). Puts menus to the left and right of the content. + */ + public static final int LEFT_RIGHT = 2; + + private CustomViewAbove mViewAbove; + + private CustomViewBehind mViewBehind; + + private OnOpenListener mOpenListener; + + private OnOpenListener mSecondaryOpenListner; + + private OnCloseListener mCloseListener; + + private OnScrolledListener mOnScrolledListener; + + + /** + * The listener interface for receiving onOpen events. + * The class that is interested in processing a onOpen + * event implements this interface, and the object created + * with that class is registered with a component using the + * component's addOnOpenListener method. When + * the onOpen event occurs, that object's appropriate + * method is invoked + */ + public interface OnOpenListener { + + /** + * On open. + */ + public void onOpen(int position); + } + + /** + * The listener interface for receiving onOpened events. + * The class that is interested in processing a onOpened + * event implements this interface, and the object created + * with that class is registered with a component using the + * component's addOnOpenedListener method. When + * the onOpened event occurs, that object's appropriate + * method is invoked. + * + * @see OnOpenedEvent + */ + public interface OnOpenedListener { + + /** + * On opened. + */ + public void onOpened(); + } + + /** + * The listener interface for receiving onClose events. + * The class that is interested in processing a onClose + * event implements this interface, and the object created + * with that class is registered with a component using the + * component's addOnCloseListener method. When + * the onClose event occurs, that object's appropriate + * method is invoked. + * + * @see OnCloseEvent + */ + public interface OnCloseListener { + + /** + * On close. + */ + public void onClose(); + } + + /** + * The listener interface for receiving onClosed events. + * The class that is interested in processing a onClosed + * event implements this interface, and the object created + * with that class is registered with a component using the + * component's addOnClosedListener method. When + * the onClosed event occurs, that object's appropriate + * method is invoked. + * + * @see OnClosedEvent + */ + public interface OnClosedListener { + + /** + * On closed. + */ + public void onClosed(); + } + + /** + * The listener interface for receiving onScrolled events. + * The class that is interested in processing a onScrolled + * event implements this interface, and the object created + * with that class is registered with a component using the + * component's setOnScrolledListener method. When + * the onScrolled event occurs, that object's appropriate + * method is invoked. + * + * @see onScrolledEvent + */ + public interface OnScrolledListener { + + /** + * On scrolled. + */ + public void onScrolled(int scroll); + } + + /** + * The Interface CanvasTransformer. + */ + public interface CanvasTransformer { + + /** + * Transform canvas. + * + * @param canvas the canvas + * @param percentOpen the percent open + */ + public void transformCanvas(Canvas canvas, float percentOpen); + } + + /** + * Fixes issue https://github.com/jfeinstein10/SlidingMenu/issues/680 + * Lollipop Bottom Nav Bar Overlays App When Moving Action Bar + */ + private void updateSystemUiVisibility(Activity activity){ + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){ + int flags = activity.getWindow().getAttributes().flags; + if ((flags & WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0){ + setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + } + } + } + + /** + * Instantiates a new SlidingMenu. + * + * @param context the associated Context + */ + public SlidingMenu(Context context) { + this(context, null); + updateSystemUiVisibility((Activity) context); + } + + /** + * Instantiates a new SlidingMenu and attach to Activity. + * + * @param activity the activity to attach slidingmenu + * @param slideStyle the slidingmenu style + */ + public SlidingMenu(Activity activity, int slideStyle) { + this(activity, null); + updateSystemUiVisibility((Activity) activity); + this.attachToActivity(activity, slideStyle); + } + + /** + * Instantiates a new SlidingMenu. + * + * @param context the associated Context + * @param attrs the attrs + */ + public SlidingMenu(Context context, AttributeSet attrs) { + this(context, attrs, 0); + updateSystemUiVisibility((Activity) context); + } + + /** + * Instantiates a new SlidingMenu. + * + * @param context the associated Context + * @param attrs the attrs + * @param defStyle the def style + */ + public SlidingMenu(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + updateSystemUiVisibility((Activity) context); + + LayoutParams behindParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + mViewBehind = new CustomViewBehind(context); + addView(mViewBehind, behindParams); + LayoutParams aboveParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + mViewAbove = new CustomViewAbove(context); + addView(mViewAbove, aboveParams); + // register the CustomViewBehind with the CustomViewAbove + mViewAbove.setCustomViewBehind(mViewBehind); + mViewBehind.setCustomViewAbove(mViewAbove); + mViewAbove.setOnPageChangeListener(new OnPageChangeListener() { + public static final int POSITION_OPEN = 0; + public static final int POSITION_CLOSE = 1; + public static final int POSITION_SECONDARY_OPEN = 2; + + public void onPageScrolled(int position, float positionOffset, + int positionOffsetPixels) { + if (mOnScrolledListener != null) { + mOnScrolledListener.onScrolled(-positionOffsetPixels); + } + } + /* + public void onPageSelected(int position, int fromPosition, boolean animated, int duration) { + if (position == fromPosition) return; + + + if (position == POSITION_CLOSE && mCloseListener != null) { + int leftOrRight = (fromPosition == 2)?1:0; + mCloseListener.onClose(leftOrRight, animated, duration); + } else if (position != POSITION_CLOSE && mOpenListener != null) { + int leftOrRight = (position == 2)?1:0; + mOpenListener.onOpen(leftOrRight, animated, duration); + } else if (position == POSITION_SECONDARY_OPEN && mSecondaryOpenListner != null ) { + mSecondaryOpenListner.onOpen(); + } + + }*/ + + public void onPageSelected(int position) { + if (position == POSITION_OPEN && mOpenListener != null) { + mOpenListener.onOpen(POSITION_OPEN); + } else if (position == POSITION_CLOSE && mCloseListener != null) { + mCloseListener.onClose(); + } else if (position == POSITION_SECONDARY_OPEN && mSecondaryOpenListner != null ) { + mSecondaryOpenListner.onOpen(POSITION_SECONDARY_OPEN); + } + } + }); + + setMode(LEFT); + setTouchModeAbove(TOUCHMODE_MARGIN); + setTouchModeBehind(TOUCHMODE_MARGIN); + setBehindOffset(0); + setBehindScrollScale(0.33f); + setShadowWidth(0); + setFadeEnabled(true); + setFadeDegree(0.33f); + setSelectorEnabled(false); + } + + /** + * Attaches the SlidingMenu to an entire Activity + * + * @param activity the Activity + * @param slideStyle either SLIDING_CONTENT or SLIDING_WINDOW + */ + public void attachToActivity(Activity activity, int slideStyle) { + attachToActivity(activity, slideStyle, false); + if(slideStyle == SLIDING_WINDOW && Build.VERSION.SDK_INT >= 21) { + setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + } + } + + /** + * Attaches the SlidingMenu to an entire Activity + * + * @param activity the Activity + * @param slideStyle either SLIDING_CONTENT or SLIDING_WINDOW + * @param actionbarOverlay whether or not the ActionBar is overlaid + */ + public void attachToActivity(Activity activity, int slideStyle, boolean actionbarOverlay) { + if (slideStyle != SLIDING_WINDOW && slideStyle != SLIDING_CONTENT) + throw new IllegalArgumentException("slideStyle must be either SLIDING_WINDOW or SLIDING_CONTENT"); + + if (getParent() != null) + throw new IllegalStateException("This SlidingMenu appears to already be attached"); + + // get the window background + TypedArray a = activity.getTheme().obtainStyledAttributes(new int[] {android.R.attr.windowBackground}); + int background = a.getResourceId(0, 0); + a.recycle(); + + switch (slideStyle) { + case SLIDING_WINDOW: + mActionbarOverlay = false; + ViewGroup decor = (ViewGroup) activity.getWindow().getDecorView(); + ViewGroup decorChild = (ViewGroup) decor.getChildAt(0); + // save ActionBar themes that have transparent assets + decorChild.setBackgroundResource(background); + decor.removeView(decorChild); + decor.addView(this); + setContent(decorChild); + break; + case SLIDING_CONTENT: + mActionbarOverlay = actionbarOverlay; + // take the above view out of + ViewGroup contentParent = (ViewGroup)activity.findViewById(android.R.id.content); + View content = contentParent.getChildAt(0); + contentParent.removeView(content); + contentParent.addView(this); + setContent(content); + // save people from having transparent backgrounds + if (content.getBackground() == null) + content.setBackgroundResource(background); + break; + } + } + + /** + * Set the above view content from a layout resource. The resource will be inflated, adding all top-level views + * to the above view. + * + * @param res the new content + */ + public void setContent(int res) { + setContent(LayoutInflater.from(getContext()).inflate(res, null)); + } + + /** + * Set the above view content to the given View. + * + * @param view The desired content to display. + */ + public void setContent(View view) { + mViewAbove.setContent(view); + showContent(); + } + + /** + * Retrieves the current content. + * @return the current content + */ + public View getContent() { + return mViewAbove.getContent(); + } + + /** + * Set the behind view (menu) content from a layout resource. The resource will be inflated, adding all top-level views + * to the behind view. + * + * @param res the new content + */ + public void setMenu(int res) { + setMenu(LayoutInflater.from(getContext()).inflate(res, null)); + } + + /** + * Set the behind view (menu) content to the given View. + * + * @param view The desired content to display. + */ + public void setMenu(View v) { + mViewBehind.setContent(v); + } + + /** + * Retrieves the main menu. + * @return the main menu + */ + public View getMenu() { + return mViewBehind.getContent(); + } + + /** + * Set the secondary behind view (right menu) content from a layout resource. The resource will be inflated, adding all top-level views + * to the behind view. + * + * @param res the new content + */ + public void setSecondaryMenu(int res) { + setSecondaryMenu(LayoutInflater.from(getContext()).inflate(res, null)); + } + + /** + * Set the secondary behind view (right menu) content to the given View. + * + * @param view The desired content to display. + */ + public void setSecondaryMenu(View v) { + mViewBehind.setSecondaryContent(v); + } + + /** + * Retrieves the current secondary menu (right). + * @return the current menu + */ + public View getSecondaryMenu() { + return mViewBehind.getSecondaryContent(); + } + + + /** + * Sets the sliding enabled. + * + * @param b true to enable sliding, false to disable it. + */ + public void setSlidingEnabled(boolean b) { + mViewAbove.setSlidingEnabled(b); + } + + /** + * Checks if is sliding enabled. + * + * @return true, if is sliding enabled + */ + public boolean isSlidingEnabled() { + return mViewAbove.isSlidingEnabled(); + } + + /** + * Sets which side the SlidingMenu should appear on. + * @param mode must be either SlidingMenu.LEFT or SlidingMenu.RIGHT + */ + public void setMode(int mode) { + if (mode != LEFT && mode != RIGHT && mode != LEFT_RIGHT) { + throw new IllegalStateException("SlidingMenu mode must be LEFT, RIGHT, or LEFT_RIGHT"); + } + mViewBehind.setMode(mode); + } + + /** + * Returns the current side that the SlidingMenu is on. + * @return the current mode, either SlidingMenu.LEFT or SlidingMenu.RIGHT + */ + public int getMode() { + return mViewBehind.getMode(); + } + + /** + * Sets whether or not the SlidingMenu is in static mode (i.e. nothing is moving and everything is showing) + * + * @param b true to set static mode, false to disable static mode. + */ + public void setStatic(boolean b) { + if (b) { + setSlidingEnabled(false); + mViewAbove.setCustomViewBehind(null); + mViewAbove.setCurrentItem(1); + // mViewBehind.setCurrentItem(0); + } else { + mViewAbove.setCurrentItem(1); + // mViewBehind.setCurrentItem(1); + mViewAbove.setCustomViewBehind(mViewBehind); + setSlidingEnabled(true); + } + } + + /** + * Opens the menu and shows the menu view. + */ + public void showMenu() { + showMenu(true); + } + + /** + * Opens the menu and shows the menu view. + * + * @param animate true to animate the transition, false to ignore animation + */ + public void showMenu(boolean animate) { + mViewAbove.setCurrentItem(0, animate); + } + + /** + * Opens the menu and shows the secondary menu view. Will default to the regular menu + * if there is only one. + */ + public void showSecondaryMenu() { + showSecondaryMenu(true); + } + + /** + * Opens the menu and shows the secondary (right) menu view. Will default to the regular menu + * if there is only one. + * + * @param animate true to animate the transition, false to ignore animation + */ + public void showSecondaryMenu(boolean animate) { + mViewAbove.setCurrentItem(2, animate); + } + + /** + * Closes the menu and shows the above view. + */ + public void showContent() { + showContent(true); + } + + /** + * Closes the menu and shows the above view. + * + * @param animate true to animate the transition, false to ignore animation + */ + public void showContent(boolean animate) { + mViewAbove.setCurrentItem(1, animate); + } + + /** + * Toggle the SlidingMenu. If it is open, it will be closed, and vice versa. + */ + public void toggle() { + toggle(true); + } + + /** + * Toggle the SlidingMenu. If it is open, it will be closed, and vice versa. + * + * @param animate true to animate the transition, false to ignore animation + */ + public void toggle(boolean animate) { + if (isMenuShowing()) { + showContent(animate); + } else { + showMenu(animate); + } + } + + /** + * Toggle the SlidingMenu. If it is open, it will be closed, and vice versa. + * + * @param animate true to animate the transition, false to ignore animation + */ + public void toggleSecondary(boolean animate) { + if (isSecondaryMenuShowing()) { + showContent(animate); + } else { + showSecondaryMenu(animate); + } + } + + /** + * Checks if is the behind view showing. + * + * @return Whether or not the behind view is showing + */ + public boolean isMenuShowing() { + return mViewAbove.getCurrentItem() == 0 || mViewAbove.getCurrentItem() == 2; + } + + /** + * Checks if is the behind view showing. + * + * @return Whether or not the behind view is showing + */ + public boolean isSecondaryMenuShowing() { + return mViewAbove.getCurrentItem() == 2; + } + + /** + * Gets the behind offset. + * + * @return The margin on the right of the screen that the behind view scrolls to + */ + public int getBehindOffset() { + return ((RelativeLayout.LayoutParams)mViewBehind.getLayoutParams()).rightMargin; + } + + /** + * Sets the behind offset. + * + * @param i The margin, in pixels, on the right of the screen that the behind view scrolls to. + */ + public void setBehindOffset(int i) { + // RelativeLayout.LayoutParams params = ((RelativeLayout.LayoutParams)mViewBehind.getLayoutParams()); + // int bottom = params.bottomMargin; + // int top = params.topMargin; + // int left = params.leftMargin; + // params.setMargins(left, top, i, bottom); + mViewBehind.setWidthOffset(i); + } + + public void setRightBehindOffset(int offset) { + mViewBehind.setSecondaryWidthOffset(offset); + } + + /** + * Sets the behind offset. + * + * @param resID The dimension resource id to be set as the behind offset. + * The menu, when open, will leave this width margin on the right of the screen. + */ + public void setBehindOffsetRes(int resID) { + int i = (int) getContext().getResources().getDimension(resID); + setBehindOffset(i); + } + + /** + * Sets the above offset. + * + * @param i the new above offset, in pixels + */ + public void setAboveOffset(int i) { + mViewAbove.setAboveOffset(i); + } + + /** + * Sets the above offset. + * + * @param resID The dimension resource id to be set as the above offset. + */ + public void setAboveOffsetRes(int resID) { + int i = (int) getContext().getResources().getDimension(resID); + setAboveOffset(i); + } + + @SuppressWarnings("deprecation") + private int getDisplayWidth() { + int width; + Display display = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)) + .getDefaultDisplay(); + try { + Class cls = Display.class; + Class[] parameterTypes = {Point.class}; + Point parameter = new Point(); + Method method = cls.getMethod("getSize", parameterTypes); + method.invoke(display, parameter); + width = parameter.x; + } catch (Exception e) { + width = display.getWidth(); + } + return width; + } + /** + * Sets the behind width. + * + * @param i The width the Sliding Menu will open to, in pixels + */ + public void setBehindWidth(int i) { + setBehindOffset(getDisplayWidth()-i); + } + + /** + * Sets the behind width. + * + * @param i The width the Sliding Menu will open to, in pixels + */ + public void setRightBehindWidth(int i) { + setRightBehindOffset(getDisplayWidth()-i); + } + + /** + * Sets the behind width. + * + * @param res The dimension resource id to be set as the behind width offset. + * The menu, when open, will open this wide. + */ + public void setBehindWidthRes(int res) { + int i = (int) getContext().getResources().getDimension(res); + setBehindWidth(i); + } + + /** + * Gets the behind scroll scale. + * + * @return The scale of the parallax scroll + */ + public float getBehindScrollScale() { + return mViewBehind.getScrollScale(); + } + + /** + * Gets the touch mode margin threshold + * @return the touch mode margin threshold + */ + public int getTouchmodeMarginThreshold() { + return mViewBehind.getMarginThreshold(); + } + + /** + * Set the touch mode margin threshold + * @param touchmodeMarginThreshold + */ + public void setTouchmodeMarginThreshold(int touchmodeMarginThreshold) { + mViewBehind.setMarginThreshold(touchmodeMarginThreshold); + } + + /** + * Sets the behind scroll scale. + * + * @param f The scale of the parallax scroll (i.e. 1.0f scrolls 1 pixel for every + * 1 pixel that the above view scrolls and 0.0f scrolls 0 pixels) + */ + public void setBehindScrollScale(float f) { + if (f < 0 && f > 1) + throw new IllegalStateException("ScrollScale must be between 0 and 1"); + mViewBehind.setScrollScale(f); + } + + /** + * Sets the behind canvas transformer. + * + * @param t the new behind canvas transformer + */ + public void setBehindCanvasTransformer(CanvasTransformer t) { + mViewBehind.setCanvasTransformer(t); + } + + /** + * Gets the touch mode above. + * + * @return the touch mode above + */ + public int getTouchModeAbove() { + return mViewAbove.getTouchMode(); + } + + /** + * Controls whether the SlidingMenu can be opened with a swipe gesture. + * Options are {@link #TOUCHMODE_MARGIN TOUCHMODE_MARGIN}, {@link #TOUCHMODE_FULLSCREEN TOUCHMODE_FULLSCREEN}, + * or {@link #TOUCHMODE_NONE TOUCHMODE_NONE} + * + * @param i the new touch mode + */ + public void setTouchModeAbove(int i) { + if (i != TOUCHMODE_FULLSCREEN && i != TOUCHMODE_MARGIN + && i != TOUCHMODE_NONE) { + throw new IllegalStateException("TouchMode must be set to either" + + "TOUCHMODE_FULLSCREEN or TOUCHMODE_MARGIN or TOUCHMODE_NONE."); + } + mViewAbove.setTouchMode(i); + } + + /** + * Controls whether the SlidingMenu can be opened with a swipe gesture. + * Options are {@link #TOUCHMODE_MARGIN TOUCHMODE_MARGIN}, {@link #TOUCHMODE_FULLSCREEN TOUCHMODE_FULLSCREEN}, + * or {@link #TOUCHMODE_NONE TOUCHMODE_NONE} + * + * @param i the new touch mode + */ + public void setTouchModeBehind(int i) { + if (i != TOUCHMODE_FULLSCREEN && i != TOUCHMODE_MARGIN + && i != TOUCHMODE_NONE) { + throw new IllegalStateException("TouchMode must be set to either" + + "TOUCHMODE_FULLSCREEN or TOUCHMODE_MARGIN or TOUCHMODE_NONE."); + } + mViewBehind.setTouchMode(i); + } + + /** + * Sets the shadow drawable. + * + * @param resId the resource ID of the new shadow drawable + */ + @SuppressWarnings("deprecation") + public void setShadowDrawable(int resId) { + setShadowDrawable(getContext().getResources().getDrawable(resId)); + } + + /** + * Sets the shadow drawable. + * + * @param d the new shadow drawable + */ + public void setShadowDrawable(Drawable d) { + mViewBehind.setShadowDrawable(d); + } + + /** + * Sets the secondary (right) shadow drawable. + * + * @param resId the resource ID of the new shadow drawable + */ + @SuppressWarnings("deprecation") + public void setSecondaryShadowDrawable(int resId) { + setSecondaryShadowDrawable(getContext().getResources().getDrawable(resId)); + } + + /** + * Sets the secondary (right) shadow drawable. + * + * @param d the new shadow drawable + */ + public void setSecondaryShadowDrawable(Drawable d) { + mViewBehind.setSecondaryShadowDrawable(d); + } + + /** + * Sets the shadow width. + * + * @param resId The dimension resource id to be set as the shadow width. + */ + public void setShadowWidthRes(int resId) { + setShadowWidth((int)getResources().getDimension(resId)); + } + + /** + * Sets the shadow width. + * + * @param pixels the new shadow width, in pixels + */ + public void setShadowWidth(int pixels) { + mViewBehind.setShadowWidth(pixels); + } + + /** + * Enables or disables the SlidingMenu's fade in and out + * + * @param b true to enable fade, false to disable it + */ + public void setFadeEnabled(boolean b) { + mViewBehind.setFadeEnabled(b); + } + + /** + * Sets how much the SlidingMenu fades in and out. Fade must be enabled, see + * {@link #setFadeEnabled(boolean) setFadeEnabled(boolean)} + * + * @param f the new fade degree, between 0.0f and 1.0f + */ + public void setFadeDegree(float f) { + mViewBehind.setFadeDegree(f); + } + + /** + * Enables or disables whether the selector is drawn + * + * @param b true to draw the selector, false to not draw the selector + */ + public void setSelectorEnabled(boolean b) { + mViewBehind.setSelectorEnabled(true); + } + + /** + * Sets the selected view. The selector will be drawn here + * + * @param v the new selected view + */ + public void setSelectedView(View v) { + mViewBehind.setSelectedView(v); + } + + /** + * Sets the selector drawable. + * + * @param res a resource ID for the selector drawable + */ + public void setSelectorDrawable(int res) { + mViewBehind.setSelectorBitmap(BitmapFactory.decodeResource(getResources(), res)); + } + + /** + * Sets the selector drawable. + * + * @param b the new selector bitmap + */ + public void setSelectorBitmap(Bitmap b) { + mViewBehind.setSelectorBitmap(b); + } + + /** + * Add a View ignored by the Touch Down event when mode is Fullscreen + * + * @param v a view to be ignored + */ + public void addIgnoredView(View v) { + mViewAbove.addIgnoredView(v); + } + + /** + * Remove a View ignored by the Touch Down event when mode is Fullscreen + * + * @param v a view not wanted to be ignored anymore + */ + public void removeIgnoredView(View v) { + mViewAbove.removeIgnoredView(v); + } + + /** + * Clear the list of Views ignored by the Touch Down event when mode is Fullscreen + */ + public void clearIgnoredViews() { + mViewAbove.clearIgnoredViews(); + } + + /** + * Sets the OnOpenListener. {@link OnOpenListener#onOpen() OnOpenListener.onOpen()} will be called when the SlidingMenu is opened + * + * @param listener the new OnOpenListener + */ + public void setOnOpenListener(OnOpenListener listener) { + //mViewAbove.setOnOpenListener(listener); + mOpenListener = listener; + } + + /** + * Sets the OnOpenListner for secondary menu {@link OnOpenListener#onOpen() OnOpenListener.onOpen()} will be called when the secondary SlidingMenu is opened + * + * @param listener the new OnOpenListener + */ + public void setSecondaryOnOpenListner(OnOpenListener listener) { + mSecondaryOpenListner = listener; + } + + /** + * Sets the OnCloseListener. {@link OnCloseListener#onClose() OnCloseListener.onClose()} will be called when the SlidingMenu is closed + * + * @param listener the new setOnCloseListener + */ + public void setOnCloseListener(OnCloseListener listener) { + //mViewAbove.setOnCloseListener(listener); + mCloseListener = listener; + } + + /** + * Sets the OnOpenedListener. {@link OnOpenedListener#onOpened() OnOpenedListener.onOpened()} will be called after the SlidingMenu is opened + * + * @param listener the new OnOpenedListener + */ + public void setOnOpenedListener(OnOpenedListener listener) { + mViewAbove.setOnOpenedListener(listener); + } + + /** + * Sets the OnClosedListener. {@link OnClosedListener#onClosed() OnClosedListener.onClosed()} will be called after the SlidingMenu is closed + * + * @param listener the new OnClosedListener + */ + public void setOnClosedListener(OnClosedListener listener) { + mViewAbove.setOnClosedListener(listener); + } + + /** + * Sets the OnClosedListener. {@link OnClosedListener#onClosed() OnClosedListener.onClosed()} will be called after the SlidingMenu is closed + * + * @param listener the new OnClosedListener + */ + public void setOnScrolledListener(OnScrolledListener listener) { + mOnScrolledListener = listener; + } + + public static class SavedState extends BaseSavedState { + + private final int mItem; + + public SavedState(Parcelable superState, int item) { + super(superState); + mItem = item; + } + + private SavedState(Parcel in) { + super(in); + mItem = in.readInt(); + } + + public int getItem() { + return mItem; + } + + /* (non-Javadoc) + * @see android.view.AbsSavedState#writeToParcel(android.os.Parcel, int) + */ + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeInt(mItem); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + + } + + /* (non-Javadoc) + * @see android.view.View#onSaveInstanceState() + */ + @Override + protected Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + SavedState ss = new SavedState(superState, mViewAbove.getCurrentItem()); + return ss; + } + + /* (non-Javadoc) + * @see android.view.View#onRestoreInstanceState(android.os.Parcelable) + */ + @Override + protected void onRestoreInstanceState(Parcelable state) { + SavedState ss = (SavedState)state; + super.onRestoreInstanceState(ss.getSuperState()); + mViewAbove.setCurrentItem(ss.getItem()); + } + + /* (non-Javadoc) + * @see android.view.ViewGroup#fitSystemWindows(android.graphics.Rect) + */ + + //@SuppressLint("NewApi") + @Override + protected boolean fitSystemWindows(Rect insets) { + int leftPadding = insets.left; + int rightPadding = insets.right; + int topPadding = insets.top; + int bottomPadding = insets.bottom; + if (!mActionbarOverlay) { + Log.v(TAG, "setting padding!"); + setPadding(leftPadding, topPadding, rightPadding, bottomPadding); + } + return true; + } + + /* + private Handler mHandler = new Handler(); + + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public void manageLayers(float percentOpen) { + if (Build.VERSION.SDK_INT < 11) return; + + boolean layer = percentOpen > 0.0f && percentOpen < 1.0f; + final int layerType = layer ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE; + + if (layerType != getContent().getLayerType()) { + mHandler.post(new Runnable() { + public void run() { + Log.v(TAG, "changing layerType. hardware? " + (layerType == View.LAYER_TYPE_HARDWARE)); + getContent().setLayerType(layerType, null); + getMenu().setLayerType(layerType, null); + if (getSecondaryMenu() != null) { + getSecondaryMenu().setLayerType(layerType, null); + } + } + }); + } + + } + */ + + +} diff --git a/android/src/dk/napp/drawer/Drawer.java b/android/src/dk/napp/drawer/Drawer.java new file mode 100755 index 0000000..173e4ae --- /dev/null +++ b/android/src/dk/napp/drawer/Drawer.java @@ -0,0 +1,525 @@ +/** + * Copyright (c) 2010-2013 by Napp ApS + * www.napp.dk + * Author Mads Møller + * + * Special thanks to Martin Guillon + * + * Appcelerator Titanium is Copyright (c) 2009-2013 by Appcelerator, Inc. + * and licensed under the Apache Public License (version 2) + */ + +package dk.napp.drawer; + +import java.util.HashMap; + +import org.appcelerator.kroll.KrollDict; +import org.appcelerator.kroll.KrollProxy; +import org.appcelerator.kroll.common.Log; +import org.appcelerator.titanium.TiBaseActivity; +import org.appcelerator.titanium.TiBaseActivity.ConfigurationChangedListener; +import org.appcelerator.titanium.TiC; +import org.appcelerator.titanium.TiDimension; +import org.appcelerator.titanium.proxy.ActivityProxy; +import org.appcelerator.titanium.proxy.TiViewProxy; +import org.appcelerator.titanium.util.TiConvert; +import org.appcelerator.titanium.view.TiCompositeLayout; +import org.appcelerator.titanium.view.TiUIView; + +import org.appcelerator.titanium.proxy.TiWindowProxy; + +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.GradientDrawable.Orientation; +import android.view.animation.Interpolator; + +import com.slidingmenu.lib.SlidingMenu.CanvasTransformer; +import com.slidingmenu.lib.SlidingMenu; + +import chrisrenke.drawerarrowdrawable.DrawerArrowDrawable ; + + +public class Drawer extends TiUIView implements ConfigurationChangedListener{ + + private SlidingMenu slidingMenu; + private TiViewProxy leftView; + private TiViewProxy rightView; + private TiViewProxy centerView; + + private static final String TAG = "NappDrawer"; + private TiBaseActivity activity; + private int leftMenuWidth; + private int rightMenuWidth; + + // Used by DrawerArrowDrawable + private float leftMenuOffset=0; + private DrawerArrowDrawable drawerArrowDrawable; + private boolean useHamburgerIcon = false; + private boolean useArrowAnimation = false; + private boolean useHamburgerIconCustomColor = false; + int hamburgerIconColor = 0; + + // Static Properties + public static final String PROPERTY_LEFT_VIEW = "leftWindow"; + public static final String PROPERTY_CENTER_VIEW = "centerWindow"; + public static final String PROPERTY_RIGHT_VIEW = "rightWindow"; + public static final String PROPERTY_LEFT_VIEW_WIDTH = "leftDrawerWidth"; + public static final String PROPERTY_RIGHT_VIEW_WIDTH = "rightDrawerWidth"; + public static final String PROPERTY_FADING = "fading"; + public static final String PROPERTY_MENU_SCROLL_SCALE = "parallaxAmount"; + public static final String PROPERTY_SHADOW_WIDTH = "shadowWidth"; + public static final String PROPERTY_OPEN_MODE = "openDrawerGestureMode"; + public static final String PROPERTY_CLOSE_MODE = "closeDrawerGestureMode"; + public static final String PROPERTY_ANIMATION_MODE = "animationMode"; + public static final String PROPERTY_HAMBURGER_ICON = "hamburgerIcon"; + public static final String PROPERTY_ARROW_ANIMATION = "arrowAnimation"; + public static final String PROPERTY_HAMBURGER_ICON_COLOR = "hamburgerIconColor"; + + // for animations + private static Interpolator interp = new Interpolator() { + @Override + public float getInterpolation(float t) { + t -= 1.0f; + return t * t * t + 1.0f; + } + }; + + + public Drawer(final DrawerProxy proxy, TiBaseActivity activity) + { + super(proxy); + this.activity = activity; + activity.addConfigurationChangedListener(this); + + // configure the SlidingMenu + slidingMenu = new SlidingMenu(activity); + + // set the drawerArrowIcon + Resources resources = activity.getResources(); + drawerArrowDrawable = new DrawerArrowDrawable(resources); + + slidingMenu.setOnClosedListener(new SlidingMenu.OnClosedListener() { + @Override + public void onClosed() { + if (proxy.hasListeners("windowDidClose")) { + KrollDict options = new KrollDict(); + proxy.fireEvent("windowDidClose", options); + } + } + }); + + slidingMenu.setOnOpenListener(new SlidingMenu.OnOpenListener() { + @Override + public void onOpen(int leftOrRight) { + if (proxy.hasListeners("windowDidOpen")) { + KrollDict options = new KrollDict(); + options.put("window", NappdrawerModule.LEFT_WINDOW); + proxy.fireEvent("windowDidOpen", options); + } + } + }); + + slidingMenu.setSecondaryOnOpenListner(new SlidingMenu.OnOpenListener() { + @Override + public void onOpen(int leftOrRight) { + if (proxy.hasListeners("windowDidOpen")) { + KrollDict options = new KrollDict(); + options.put("window", NappdrawerModule.RIGHT_WINDOW); + proxy.fireEvent("windowDidOpen", options); + } + } + }); + + slidingMenu.setOnScrolledListener(new SlidingMenu.OnScrolledListener() { + @Override + public void onScrolled(int scroll) { + if (proxy.hasListeners("didChangeOffset")) { + KrollDict options = new KrollDict(); + options.put("offset", scroll); + proxy.fireEvent("didChangeOffset", options); + } + if( useArrowAnimation){ + // Only the rightDrawer should convert the offset in 0-1 range for the drawerArrowAnimation. + if (scroll <= 0f) { + leftMenuOffset = 0f; + }else{ + leftMenuOffset = (float) scroll/leftMenuWidth; + } + + if (leftMenuOffset >= .995) { + drawerArrowDrawable.setFlip(true); + } else if (leftMenuOffset <= .005) { + drawerArrowDrawable.setFlip(false); + } + drawerArrowDrawable.setParameter(leftMenuOffset); + } + } + }); + + slidingMenu.setMode(SlidingMenu.LEFT); + slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN); + leftMenuWidth = -100; + slidingMenu.setFadeDegree(0.0f); + slidingMenu.setBehindScrollScale(0.0f); + slidingMenu.setShadowWidth(0); + + updateMenuWidth(); + + slidingMenu.attachToActivity(activity, SlidingMenu.SLIDING_WINDOW); + + // shadow + int[] colors1 = {Color.argb(0, 0, 0, 0), Color.argb(50, 0, 0, 0)}; + GradientDrawable shadow = new GradientDrawable(Orientation.LEFT_RIGHT, colors1); + GradientDrawable shadowR = new GradientDrawable(Orientation.RIGHT_LEFT, colors1); + slidingMenu.setShadowDrawable(shadow); + slidingMenu.setSecondaryShadowDrawable(shadowR); + + setNativeView(slidingMenu); + } + + public SlidingMenu getSlidingMenu() + { + return slidingMenu; + } + + private void updateMenuWidth() + { + if (leftMenuWidth > 0) + slidingMenu.setBehindWidth(leftMenuWidth); + else + slidingMenu.setBehindOffset(-leftMenuWidth); + } + + private void updateRightMenuWidth() + { + if (rightMenuWidth > 0) + slidingMenu.setRightBehindWidth(rightMenuWidth); + else + slidingMenu.setRightBehindOffset(-rightMenuWidth); + } + + + public int getMenuWidth() + { + return leftMenuWidth; + } + + private void updateCloseDrawerGestureMode(int mode) + { + if (mode == NappdrawerModule.CLOSE_MODE_ALL) { + slidingMenu.setTouchModeBehind(SlidingMenu.TOUCHMODE_FULLSCREEN); + } else if (mode == NappdrawerModule.CLOSE_MODE_MARGIN) { + slidingMenu.setTouchModeBehind(SlidingMenu.TOUCHMODE_MARGIN); + } else if (mode == NappdrawerModule.CLOSE_MODE_NONE) { + slidingMenu.setTouchModeBehind(SlidingMenu.TOUCHMODE_NONE); + } else { + slidingMenu.setTouchModeBehind(SlidingMenu.TOUCHMODE_FULLSCREEN); + } + } + + private void updateOpenDrawerGestureMode(int mode) + { + if (mode == NappdrawerModule.OPEN_MODE_ALL) { + slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN); + } else if (mode == NappdrawerModule.OPEN_MODE_MARGIN) { + slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_MARGIN); + } else if (mode == NappdrawerModule.OPEN_MODE_NONE) { + slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_NONE); + } else { + slidingMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN); + } + } + + private void updateAnimationMode(int mode) + { + if (mode == NappdrawerModule.ANIMATION_SCALE) { + // scale + slidingMenu.setBehindCanvasTransformer(new CanvasTransformer() { + @Override + public void transformCanvas(Canvas canvas, float percentOpen) { + canvas.scale(percentOpen, 1, 0, 0); + } + }); + } else if (mode == NappdrawerModule.ANIMATION_SLIDEUP) { + // slide + slidingMenu.setBehindCanvasTransformer(new CanvasTransformer() { + @Override + public void transformCanvas(Canvas canvas, float percentOpen) { + canvas.translate(0, canvas.getHeight()*(1-interp.getInterpolation(percentOpen))); + } + }); + } else if (mode == NappdrawerModule.ANIMATION_ZOOM) { + // zoom animation + slidingMenu.setBehindCanvasTransformer(new CanvasTransformer() { + @Override + public void transformCanvas(Canvas canvas, float percentOpen) { + float scale = (float) (percentOpen*0.25 + 0.75); + canvas.scale(scale, scale, canvas.getWidth()/2, canvas.getHeight()/2); + } + }); + } else { + // No animation + slidingMenu.setBehindCanvasTransformer(new CanvasTransformer() { + @Override + public void transformCanvas(Canvas canvas, float percentOpen) {} + }); + } + + // we need to reset the scrollScale when applying custom animations + if( mode == NappdrawerModule.ANIMATION_SCALE || + mode == NappdrawerModule.ANIMATION_SLIDEUP || + mode == NappdrawerModule.ANIMATION_ZOOM){ + slidingMenu.setBehindScrollScale(0.0f); + } + + } + + + + private void updateMenus() { + if (this.leftView != null && this.rightView != null) { + slidingMenu.setMode(SlidingMenu.LEFT_RIGHT); + slidingMenu.setMenu((this.leftView).getOrCreateView().getOuterView()); + slidingMenu.setSecondaryMenu((this.rightView).getOrCreateView().getOuterView()); + } + else if (this.rightView != null) + { + slidingMenu.setMode(SlidingMenu.RIGHT); + slidingMenu.setMenu((this.rightView).getOrCreateView().getOuterView()); + slidingMenu.setSecondaryMenu(null); + } + else if (this.leftView != null) + { + slidingMenu.setMode(SlidingMenu.LEFT); + slidingMenu.setMenu((this.leftView).getOrCreateView().getOuterView()); + slidingMenu.setSecondaryMenu(null); + } + else + { + slidingMenu.setMode(SlidingMenu.LEFT); + slidingMenu.setMenu(null); + slidingMenu.setSecondaryMenu(null); + } + } + + + private void updateDrawerArrowDrawable() { + if( useArrowAnimation || useHamburgerIcon ){ + if( useHamburgerIconCustomColor ){ + drawerArrowDrawable.setStrokeColor(hamburgerIconColor); + } + activity.getSupportActionBar().setHomeAsUpIndicator(drawerArrowDrawable); + activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true); + }else{ + activity.getSupportActionBar().setHomeAsUpIndicator(0); + } + } + + @Override + public void processProperties(KrollDict d) + { + if (d.containsKey(TiC.PROPERTY_ACTIVITY)) { + Object activityObject = d.get(TiC.PROPERTY_ACTIVITY); + ActivityProxy activityProxy = getProxy().getActivityProxy(); + if (activityObject instanceof HashMap && activityProxy != null) { + @SuppressWarnings("unchecked") + KrollDict options = new KrollDict((HashMap) activityObject); + activityProxy.handleCreationDict(options); + } + } + if (d.containsKey(PROPERTY_LEFT_VIEW)) { + Object leftView = d.get(PROPERTY_LEFT_VIEW); + if (leftView != null && leftView instanceof TiViewProxy) { + if (leftView instanceof TiWindowProxy) + throw new IllegalStateException("[ERROR] Cannot use window as SlideMenu view"); + this.leftView = (TiViewProxy)leftView; + } else { + Log.e(TAG, "[ERROR] Invalid type for leftView"); + } + } + if (d.containsKey(PROPERTY_RIGHT_VIEW)) { + Object rightView = d.get(PROPERTY_RIGHT_VIEW); + if (rightView != null && rightView instanceof TiViewProxy) { + if (rightView instanceof TiWindowProxy) + throw new IllegalStateException("[ERROR] Cannot use window as SlideMenu view"); + this.rightView = (TiViewProxy)rightView; + } else { + Log.e(TAG, "[ERROR] Invalid type for rightView"); + } + } + + if (d.containsKey(PROPERTY_CENTER_VIEW)) { + Object centerView = d.get(PROPERTY_CENTER_VIEW); + if (centerView != null && centerView instanceof TiViewProxy) { + if (centerView instanceof TiWindowProxy) + throw new IllegalStateException("[ERROR] Cannot use window as SlideMenu view"); + + this.centerView = (TiViewProxy)centerView; + TiCompositeLayout content = ((TiCompositeLayout) activity.getLayout()); + TiCompositeLayout.LayoutParams params = new TiCompositeLayout.LayoutParams(); + params.autoFillsHeight = true; + params.autoFillsWidth = true; + content.addView(((TiViewProxy)centerView).getOrCreateView().getOuterView(), params); + } else { + Log.e(TAG, "[ERROR] Invalid type for centerView"); + } + } + + + updateMenus(); + + if(d.containsKey(PROPERTY_CLOSE_MODE)) { + updateCloseDrawerGestureMode(TiConvert.toInt(d.get(PROPERTY_CLOSE_MODE))); + } + + if(d.containsKey(PROPERTY_OPEN_MODE)) { + updateOpenDrawerGestureMode(TiConvert.toInt(d.get(PROPERTY_OPEN_MODE))); + } + + if (d.containsKey(PROPERTY_LEFT_VIEW_WIDTH)) { + leftMenuWidth = getDevicePixels(d.get(PROPERTY_LEFT_VIEW_WIDTH)); + updateMenuWidth(); + } + if (d.containsKey(PROPERTY_RIGHT_VIEW_WIDTH)) { + rightMenuWidth = getDevicePixels(d.get(PROPERTY_RIGHT_VIEW_WIDTH)); + updateRightMenuWidth(); + } + + if (d.containsKey(PROPERTY_FADING)) { + slidingMenu.setFadeDegree(d.getDouble(PROPERTY_FADING).floatValue()); + } + if (d.containsKey(PROPERTY_MENU_SCROLL_SCALE)) { + slidingMenu.setBehindScrollScale(d.getDouble(PROPERTY_MENU_SCROLL_SCALE).floatValue()); + } + if (d.containsKey(PROPERTY_SHADOW_WIDTH)) { + slidingMenu.setShadowWidth(getDevicePixels(d.get(PROPERTY_SHADOW_WIDTH))); + } + + if (d.containsKey(PROPERTY_ANIMATION_MODE)) { + updateAnimationMode(TiConvert.toInt(d.get(PROPERTY_ANIMATION_MODE))); + } + + if (d.containsKey(PROPERTY_HAMBURGER_ICON)) { + useHamburgerIcon = TiConvert.toBoolean(d.get(PROPERTY_HAMBURGER_ICON)); + updateDrawerArrowDrawable(); + } + + if (d.containsKey(PROPERTY_HAMBURGER_ICON_COLOR)) { + hamburgerIconColor = TiConvert.toColor(d.getString(PROPERTY_HAMBURGER_ICON_COLOR)); + useHamburgerIconCustomColor = true; + updateDrawerArrowDrawable(); + } + + if (d.containsKey(PROPERTY_ARROW_ANIMATION)) { + useArrowAnimation = TiConvert.toBoolean(d.get(PROPERTY_ARROW_ANIMATION)); + updateDrawerArrowDrawable(); + } + + + super.processProperties(d); + } + + @Override + public void propertyChanged(String key, Object oldValue, Object newValue, KrollProxy proxy) + { + Log.d(TAG, "Property: " + key + " old: " + oldValue + " new: " + newValue, Log.DEBUG_MODE); + + if (key.equals(PROPERTY_LEFT_VIEW)) { + + if (newValue == this.leftView) return; + TiViewProxy newProxy = null; + if (newValue != null && newValue instanceof TiViewProxy) { + if (newValue instanceof TiWindowProxy) + throw new IllegalStateException("Cannot use window as SlideMenu view"); + newProxy = (TiViewProxy)newValue; + } else { + Log.e(TAG, "Invalid type for leftView"); + } + this.leftView = newProxy; + updateMenus(); + } else if (key.equals(PROPERTY_RIGHT_VIEW)) { + if (newValue == this.rightView) return; + TiViewProxy newProxy = null; + if (newValue != null && newValue instanceof TiViewProxy) { + if (newValue instanceof TiWindowProxy) + throw new IllegalStateException("Cannot use window as SlideMenu view"); + newProxy = (TiViewProxy)newValue; + } else { + Log.e(TAG, "Invalid type for rightView"); + } + this.rightView = newProxy; + updateMenus(); + } else if (key.equals(PROPERTY_CENTER_VIEW)) { + if (newValue == this.centerView) return; + TiCompositeLayout content = ((TiCompositeLayout) activity.getLayout()); + TiViewProxy newProxy = null; + int index = 0; + if (this.centerView != null) { + index = content.indexOfChild(this.centerView.getOrCreateView().getNativeView()); + } + if (newValue != null && newValue instanceof TiViewProxy) { + if (newValue instanceof TiWindowProxy) + throw new IllegalStateException("Cannot use window as SlideMenu view"); + newProxy = (TiViewProxy)newValue; + TiCompositeLayout.LayoutParams params = new TiCompositeLayout.LayoutParams(); + params.autoFillsHeight = true; + params.autoFillsWidth = true; + content.addView(newProxy.getOrCreateView().getOuterView(), index, params); + } else { + Log.e(TAG, "Invalid type for centerView"); + } + if (this.centerView != null) { + content.removeView(this.centerView.getOrCreateView().getNativeView()); + } + this.centerView = newProxy; + } else if(key.equals(PROPERTY_CLOSE_MODE)) { + updateCloseDrawerGestureMode(TiConvert.toInt(newValue)); + } else if(key.equals(PROPERTY_OPEN_MODE)) { + updateOpenDrawerGestureMode(TiConvert.toInt(newValue)); + } else if (key.equals(PROPERTY_LEFT_VIEW_WIDTH)) { + leftMenuWidth = getDevicePixels(newValue); + updateMenuWidth(); + } else if (key.equals(PROPERTY_RIGHT_VIEW_WIDTH)) { + rightMenuWidth = getDevicePixels(newValue); + updateRightMenuWidth(); + } else if (key.equals(PROPERTY_FADING)) { + slidingMenu.setFadeDegree(TiConvert.toFloat(newValue)); + } else if (key.equals(PROPERTY_MENU_SCROLL_SCALE)) { + slidingMenu.setBehindScrollScale(TiConvert.toFloat(newValue)); + } else if (key.equals(PROPERTY_SHADOW_WIDTH)) { + slidingMenu.setShadowWidth(getDevicePixels(newValue)); + } else if (key.equals(PROPERTY_ANIMATION_MODE)) { + updateAnimationMode(TiConvert.toInt(newValue)); + } else if (key.equals(PROPERTY_HAMBURGER_ICON)) { + useHamburgerIcon = TiConvert.toBoolean(newValue); + updateDrawerArrowDrawable(); + } else if (key.equals(PROPERTY_HAMBURGER_ICON_COLOR)) { + hamburgerIconColor = TiConvert.toColor((String) newValue); + useHamburgerIconCustomColor = true; + updateDrawerArrowDrawable(); + } else if (key.equals(PROPERTY_ARROW_ANIMATION)) { + useArrowAnimation = TiConvert.toBoolean(newValue); + updateDrawerArrowDrawable(); + } else { + super.propertyChanged(key, oldValue, newValue, proxy); + } + } + + @Override + public void onConfigurationChanged(TiBaseActivity activity, Configuration newConfig) { + updateMenuWidth(); + } + + /** + * Napp: "100dp" to pixels + * @param value + * @return pixels + */ + public int getDevicePixels(Object value){ + return TiConvert.toTiDimension(TiConvert.toString(value), TiDimension.TYPE_WIDTH).getAsPixels(slidingMenu); + } + +} diff --git a/android/src/dk/napp/drawer/DrawerProxy.java b/android/src/dk/napp/drawer/DrawerProxy.java new file mode 100755 index 0000000..6aa2db4 --- /dev/null +++ b/android/src/dk/napp/drawer/DrawerProxy.java @@ -0,0 +1,533 @@ +/** + * Copyright (c) 2010-2013 by Napp ApS + * www.napp.dk + * Author Mads Møller + * + * Special thanks to Martin Guillon + * + * Appcelerator Titanium is Copyright (c) 2009-2013 by Appcelerator, Inc. + * and licensed under the Apache Public License (version 2) + */ + +package dk.napp.drawer; + +import java.lang.ref.WeakReference; + +import org.appcelerator.kroll.KrollDict; +import org.appcelerator.kroll.annotations.Kroll; +import org.appcelerator.kroll.common.Log; +import org.appcelerator.titanium.TiActivity; +import org.appcelerator.titanium.TiActivityWindow; +import org.appcelerator.titanium.TiActivityWindows; +import org.appcelerator.titanium.TiApplication; +import org.appcelerator.titanium.TiBaseActivity; +import org.appcelerator.titanium.TiBlob; +import org.appcelerator.titanium.TiC; +import org.appcelerator.titanium.proxy.TiWindowProxy; +import org.appcelerator.titanium.util.TiConvert; +import org.appcelerator.titanium.util.TiUIHelper; + +import com.slidingmenu.lib.SlidingMenu; + +import dk.napp.drawer.Drawer; +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.os.Message; + +@Kroll.proxy(creatableInModule=NappdrawerModule.class) +public class DrawerProxy extends TiWindowProxy implements TiActivityWindow +{ + private static final String TAG = "NappDrawerProxy"; + + private static final int MSG_FIRST_ID = TiWindowProxy.MSG_LAST_ID + 1; + + private static final int MSG_TOGGLE_LEFT_VIEW = MSG_FIRST_ID + 100; + private static final int MSG_TOGGLE_RIGHT_VIEW = MSG_FIRST_ID + 101; + private static final int MSG_OPEN_LEFT_VIEW = MSG_FIRST_ID + 102; + private static final int MSG_OPEN_RIGHT_VIEW = MSG_FIRST_ID + 103; + private static final int MSG_CLOSE_LEFT_VIEW = MSG_FIRST_ID + 104; + private static final int MSG_CLOSE_RIGHT_VIEW = MSG_FIRST_ID + 105; + private static final int MSG_CLOSE_VIEWS = MSG_FIRST_ID + 106; + + protected static final int MSG_LAST_ID = MSG_FIRST_ID + 999; + + private WeakReference slideMenuActivity; + private WeakReference slidingMenu; + + public DrawerProxy() + { + super(); + } + + @Override + public boolean handleMessage(Message msg) + { + switch (msg.what) { + case MSG_TOGGLE_LEFT_VIEW: { + handleToggleLeftView((Boolean)msg.obj); + return true; + } + case MSG_TOGGLE_RIGHT_VIEW: { + handleToggleRightView((Boolean)msg.obj); + return true; + } + case MSG_OPEN_LEFT_VIEW: { + handleOpenLeftView((Boolean)msg.obj); + return true; + } + case MSG_OPEN_RIGHT_VIEW: { + handleOpenRightView((Boolean)msg.obj); + return true; + } + case MSG_CLOSE_LEFT_VIEW: { + handleCloseLeftView((Boolean)msg.obj); + return true; + } + case MSG_CLOSE_RIGHT_VIEW: { + handleCloseRightView((Boolean)msg.obj); + return true; + } + case MSG_CLOSE_VIEWS: { + handleCloseViews((Boolean)msg.obj); + return true; + } + default : { + return super.handleMessage(msg); + } + } + } + + @Override + public void handleCreationDict(KrollDict options) { + super.handleCreationDict(options); + + // Support setting orientation modes at creation. + Object orientationModes = options.get( TiC.PROPERTY_ORIENTATION_MODES); + if (orientationModes != null && orientationModes instanceof Object[]) { + try { + int[] modes = TiConvert.toIntArray((Object[]) orientationModes); + setOrientationModes(modes); + + } catch (ClassCastException e) { + Log.e(TAG, "Invalid orientationMode array. Must only contain orientation mode constants."); + } + } + } + + + @Override + protected void handleOpen(KrollDict options) + { + Activity topActivity = TiApplication.getAppCurrentActivity(); + + if (topActivity == null || topActivity.isFinishing()) { + Log.w(TAG, "Unable to open drawer. Activity is null"); + return; + } + + Intent intent = new Intent(topActivity, TiActivity.class); + fillIntent(topActivity, intent); + + int windowId = TiActivityWindows.addWindow(this); + intent.putExtra(TiC.INTENT_PROPERTY_USE_ACTIVITY_WINDOW, true); + intent.putExtra(TiC.INTENT_PROPERTY_WINDOW_ID, windowId); + + topActivity.startActivity(intent); + } + + @Override + public void windowCreated(TiBaseActivity activity, Bundle savedInstanceState) { + slideMenuActivity = new WeakReference(activity); + activity.setWindowProxy(this); + setActivity(activity); + view = new Drawer(this, activity); + slidingMenu = new WeakReference(((Drawer)view).getSlidingMenu()); + setModelListener(view); + + handlePostOpen(); + + // Push the tab group onto the window stack. It needs to intercept + // stack changes to properly dispatch tab focus and blur events + // when windows open and close on top of it. + activity.addWindowToStack(this); + } + + @Override + public void handlePostOpen() + { + super.handlePostOpen(); + + opened = true; + + // First open before we load and focus our first tab. + fireEvent(TiC.EVENT_OPEN, null); + + // Setup the new tab activity like setting orientation modes. + onWindowActivityCreated(); + } + + @Override + protected void handleClose(KrollDict options) + { + Log.d(TAG, "handleClose: " + options, Log.DEBUG_MODE); + + modelListener = null; + releaseViews(); + view = null; + + opened = false; + + Activity activity = slideMenuActivity.get(); + if (activity != null && !activity.isFinishing()) { + activity.finish(); + } + + } + + @Override + public void closeFromActivity(boolean activityIsFinishing) { + // Call super to fire the close event on the tab group. + // This event must fire after each tab has been closed. + super.closeFromActivity(activityIsFinishing); + } + + @Override + public void onWindowFocusChange(boolean focused) { + if (focused){ + fireEvent(TiC.EVENT_FOCUS, null); + } else { + fireEvent(TiC.EVENT_BLUR, null); + } + } + + private void fillIntent(Activity activity, Intent intent) + { + if (hasProperty(TiC.PROPERTY_FULLSCREEN)) { + intent.putExtra(TiC.PROPERTY_FULLSCREEN, TiConvert.toBoolean(getProperty(TiC.PROPERTY_FULLSCREEN))); + } + if (hasProperty(TiC.PROPERTY_WINDOW_SOFT_INPUT_MODE)) { + intent.putExtra(TiC.PROPERTY_WINDOW_SOFT_INPUT_MODE, TiConvert.toInt(getProperty(TiC.PROPERTY_WINDOW_SOFT_INPUT_MODE))); + } + + if (hasProperty(TiC.PROPERTY_EXIT_ON_CLOSE)) { + intent.putExtra(TiC.INTENT_PROPERTY_FINISH_ROOT, TiConvert.toBoolean(getProperty(TiC.PROPERTY_EXIT_ON_CLOSE))); + } else { + intent.putExtra(TiC.INTENT_PROPERTY_FINISH_ROOT, activity.isTaskRoot()); + } + } + + @Override + public TiBlob handleToImage() + { + KrollDict d = TiUIHelper.viewToImage(new KrollDict(), getActivity().getWindow().getDecorView()); + return TiUIHelper.getImageFromDict(d); + } + + @Override + public void releaseViews() + { + super.releaseViews(); + } + + @Override + protected Activity getWindowActivity() + { + return (slideMenuActivity != null) ? slideMenuActivity.get() : null; + } + + private void handleToggleLeftView(boolean animated) + { + SlidingMenu menu = slidingMenu.get(); + menu.toggle(animated); + } + + private void handleToggleRightView(boolean animated) + { + SlidingMenu menu = slidingMenu.get(); + menu.toggleSecondary(animated); + } + + private void handleOpenLeftView(boolean animated) + { + SlidingMenu menu = slidingMenu.get(); + menu.showMenu(animated); + } + + private void handleOpenRightView(boolean animated) + { + SlidingMenu menu = slidingMenu.get(); + menu.showSecondaryMenu(animated); + } + + private void handleCloseLeftView(boolean animated) + { + SlidingMenu menu = slidingMenu.get(); + if (menu.isMenuShowing()) + menu.showContent(animated); + } + + private void handleCloseRightView(boolean animated) + { + SlidingMenu menu = slidingMenu.get(); + if (menu.isSecondaryMenuShowing()) + menu.showContent(animated); + } + + private void handleCloseViews(boolean animated) + { + SlidingMenu menu = slidingMenu.get(); + if (menu.isMenuShowing() || menu.isSecondaryMenuShowing()) + menu.showContent(animated); + } + + public SlidingMenu getSlidingMenu(){ + return slidingMenu.get(); + } + + /* + * METHODS + */ + + @Kroll.method + public void toggleLeftWindow(@Kroll.argument(optional = true) Object obj) + { + Boolean animated = true; + if (obj != null) { + animated = TiConvert.toBoolean(obj); + } + + if (TiApplication.isUIThread()) { + handleToggleLeftView(animated); + return; + } + Message message = getMainHandler().obtainMessage(MSG_TOGGLE_LEFT_VIEW, animated); + message.sendToTarget(); + } + + @Kroll.method + public void toggleRightWindow(@Kroll.argument(optional = true) Object obj) + { + Boolean animated = true; + if (obj != null) { + animated = TiConvert.toBoolean(obj); + } + + if (TiApplication.isUIThread()) { + handleToggleRightView(animated); + return; + } + Message message = getMainHandler().obtainMessage(MSG_TOGGLE_RIGHT_VIEW, animated); + message.sendToTarget(); + } + + @Kroll.method + public void openLeftWindow(@Kroll.argument(optional = true) Object obj) + { + Boolean animated = true; + if (obj != null) { + animated = TiConvert.toBoolean(obj); + } + + if (TiApplication.isUIThread()) { + handleOpenLeftView(animated); + return; + } + Message message = getMainHandler().obtainMessage(MSG_OPEN_LEFT_VIEW, animated); + message.sendToTarget(); + } + + @Kroll.method + public void openRightWindow(@Kroll.argument(optional = true) Object obj) + { + Boolean animated = true; + if (obj != null) { + animated = TiConvert.toBoolean(obj); + } + + if (TiApplication.isUIThread()) { + handleOpenRightView(animated); + return; + } + Message message = getMainHandler().obtainMessage(MSG_OPEN_RIGHT_VIEW, animated); + message.sendToTarget(); + } + + @Kroll.method + public void closeLeftWindow(@Kroll.argument(optional = true) Object obj) + { + Boolean animated = true; + if (obj != null) { + animated = TiConvert.toBoolean(obj); + } + + if (TiApplication.isUIThread()) { + handleCloseLeftView(animated); + return; + } + Message message = getMainHandler().obtainMessage(MSG_CLOSE_LEFT_VIEW, animated); + message.sendToTarget(); + } + + @Kroll.method + public void closeRightWindow(@Kroll.argument(optional = true) Object obj) + { + Boolean animated = true; + if (obj != null) { + animated = TiConvert.toBoolean(obj); + } + + if (TiApplication.isUIThread()) { + handleCloseRightView(animated); + return; + } + Message message = getMainHandler().obtainMessage(MSG_CLOSE_RIGHT_VIEW, animated); + message.sendToTarget(); + } + + @Kroll.method + public void closeWindows(@Kroll.argument(optional = true) Object obj) + { + Boolean animated = true; + if (obj != null) { + animated = TiConvert.toBoolean(obj); + } + + if (TiApplication.isUIThread()) { + handleCloseViews(animated); + return; + } + Message message = getMainHandler().obtainMessage(MSG_CLOSE_VIEWS, animated); + message.sendToTarget(); + } + + @Kroll.method + public boolean isLeftWindowOpen() + { + return getSlidingMenu().isMenuShowing(); + } + + @Kroll.method + public boolean isRightWindowOpen() + { + return getSlidingMenu().isSecondaryMenuShowing(); + } + + @Kroll.method + public boolean isAnyWindowOpen() + { + SlidingMenu menu = getSlidingMenu(); + return menu.isSecondaryMenuShowing() || menu.isMenuShowing(); + } + + @Kroll.method + public int getRealLeftViewWidth() + { + SlidingMenu menu = slidingMenu.get(); + return menu.getBehindOffset(); + } + + @Kroll.method + public int getRealRightViewWidth() + { + return getSlidingMenu().getBehindOffset(); + } + + /* + * PROPERTIES + */ + + @Kroll.method @Kroll.setProperty + @Override + public void setOrientationModes(int[] modes) { + // Unlike Windows this setter is not defined in JavaScript. + // We need to expose it here with an annotation. + super.setOrientationModes(modes); + } + + // Parallax + @Kroll.method @Kroll.getProperty + public float getParallaxAmount() { + SlidingMenu menu = slidingMenu.get(); + return menu.getBehindScrollScale(); + } + + @Kroll.method @Kroll.setProperty + public void setParallaxAmount(Object value){ + SlidingMenu menu = slidingMenu.get(); + menu.setBehindScrollScale(TiConvert.toFloat(value)); + } + + @Kroll.method @Kroll.setProperty + public void setFading(Object arg){ + setPropertyAndFire(Drawer.PROPERTY_FADING, arg); + } + + // window setters.. + @Kroll.method @Kroll.setProperty + public void setLeftWindow(Object arg){ + setPropertyAndFire(Drawer.PROPERTY_LEFT_VIEW, arg); + } + + @Kroll.method @Kroll.setProperty + public void setRightWindow(Object arg){ + setPropertyAndFire(Drawer.PROPERTY_RIGHT_VIEW, arg); + } + + @Kroll.method @Kroll.setProperty + public void setCenterWindow(Object arg){ + setPropertyAndFire(Drawer.PROPERTY_CENTER_VIEW, arg); + } + + // Drawer width + @Kroll.method @Kroll.setProperty + public void setLeftDrawerWidth(Object arg){ + setPropertyAndFire(Drawer.PROPERTY_LEFT_VIEW_WIDTH, arg); + } + + @Kroll.method @Kroll.setProperty + public void setRightDrawerWidth(Object arg){ + setPropertyAndFire(Drawer.PROPERTY_RIGHT_VIEW_WIDTH, arg); + } + + // Shadow + @Kroll.method @Kroll.setProperty + public void setShadowWidth(Object arg){ + setPropertyAndFire(Drawer.PROPERTY_SHADOW_WIDTH, arg); + } + + @Kroll.method @Kroll.setProperty + public void setOpenDrawerGestureMode(Object arg){ + setPropertyAndFire(Drawer.PROPERTY_OPEN_MODE, arg); + } + + // Gesture & animation modes + @Kroll.method @Kroll.setProperty + public void setCloseDrawerGestureMode(Object arg){ + setPropertyAndFire(Drawer.PROPERTY_CLOSE_MODE, arg); + } + + @Kroll.method @Kroll.setProperty + public void setAnimationMode(Object arg){ + setPropertyAndFire(Drawer.PROPERTY_ANIMATION_MODE, arg); + } + + + @Kroll.method + @Kroll.setProperty + public void setHamburgerIcon(Object arg) { + setPropertyAndFire(Drawer.PROPERTY_HAMBURGER_ICON, arg); + } + + @Kroll.method + @Kroll.setProperty + public void setHamburgerIconColor(Object arg) { + setPropertyAndFire(Drawer.PROPERTY_HAMBURGER_ICON_COLOR, arg); + } + + @Kroll.method + @Kroll.setProperty + public void setArrowAnimation(Object arg) { + setPropertyAndFire(Drawer.PROPERTY_ARROW_ANIMATION, arg); + } +} diff --git a/android/src/dk/napp/drawer/NappdrawerModule.java b/android/src/dk/napp/drawer/NappdrawerModule.java new file mode 100755 index 0000000..09e517b --- /dev/null +++ b/android/src/dk/napp/drawer/NappdrawerModule.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2010-2013 by Napp ApS + * www.napp.dk + * Author Mads Møller + * + * Special thanks to Martin Guillon + * + * Appcelerator Titanium is Copyright (c) 2009-2013 by Appcelerator, Inc. + * and licensed under the Apache Public License (version 2) + */ + +package dk.napp.drawer; + +import org.appcelerator.kroll.KrollModule; +import org.appcelerator.kroll.annotations.Kroll; + +import org.appcelerator.titanium.TiApplication; +import org.appcelerator.kroll.common.TiConfig; + + +@Kroll.module(name="Nappdrawer", id="dk.napp.drawer") +public class NappdrawerModule extends KrollModule +{ + private static final String LCAT = "NappDrawerModule"; + private static final boolean DBG = TiConfig.LOGD; + + // constants + @Kroll.constant public static final int LEFT_WINDOW = 1; + @Kroll.constant public static final int RIGHT_WINDOW = 3; + @Kroll.constant public static final int CENTER_WINDOW = 2; + + @Kroll.constant public static final int OPEN_MODE_MARGIN = 0; + @Kroll.constant public static final int OPEN_MODE_ALL = 1; + @Kroll.constant public static final int OPEN_MODE_NONE = 2; + + @Kroll.constant public static final int CLOSE_MODE_MARGIN = 0; + @Kroll.constant public static final int CLOSE_MODE_ALL = 1; + @Kroll.constant public static final int CLOSE_MODE_NONE = 2; + + @Kroll.constant public static final int ANIMATION_NONE = 1; + @Kroll.constant public static final int ANIMATION_SLIDEUP = 2; + @Kroll.constant public static final int ANIMATION_ZOOM = 3; + @Kroll.constant public static final int ANIMATION_SCALE = 4; + + public NappdrawerModule() + { + super(); + } + + @Kroll.onAppCreate + public static void onAppCreate(TiApplication app) + { + // lets do nothing :) + } + +} + diff --git a/android/src/dk/napp/drawer/RHelper.java b/android/src/dk/napp/drawer/RHelper.java new file mode 100755 index 0000000..7c34a45 --- /dev/null +++ b/android/src/dk/napp/drawer/RHelper.java @@ -0,0 +1,79 @@ +package dk.napp.drawer; + +import org.appcelerator.titanium.util.TiRHelper; +import org.appcelerator.titanium.util.TiRHelper.ResourceNotFoundException; + +public class RHelper { + + public static int getString(String str) { + try { + return TiRHelper.getApplicationResource("string." + str); + } catch (ResourceNotFoundException e) { + e.printStackTrace(); + return 0; + } + } + + public static int getLayout(String str) { + try { + return TiRHelper.getApplicationResource("layout." + str); + } catch (ResourceNotFoundException e) { + e.printStackTrace(); + return 0; + } + } + + public static int getId(String str) { + try { + return TiRHelper.getApplicationResource("id." + str); + } catch (ResourceNotFoundException e) { + e.printStackTrace(); + return 0; + } + } + + public static int getDrawable(String str) { + try { + return TiRHelper.getApplicationResource("drawable." + str); + } catch (ResourceNotFoundException e) { + e.printStackTrace(); + return 0; + } + } + + public static int getColor(String str) { + try { + return TiRHelper.getApplicationResource("color." + str); + } catch (ResourceNotFoundException e) { + e.printStackTrace(); + return 0; + } + } + + public static int getRaw(String str) { + try { + return TiRHelper.getApplicationResource("raw." + str); + } catch (ResourceNotFoundException e) { + e.printStackTrace(); + return 0; + } + } + + public static int getXML(String str) { + try { + return TiRHelper.getApplicationResource("xml." + str); + } catch (ResourceNotFoundException e) { + e.printStackTrace(); + return 0; + } + } + + public static int getAnimator(String str) { + try { + return TiRHelper.getApplicationResource("animator." + str); + } catch (ResourceNotFoundException e) { + e.printStackTrace(); + return 0; + } + } +} diff --git a/android/timodule.xml b/android/timodule.xml new file mode 100755 index 0000000..416eabd --- /dev/null +++ b/android/timodule.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/appc-npm b/appc-npm new file mode 100755 index 0000000..6c68269 --- /dev/null +++ b/appc-npm @@ -0,0 +1,263 @@ +#!/usr/bin/env node + +var path = require('path'); +var fs = require('fs'); +var child_process = require('child_process'); + +var OS_WINDOWS = (process.platform === 'win32'); + +install(); + +function install() { + var pkg; + + try { + pkg = JSON.parse(fs.readFileSync('package.json', { + encoding: 'utf-8' + })); + } catch (e) { + die('Could not read package.json'); + } + + var appcNPM = pkg['appc-npm']; + + if (typeof appcNPM !== 'object') { + die('Could not find \'appc-npm\' in package.json'); + } + + var target = findTarget(); + + if (!target) { + die('Could not find project'); + } + + var targetPath = (typeof appcNPM.target === 'object') ? appcNPM.target[target.name] : appcNPM.target; + + if (typeof targetPath !== 'string') { + die('Could not find \'appc-npm.target.' + target.name + '\' or \'appc-npm.target\' in package.json'); + } + + targetPath = path.join(target.base, targetPath); + + var zipFiles = toArray(appcNPM.unzip); + + if (zipFiles.length > 0) { + + if (OS_WINDOWS) { + mkdirsSync(targetPath); + } + + unzip(zipFiles, targetPath); + + } else { + var ignore = toArray(appcNPM.ignore); + ignore.push('appc-npm'); + + try { + copySync(__dirname, targetPath, function (fullPath) { + var relPath = fullPath.substr(__dirname.length + 1); + + return ignore.indexOf(relPath) === -1; + }); + + } catch (e) { + die('Could not copy \'' + __dirname + '\' to \'' + targetPath + '\''); + } + + console.log('Copied \'' + __dirname + '\' to \'' + targetPath + '\''); + + console.log(); + process.exit(0); + } +} + +function unzip(files, targetPath) { + var file = files.shift(); + var filePath = path.join(__dirname, file); + var command; + + if (OS_WINDOWS) { + command = '/c cd "' + targetPath + '" && jar xf "' + filePath + '"'; + + } else { + command = 'unzip "' + filePath + '" -d "' + targetPath + '"'; + } + + return child_process.exec(command, function(error, stdout, stderr) { + + if (error) { + + var err = 'Could not unzip \'' + filePath + '\': ' + stderr; + + if (OS_WINDOWS) { + var EOL = require('os').EOL; + err += EOL + 'Make sure you have Java JDK installed and added to PATH: ' + EOL + 'http://docs.oracle.com/javase/8/docs/technotes/guides/install/windows_jdk_install.html#BABGDJFH'; + } + + die(err); + + } else { + console.log('Unzipped \'' + filePath + '\' to \'' + targetPath + '\''); + + if (files.length > 0) { + unzip(files, targetPath); + + } else { + console.log(); + process.exit(0); + } + } + }); +} + +function findTarget(dir) { + + if (dir) { + + if (fs.existsSync(path.join(dir, 'appc.json'))) { + return { + name: 'arrow', + base: dir + }; + } else if (fs.existsSync(path.join(dir, 'app', 'controllers', 'index.js'))) { + return { + name: 'alloy', + base: dir + }; + } else if (fs.existsSync(path.join(dir, 'tiapp.xml'))) { + return { + name: 'titanium', + base: dir + }; + } + + } else { + dir = __dirname; + } + + dirUp = path.resolve(dir, '..', '..'); + + if (!dirUp || dirUp === dir) { + return; + } + + return findTarget(dirUp); +} + +function die(err) { + console.error(err); + console.error(); + process.exit(1); +} + +function toArray(val) { + + if (typeof val === 'string') { + return [val]; + } else if (Object.prototype.toString.call(val) === '[object Array]') { + return val; + } else { + return []; + } + +} + +/* jshint ignore:start */ + +// https://github.com/jprichardson/node-fs-extra/blob/master/lib/copy/copy-sync.js +function copySync (src, dest, options) { + if (typeof options === 'function' || options instanceof RegExp) { + options = {filter: options} + } + + options = options || {} + options.recursive = !!options.recursive + + // default to true for now + options.clobber = 'clobber' in options ? !!options.clobber : true + + options.filter = options.filter || function () { return true } + + var stats = options.recursive ? fs.lstatSync(src) : fs.statSync(src) + var destFolder = path.dirname(dest) + var destFolderExists = fs.existsSync(destFolder) + var performCopy = false + + if (stats.isFile()) { + if (options.filter instanceof RegExp) performCopy = options.filter.test(src) + else if (typeof options.filter === 'function') performCopy = options.filter(src) + + if (performCopy) { + if (!destFolderExists) mkdirsSync(destFolder) + copyFileSync(src, dest, options.clobber) + } + } else if (stats.isDirectory()) { + if (!fs.existsSync(dest)) mkdirsSync(dest) + var contents = fs.readdirSync(src) + contents.forEach(function (content) { + copySync(path.join(src, content), path.join(dest, content), {filter: options.filter, recursive: true}) + }) + } else if (options.recursive && stats.isSymbolicLink()) { + var srcPath = fs.readlinkSync(src) + fs.symlinkSync(srcPath, dest) + } +} + +// https://github.com/jprichardson/node-fs-extra/blob/master/lib/copy/copy-file-sync.js +function copyFileSync (srcFile, destFile, clobber) { + + if (fs.existsSync(destFile) && !clobber) { + throw Error('EEXIST') + } + + // simplified to work with vanilla fs + fs.createReadStream(srcFile).pipe(fs.createWriteStream(destFile)); +} + +// https://github.com/jprichardson/node-fs-extra/blob/master/lib/mkdirs/mkdirs.js +var o777 = parseInt('0777', 8) + +function mkdirsSync (p, opts, made) { + if (!opts || typeof opts !== 'object') { + opts = { mode: opts } + } + + var mode = opts.mode + var xfs = opts.fs || fs + + if (mode === undefined) { + mode = o777 & (~process.umask()) + } + if (!made) made = null + + p = path.resolve(p) + + try { + xfs.mkdirSync(p)//, mode) + + + diff --git a/ios/titanium.xcconfig b/ios/titanium.xcconfig new file mode 100755 index 0000000..3910065 --- /dev/null +++ b/ios/titanium.xcconfig @@ -0,0 +1,14 @@ +// +// +// CHANGE THESE VALUES TO REFLECT THE VERSION (AND LOCATION IF DIFFERENT) +// OF YOUR TITANIUM SDK YOU'RE BUILDING FOR +// +// +TITANIUM_SDK_VERSION = 10.1.0nightly + +// +// THESE SHOULD BE OK GENERALLY AS-IS +// +TITANIUM_SDK = /Users/$(USER)/Library/Application Support/Titanium/mobilesdk/osx/$(TITANIUM_SDK_VERSION) +HEADER_SEARCH_PATHS = $(inherited) "$(TITANIUM_SDK)/iphone/include" +FRAMEWORK_SEARCH_PATHS = $(inherited) "$(TITANIUM_SDK)/iphone/Frameworks/**" diff --git a/package.json b/package.json new file mode 100755 index 0000000..30c70b1 --- /dev/null +++ b/package.json @@ -0,0 +1,40 @@ +{ + "version": "2.0.0", + "description": "A side drawer navigation container view controller for Appcelerator Titanium.", + "author": "Mads Møller", + "license": "MIT", + "name": "ti-module-dk.napp.drawer", + "appc-npm": { + "target": { + "titanium": "/", + "alloy": "/" + }, + "unzip": [ + "ios/dist/dk.napp.drawer-iphone-2.0.0.zip", + "android/dist/dk.napp.drawer-android-2.0.2.zip" + ] + }, + "files": [ + "appc-npm", + "ios/dist/dk.napp.drawer-iphone-2.0.0.zip", + "android/dist/dk.napp.drawer-android-2.0.2.zip" + ], + "scripts": { + "postinstall": "node ./appc-npm" + }, + "keywords": [ + "appcelerator", + "appc-npm", + "ti-module", + "titanium", + "alloy" + ], + "repository": { + "type": "git", + "url": "git://github.com/viezel/NappDrawer.git" + }, + "homepage": "https://github.com/viezel/NappDrawer", + "bugs": { + "url": "https://github.com/viezel/NappDrawer/issues" + } +}