diff --git a/README.md b/README.md index b28d3ef..12ece21 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ for Android, iOS and WP8, by [Eddy Verbruggen](http://www.x-services.nl/phonegap 3. [Manually](#manually) 3. [PhoneGap Build](#phonegap-build) 4. [Usage](#4-usage) + 4. [Styling](#styling) 5. [Credits](#5-credits) 6. [Changelog](#6-changelog) 7. [License](#7-license) @@ -48,10 +49,18 @@ iOS ![ScreenShot](screenshots/screenshot-ios-toast.png) +A few styling options + +![ScreenShot](screenshots/styling-green.png) + +![ScreenShot](screenshots/styling-red.png) + + Android ![ScreenShot](screenshots/screenshot-android-toast.png) + Windows Phone 8 ![ScreenShot](screenshots/screenshot-wp8.jpg) @@ -203,6 +212,30 @@ called again. You can distinguish between those events of course: ); ``` +### Styling +Since version 2.4.0 you can pass an optional `styling` object to the plugin. +The defaults make sure the Toast looks the same as when you would not pass in the `styling` object at all. + +Note that on WP this object is currently ignored. + +```js + window.plugins.toast.showWithOptions({ + message: "hey there", + duration: "short", + position: "bottom", + styling: { + opacity: 0.75, // 0.0 (transparent) to 1.0 (opaque). Default 0.8 + backgroundColor: '#FF0000', // make sure you use #RRGGBB. Default #333333 + cornerRadius: 16, // minimum is 0 (square). iOS default 20, Android default 100 + horizontalPadding: 20, // iOS default 16, Android default 50 + verticalPadding: 16 // iOS default 12, Android default 30 + } + }); +``` + +Tip: if you need to pass different values for iOS and Android you can use fi. the device plugin +to determine the platform and pass `opacity: isAndroid() ? 0.7 : 0.9`. + ### WP8 quirks The WP8 implementation needs a little more work, but it's perfectly useable when you keep this in mind: * You can't show two Toasts simultaneously. @@ -217,15 +250,12 @@ The Android code was entirely created by me. For iOS most credits go to this excellent [Toast for iOS project by Charles Scalesse] (https://github.com/scalessec/Toast). ## 6. CHANGELOG -2.3.2: The click event introduced with 2.3.0 did not work with Android 5+. - -2.3.0: The plugin will now report back to JS if Toasts were tapped by the user. - -2.0.1: iOS messages are hidden when another one is shown. [Thanks Richie Min!](https://github.com/EddyVerbruggen/Toast-PhoneGap-Plugin/pull/13) - -2.0: WP8 support - -1.0: initial version supporting Android and iOS +- 2.4.0: You can now style the Toast with a number of properties. See +- 2.3.2: The click event introduced with 2.3.0 did not work with Android 5+. +- 2.3.0: The plugin will now report back to JS if Toasts were tapped by the user. +- 2.0.1: iOS messages are hidden when another one is shown. [Thanks Richie Min!](https://github.com/EddyVerbruggen/Toast-PhoneGap-Plugin/pull/13) +- 2.0: WP8 support +- 1.0: initial version supporting Android and iOS ## 7. License diff --git a/package.json b/package.json index 8e6cae3..3571666 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-x-toast", - "version": "2.3.2", + "version": "2.4.0", "description": "This plugin allows you to show a Toast. A Toast is a little non intrusive buttonless popup which automatically disappears.", "cordova": { "id": "cordova-plugin-x-toast", diff --git a/plugin.xml b/plugin.xml index 4ea80a7..8028b8e 100755 --- a/plugin.xml +++ b/plugin.xml @@ -2,7 +2,7 @@ + version="2.4.0"> Toast diff --git a/screenshots/styling-green.png b/screenshots/styling-green.png new file mode 100644 index 0000000..f325fe0 Binary files /dev/null and b/screenshots/styling-green.png differ diff --git a/screenshots/styling-red.png b/screenshots/styling-red.png new file mode 100644 index 0000000..aac1432 Binary files /dev/null and b/screenshots/styling-red.png differ diff --git a/src/android/nl/xservices/plugins/Toast.java b/src/android/nl/xservices/plugins/Toast.java index dc58576..580daac 100644 --- a/src/android/nl/xservices/plugins/Toast.java +++ b/src/android/nl/xservices/plugins/Toast.java @@ -1,5 +1,7 @@ package nl.xservices.plugins; +import android.graphics.Color; +import android.graphics.drawable.GradientDrawable; import android.os.Build; import android.view.Gravity; import android.view.MotionEvent; @@ -37,7 +39,7 @@ public class Toast extends CordovaPlugin { private android.widget.Toast mostRecentToast; private ViewGroup viewGroup; - private static final boolean IS_AT_LEAST_ANDROID5 = Build.VERSION.SDK_INT >= 21; + private static final boolean IS_AT_LEAST_LOLLIPOP = Build.VERSION.SDK_INT >= 21; // note that webView.isPaused() is not Xwalk compatible, so tracking it poor-man style private boolean isPaused; @@ -65,20 +67,15 @@ public class Toast extends CordovaPlugin { final String position = options.getString("position"); final int addPixelsY = options.has("addPixelsY") ? options.getInt("addPixelsY") : 0; final JSONObject data = options.has("data") ? options.getJSONObject("data") : null; + final JSONObject styling = options.optJSONObject("styling"); cordova.getActivity().runOnUiThread(new Runnable() { public void run() { final android.widget.Toast toast = android.widget.Toast.makeText( - IS_AT_LEAST_ANDROID5 ? cordova.getActivity().getWindow().getContext() : cordova.getActivity().getApplicationContext(), + IS_AT_LEAST_LOLLIPOP ? cordova.getActivity().getWindow().getContext() : cordova.getActivity().getApplicationContext(), message, "short".equals(duration) ? android.widget.Toast.LENGTH_SHORT : android.widget.Toast.LENGTH_LONG); - // if we want to change the background color some day, we can use this -// try { -// final Method setTintMethod = Drawable.class.getMethod("setTint", int.class); -// setTintMethod.invoke(toast.getView().getBackground(), Color.RED); // default is Color.DKGRAY -// } catch (Exception ignore) { -// } if ("top".equals(position)) { toast.setGravity(GRAVITY_TOP, 0, BASE_TOP_BOTTOM_OFFSET + addPixelsY); } else if ("bottom".equals(position)) { @@ -90,9 +87,32 @@ public class Toast extends CordovaPlugin { return; } + // if one of the custom layout options have been passed in, draw our own shape + if (styling != null && Build.VERSION.SDK_INT >= 16) { + + // the defaults mimic the default toast as close as possible + final String backgroundColor = styling.optString("backgroundColor", "#333333"); + final double opacity = styling.optDouble("opacity", 0.8); + final int cornerRadius = styling.optInt("cornerRadius", 100); + final int horizontalPadding = styling.optInt("horizontalPadding", 50); + final int verticalPadding = styling.optInt("verticalPadding", 30); + + GradientDrawable shape = new GradientDrawable(); + shape.setCornerRadius(cornerRadius); + shape.setAlpha((int)(opacity * 255)); // 0-255, where 0 is an invisible background + shape.setColor(Color.parseColor(backgroundColor)); + toast.getView().setBackground(shape); + toast.getView().setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding); + + // this gives the toast a very subtle shadow on newer devices + if (Build.VERSION.SDK_INT >= 21) { + toast.getView().setElevation(6); + } + } + // On Android >= 5 you can no longer rely on the 'toast.getView().setOnTouchListener', // so created something funky that compares the Toast position to the tap coordinates. - if (IS_AT_LEAST_ANDROID5) { + if (IS_AT_LEAST_LOLLIPOP) { getViewGroup().setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { diff --git a/src/ios/Toast+UIView.h b/src/ios/Toast+UIView.h index 346f853..b2b1e7f 100644 --- a/src/ios/Toast+UIView.h +++ b/src/ios/Toast+UIView.h @@ -6,7 +6,7 @@ // each makeToast method creates a view and displays it as toast - (void)makeToast:(NSString *)message; - (void)makeToast:(NSString *)message duration:(NSTimeInterval)interval position:(id)position; -- (void)makeToast:(NSString *)message duration:(NSTimeInterval)duration position:(id)position addPixelsY:(int)addPixelsY data:(NSDictionary*)data commandDelegate:(id )commandDelegate callbackId:(NSString *)callbackId; +- (void)makeToast:(NSString *)message duration:(NSTimeInterval)duration position:(id)position addPixelsY:(int)addPixelsY data:(NSDictionary*)data styling:(NSDictionary*)styling commandDelegate:(id )commandDelegate callbackId:(NSString *)callbackId; - (void)makeToast:(NSString *)message duration:(NSTimeInterval)interval position:(id)position image:(UIImage *)image; - (void)makeToast:(NSString *)message duration:(NSTimeInterval)interval position:(id)position title:(NSString *)title; - (void)makeToast:(NSString *)message duration:(NSTimeInterval)interval position:(id)position title:(NSString *)title image:(UIImage *)image; diff --git a/src/ios/Toast+UIView.m b/src/ios/Toast+UIView.m index 83b32d6..4d4cf1b 100644 --- a/src/ios/Toast+UIView.m +++ b/src/ios/Toast+UIView.m @@ -53,6 +53,7 @@ static id commandDelegate; static id callbackId; static id msg; static id data; +static id styling; @interface UIView (ToastPrivate) @@ -79,13 +80,22 @@ static id data; [self showToast:toast duration:duration position:position]; } -- (void)makeToast:(NSString *)message duration:(NSTimeInterval)duration position:(id)position addPixelsY:(int)addPixelsY data:(NSDictionary*)_data commandDelegate:(id )_commandDelegate callbackId:(NSString *)_callbackId { - commandDelegate = _commandDelegate; - callbackId = _callbackId; - msg = message; - data = _data; - UIView *toast = [self viewForMessage:message title:nil image:nil]; - [self showToast:toast duration:duration position:position addedPixelsY:addPixelsY]; +- (void)makeToast:(NSString *)message + duration:(NSTimeInterval)duration + position:(id)position addPixelsY:(int)addPixelsY + data:(NSDictionary*)_data + styling:(NSDictionary*)_styling + commandDelegate:(id )_commandDelegate + callbackId:(NSString *)_callbackId { + + commandDelegate = _commandDelegate; + callbackId = _callbackId; + msg = message; + data = _data; + styling = _styling; + + UIView *toast = [self viewForMessage:message title:nil image:nil]; + [self showToast:toast duration:duration position:position addedPixelsY:addPixelsY]; } - (void)makeToast:(NSString *)message duration:(NSTimeInterval)duration position:(id)position title:(NSString *)title { @@ -295,7 +305,9 @@ static id data; // create the parent view UIView *wrapperView = [[UIView alloc] init]; wrapperView.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin); - wrapperView.layer.cornerRadius = CSToastCornerRadius; + + NSNumber * cornerRadius = styling[@"cornerRadius"]; + wrapperView.layer.cornerRadius = cornerRadius == nil ? CSToastCornerRadius : [cornerRadius floatValue]; if (CSToastDisplayShadow) { wrapperView.layer.shadowColor = [UIColor blackColor].CGColor; @@ -304,12 +316,23 @@ static id data; wrapperView.layer.shadowOffset = CSToastShadowOffset; } - wrapperView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:CSToastOpacity]; + NSString * backgroundColor = styling[@"backgroundColor"]; + UIColor *theColor = backgroundColor == nil ? [UIColor blackColor] : [self colorFromHexString:backgroundColor]; + + NSNumber * opacity = styling[@"opacity"]; + CGFloat theOpacity = opacity == nil ? CSToastOpacity : [opacity floatValue]; + + NSNumber * horizontalPadding = styling[@"horizontalPadding"]; + NSNumber * verticalPadding = styling[@"verticalPadding"]; + CGFloat theHorizontalPadding = horizontalPadding == nil ? CSToastHorizontalPadding : [horizontalPadding floatValue]; + CGFloat theVerticalPadding = verticalPadding == nil ? CSToastVerticalPadding : [verticalPadding floatValue]; + + wrapperView.backgroundColor = [theColor colorWithAlphaComponent:theOpacity]; if(image != nil) { imageView = [[UIImageView alloc] initWithImage:image]; imageView.contentMode = UIViewContentModeScaleAspectFit; - imageView.frame = CGRectMake(CSToastHorizontalPadding, CSToastVerticalPadding, CSToastImageViewWidth, CSToastImageViewHeight); + imageView.frame = CGRectMake(theHorizontalPadding, theVerticalPadding, CSToastImageViewWidth, CSToastImageViewHeight); } CGFloat imageWidth, imageHeight, imageLeft; @@ -318,7 +341,7 @@ static id data; if(imageView != nil) { imageWidth = imageView.bounds.size.width; imageHeight = imageView.bounds.size.height; - imageLeft = CSToastHorizontalPadding; + imageLeft = theHorizontalPadding; } else { imageWidth = imageHeight = imageLeft = 0.0; } @@ -362,8 +385,8 @@ static id data; if(titleLabel != nil) { titleWidth = titleLabel.bounds.size.width; titleHeight = titleLabel.bounds.size.height; - titleTop = CSToastVerticalPadding; - titleLeft = imageLeft + imageWidth + CSToastHorizontalPadding; + titleTop = theVerticalPadding; + titleLeft = imageLeft + imageWidth + theHorizontalPadding; } else { titleWidth = titleHeight = titleTop = titleLeft = 0.0; } @@ -374,8 +397,8 @@ static id data; if(messageLabel != nil) { messageWidth = messageLabel.bounds.size.width; messageHeight = messageLabel.bounds.size.height; - messageLeft = imageLeft + imageWidth + CSToastHorizontalPadding; - messageTop = titleTop + titleHeight + CSToastVerticalPadding; + messageLeft = imageLeft + imageWidth + theHorizontalPadding; + messageTop = titleTop + titleHeight + theVerticalPadding; } else { messageWidth = messageHeight = messageLeft = messageTop = 0.0; } @@ -384,8 +407,8 @@ static id data; CGFloat longerLeft = MAX(titleLeft, messageLeft); // wrapper width uses the longerWidth or the image width, whatever is larger. same logic applies to the wrapper height - CGFloat wrapperWidth = MAX((imageWidth + (CSToastHorizontalPadding * 2)), (longerLeft + longerWidth + CSToastHorizontalPadding)); - CGFloat wrapperHeight = MAX((messageTop + messageHeight + CSToastVerticalPadding), (imageHeight + (CSToastVerticalPadding * 2))); + CGFloat wrapperWidth = MAX((imageWidth + (theHorizontalPadding * 2)), (longerLeft + longerWidth + theHorizontalPadding)); + CGFloat wrapperHeight = MAX((messageTop + messageHeight + theVerticalPadding), (imageHeight + (theVerticalPadding * 2))); wrapperView.frame = CGRectMake(0.0, 0.0, wrapperWidth, wrapperHeight); @@ -406,4 +429,13 @@ static id data; return wrapperView; } +// Assumes input like "#00FF00" (#RRGGBB) +- (UIColor*) colorFromHexString:(NSString*) hexString { + unsigned rgbValue = 0; + NSScanner *scanner = [NSScanner scannerWithString:hexString]; + [scanner setScanLocation:1]; // bypass '#' character + [scanner scanHexInt:&rgbValue]; + return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16) / 255.0 green:((rgbValue & 0xFF00) >> 8) / 255.0 blue:(rgbValue & 0xFF) / 255.0 alpha:1.0]; +} + @end diff --git a/src/ios/Toast.m b/src/ios/Toast.m index 54e59b4..bd8e4e9 100644 --- a/src/ios/Toast.m +++ b/src/ios/Toast.m @@ -6,13 +6,14 @@ - (void)show:(CDVInvokedUrlCommand*)command { - NSDictionary* options = [command.arguments objectAtIndex:0]; + NSDictionary* options = [command argumentAtIndex:0]; - NSString *message = [options objectForKey:@"message"]; - NSString *duration = [options objectForKey:@"duration"]; - NSString *position = [options objectForKey:@"position"]; - NSDictionary *data = [options objectForKey:@"data"]; - NSNumber *addPixelsY = [options objectForKey:@"addPixelsY"]; + NSString *message = options[@"message"]; + NSString *duration = options[@"duration"]; + NSString *position = options[@"position"]; + NSDictionary *data = options[@"data"]; + NSNumber *addPixelsY = options[@"addPixelsY"]; + NSDictionary *styling = options[@"styling"]; if (![position isEqual: @"top"] && ![position isEqual: @"center"] && ![position isEqual: @"bottom"]) { CDVPluginResult * pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"invalid position. valid options are 'top', 'center' and 'bottom'"]; @@ -36,6 +37,7 @@ position:position addPixelsY:addPixelsY == nil ? 0 : [addPixelsY intValue] data:data + styling:styling commandDelegate:self.commandDelegate callbackId:command.callbackId];