diff --git a/android/build.gradle b/android/build.gradle
index 099e2d2ac5d..86eb3e36fad 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -11,6 +11,7 @@ buildscript {
repositories {
google()
mavenCentral()
+ maven { url 'https://jitpack.io' }
}
dependencies {
classpath 'com.android.tools.build:gradle:8.8.2'
diff --git a/android/modules/ui/res/values/attrs.xml b/android/modules/ui/res/values/attrs.xml
index dbc78c01f74..c8bf2371b06 100644
--- a/android/modules/ui/res/values/attrs.xml
+++ b/android/modules/ui/res/values/attrs.xml
@@ -10,4 +10,7 @@
+
+
+
diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/BlurViewProxy.java b/android/modules/ui/src/java/ti/modules/titanium/ui/BlurViewProxy.java
new file mode 100644
index 00000000000..54f91b85861
--- /dev/null
+++ b/android/modules/ui/src/java/ti/modules/titanium/ui/BlurViewProxy.java
@@ -0,0 +1,37 @@
+ /**
+ * Titanium SDK
+ * Copyright TiDev, Inc. 04/07/2022-Present
+ * Licensed under the terms of the Apache Public License
+ * Please see the LICENSE included with this distribution for details.
+ */
+package ti.modules.titanium.ui;
+
+import android.app.Activity;
+
+import org.appcelerator.kroll.annotations.Kroll;
+import org.appcelerator.titanium.proxy.TiViewProxy;
+import org.appcelerator.titanium.view.TiUIView;
+
+import ti.modules.titanium.ui.widget.TiUIBlurView;
+
+@Kroll.proxy(creatableInModule = UIModule.class)
+public class BlurViewProxy extends TiViewProxy
+{
+ public BlurViewProxy()
+ {
+ super();
+ }
+
+ @Override
+ public TiUIView createView(Activity activity)
+ {
+ return new TiUIBlurView(this);
+ }
+
+ @Override
+ public String getApiName()
+ {
+ return "Ti.UI.BlurView";
+ }
+}
+
diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/UIModule.java b/android/modules/ui/src/java/ti/modules/titanium/ui/UIModule.java
index 19d77525a1f..684f8d0fb94 100644
--- a/android/modules/ui/src/java/ti/modules/titanium/ui/UIModule.java
+++ b/android/modules/ui/src/java/ti/modules/titanium/ui/UIModule.java
@@ -220,6 +220,14 @@ public class UIModule extends KrollModule implements TiApplication.Configuration
@Kroll.constant
public static final int BUTTON_STYLE_OPTION_NEUTRAL = 6;
+ // BlurView styles (cross-platform constants)
+ @Kroll.constant
+ public static final int BLUR_EFFECT_STYLE_EXTRA_LIGHT = 0;
+ @Kroll.constant
+ public static final int BLUR_EFFECT_STYLE_LIGHT = 1;
+ @Kroll.constant
+ public static final int BLUR_EFFECT_STYLE_DARK = 2;
+
@Kroll.constant
public static final int DATE_PICKER_STYLE_AUTOMATIC = 1;
@Kroll.constant
diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/TiUIBlurView.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/TiUIBlurView.java
new file mode 100644
index 00000000000..3e5bf99b9c0
--- /dev/null
+++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/TiUIBlurView.java
@@ -0,0 +1,227 @@
+/**
+* Titanium SDK
+* Copyright TiDev, Inc. 04/07/2022-Present
+* Licensed under the terms of the Apache Public License
+* Please see the LICENSE included with this distribution for details.
+*/
+package ti.modules.titanium.ui.widget;
+
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import org.appcelerator.kroll.KrollDict;
+import org.appcelerator.kroll.KrollProxy;
+import org.appcelerator.kroll.common.Log;
+import org.appcelerator.titanium.TiC;
+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 androidx.annotation.Nullable;
+
+import eightbitlab.com.blurview.BlurView;
+import ti.modules.titanium.ui.UIModule;
+
+public class TiUIBlurView extends TiUIView
+{
+ private static final String TAG = "TiUIBlurView";
+
+ // Custom property keys (Android-only)
+ private static final String PROPERTY_EFFECT = "effect"; // Number (Ti.UI.BLUR_EFFECT_STYLE_*)
+ private static final String PROPERTY_BLUR_RADIUS = "blurRadius"; // Number
+ private static final String PROPERTY_OVERLAY_COLOR = "overlayColor"; // String color
+
+ private final BlurView blurView;
+ private final TiCompositeLayout contentLayout;
+
+ // Current config
+ private float blurRadius = 16f;
+ private int overlayColor = 0x00FFFFFF; // transparent by default
+ private int effectStyle = -1; // unset
+
+ private class BlurContainer extends FrameLayout
+ {
+ final TiCompositeLayout layout;
+ final BlurView innerBlurView;
+
+ BlurContainer()
+ {
+ super(proxy.getActivity());
+
+ // Determine arrangement from proxy
+ TiCompositeLayout.LayoutArrangement arrangement = TiCompositeLayout.LayoutArrangement.DEFAULT;
+ Object layoutValue = proxy.getProperty(TiC.PROPERTY_LAYOUT);
+ if (TiC.LAYOUT_VERTICAL.equals(layoutValue)) {
+ arrangement = TiCompositeLayout.LayoutArrangement.VERTICAL;
+ } else if (TiC.LAYOUT_HORIZONTAL.equals(layoutValue)) {
+ arrangement = TiCompositeLayout.LayoutArrangement.HORIZONTAL;
+ }
+
+ // Create blur and content layout
+ innerBlurView = new BlurView(proxy.getActivity());
+ innerBlurView.setLayoutParams(new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+
+ layout = new TiCompositeLayout(proxy.getActivity(), arrangement, proxy);
+ layout.setLayoutParams(new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+
+ // Add in order: blur background then content overlay
+ super.addView(innerBlurView);
+ super.addView(layout);
+ }
+
+ TiCompositeLayout getLayout()
+ {
+ return layout;
+ }
+
+ BlurView getBlurView()
+ {
+ return innerBlurView;
+ }
+
+ @Override
+ public void addView(View child, ViewGroup.LayoutParams params)
+ {
+ // Route adds to TiCompositeLayout to ensure correct LayoutParams
+ layout.addView(child, params);
+ }
+ }
+
+ public TiUIBlurView(TiViewProxy proxy)
+ {
+ super(proxy);
+
+ BlurContainer container = new BlurContainer();
+ this.blurView = container.getBlurView();
+ this.contentLayout = container.getLayout();
+
+ setNativeView(container);
+ // Defer setup until we know the target (if provided)
+ setupOrUpdateBlur();
+ }
+
+ @Override
+ public void processProperties(KrollDict d)
+ {
+ super.processProperties(d);
+
+ if (d.containsKey(PROPERTY_BLUR_RADIUS)) {
+ this.blurRadius = TiConvert.toFloat(d, PROPERTY_BLUR_RADIUS, this.blurRadius);
+ }
+ if (d.containsKey(PROPERTY_OVERLAY_COLOR)) {
+ this.overlayColor = TiConvert.toColor(d.get(PROPERTY_OVERLAY_COLOR), proxy.getActivity());
+ }
+ if (d.containsKey(PROPERTY_EFFECT)) {
+ this.effectStyle = TiConvert.toInt(d.get(PROPERTY_EFFECT), -1);
+ applyEffectPresetIfAny();
+ }
+
+ setupOrUpdateBlur();
+ }
+
+ @Override
+ public void propertyChanged(String key, Object oldValue, Object newValue, KrollProxy proxy)
+ {
+ if (Log.isDebugModeEnabled()) {
+ Log.d(TAG, "Property changed: " + key + ", new: " + newValue, Log.DEBUG_MODE);
+ }
+
+ if (PROPERTY_BLUR_RADIUS.equals(key)) {
+ this.blurRadius = TiConvert.toFloat(newValue, this.blurRadius);
+ if (blurView != null) {
+ blurView.setBlurRadius(this.blurRadius);
+ }
+ } else if (PROPERTY_OVERLAY_COLOR.equals(key)) {
+ this.overlayColor = TiConvert.toColor(newValue, proxy.getActivity());
+ if (blurView != null) {
+ blurView.setOverlayColor(this.overlayColor);
+ }
+ } else if (PROPERTY_EFFECT.equals(key)) {
+ this.effectStyle = TiConvert.toInt(newValue, -1);
+ applyEffectPresetIfAny();
+ if (blurView != null) {
+ blurView.setOverlayColor(this.overlayColor);
+ blurView.setBlurRadius(this.blurRadius);
+ }
+ } else {
+ super.propertyChanged(key, oldValue, newValue, proxy);
+ }
+ }
+
+ private void setupOrUpdateBlur()
+ {
+ if (blurView == null) {
+ return;
+ }
+
+ Drawable windowBackground = getWindowBackground();
+ if (windowBackground == null) {
+ windowBackground = new ColorDrawable(Color.TRANSPARENT);
+ }
+
+ // Legacy API: setup with root content view, using RenderScriptBlur for <31
+ ViewGroup rootView = getRootContentView();
+ if (rootView == null) {
+ return;
+ }
+
+ blurView.setupWith(rootView)
+ .setFrameClearDrawable(windowBackground)
+ .setBlurRadius(this.blurRadius)
+ .setOverlayColor(this.overlayColor);
+ }
+
+ private void applyEffectPresetIfAny()
+ {
+ // Map iOS-like styles to Android parameters.
+ // These constants are defined on Ti.UI.*
+ // Default values already set on fields.
+ switch (this.effectStyle) {
+ case UIModule.BLUR_EFFECT_STYLE_EXTRA_LIGHT:
+ this.blurRadius = 16f;
+ this.overlayColor = 0x66FFFFFF; // strongest white tint
+ break;
+ case UIModule.BLUR_EFFECT_STYLE_LIGHT:
+ this.blurRadius = 16f;
+ this.overlayColor = 0x44FFFFFF; // medium white tint
+ break;
+ case UIModule.BLUR_EFFECT_STYLE_DARK:
+ this.blurRadius = 16f;
+ this.overlayColor = 0x66000000; // dark tint
+ break;
+ default:
+ // leave custom values as-is when unknown
+ break;
+ }
+ }
+
+ @Nullable
+ private ViewGroup getRootContentView()
+ {
+ if (proxy == null || proxy.getActivity() == null || proxy.getActivity().getWindow() == null) {
+ return null;
+ }
+ View decor = proxy.getActivity().getWindow().getDecorView();
+ View content = decor.findViewById(android.R.id.content);
+ if (content instanceof ViewGroup) {
+ return (ViewGroup) content;
+ }
+ return null;
+ }
+
+ @Nullable
+ private Drawable getWindowBackground()
+ {
+ if (proxy == null || proxy.getActivity() == null || proxy.getActivity().getWindow() == null) {
+ return null;
+ }
+ return proxy.getActivity().getWindow().getDecorView().getBackground();
+ }
+}
diff --git a/android/titanium/src/java/eightbitlab/com/blurview/BlurAlgorithm.java b/android/titanium/src/java/eightbitlab/com/blurview/BlurAlgorithm.java
new file mode 100644
index 00000000000..0c2b458197f
--- /dev/null
+++ b/android/titanium/src/java/eightbitlab/com/blurview/BlurAlgorithm.java
@@ -0,0 +1,43 @@
+package eightbitlab.com.blurview;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+
+import androidx.annotation.NonNull;
+
+public interface BlurAlgorithm {
+ /**
+ * @param bitmap bitmap to be blurred
+ * @param blurRadius blur radius
+ * @return blurred bitmap
+ */
+ Bitmap blur(@NonNull Bitmap bitmap, @NonNull float blurRadius);
+
+ /**
+ * Frees allocated resources
+ */
+ void destroy();
+
+ /**
+ * @return true if this algorithm returns the same instance of bitmap as it accepted
+ * false if it creates a new instance.
+ *
+ * If you return false from this method, you'll be responsible to swap bitmaps in your
+ * {@link BlurAlgorithm#blur(Bitmap, float)} implementation
+ * (assign input bitmap to your field and return the instance algorithm just blurred).
+ */
+ boolean canModifyBitmap();
+
+ /**
+ * Retrieve the {@link android.graphics.Bitmap.Config} on which the {@link BlurAlgorithm}
+ * can actually work.
+ *
+ * @return bitmap config supported by the given blur algorithm.
+ */
+ @NonNull
+ Bitmap.Config getSupportedBitmapConfig();
+
+ float scaleFactor();
+
+ void render(@NonNull Canvas canvas, @NonNull Bitmap bitmap);
+}
diff --git a/android/titanium/src/java/eightbitlab/com/blurview/BlurController.java b/android/titanium/src/java/eightbitlab/com/blurview/BlurController.java
new file mode 100644
index 00000000000..059bdd9b815
--- /dev/null
+++ b/android/titanium/src/java/eightbitlab/com/blurview/BlurController.java
@@ -0,0 +1,26 @@
+package eightbitlab.com.blurview;
+
+import android.graphics.Canvas;
+
+public interface BlurController extends BlurViewFacade {
+
+ float DEFAULT_SCALE_FACTOR = 6f;
+ float DEFAULT_BLUR_RADIUS = 16f;
+
+ /**
+ * Draws blurred content on given canvas
+ *
+ * @return true if BlurView should proceed with drawing itself and its children
+ */
+ boolean draw(Canvas canvas);
+
+ /**
+ * Must be used to notify Controller when BlurView's size has changed
+ */
+ void updateBlurViewSize();
+
+ /**
+ * Frees allocated resources
+ */
+ void destroy();
+}
diff --git a/android/titanium/src/java/eightbitlab/com/blurview/BlurView.java b/android/titanium/src/java/eightbitlab/com/blurview/BlurView.java
new file mode 100644
index 00000000000..f75c140cd9c
--- /dev/null
+++ b/android/titanium/src/java/eightbitlab/com/blurview/BlurView.java
@@ -0,0 +1,171 @@
+package eightbitlab.com.blurview;
+
+import static eightbitlab.com.blurview.PreDrawBlurController.TRANSPARENT;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import org.appcelerator.titanium.R;
+
+/**
+ * FrameLayout that blurs its underlying content.
+ * Can have children and draw them over blurred background.
+ */
+public class BlurView extends FrameLayout
+{
+
+ private static final String TAG = BlurView.class.getSimpleName();
+
+ BlurController blurController = new NoOpController();
+
+ @ColorInt
+ private int overlayColor;
+
+ public BlurView(Context context)
+ {
+ super(context);
+ init(null, 0);
+ }
+
+ public BlurView(Context context, AttributeSet attrs)
+ {
+ super(context, attrs);
+ init(attrs, 0);
+ }
+
+ public BlurView(Context context, AttributeSet attrs, int defStyleAttr)
+ {
+ super(context, attrs, defStyleAttr);
+ init(attrs, defStyleAttr);
+ }
+
+ private void init(AttributeSet attrs, int defStyleAttr)
+ {
+ TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.BlurView, defStyleAttr, 0);
+ overlayColor = a.getColor(R.styleable.BlurView_blurOverlayColor, TRANSPARENT);
+ a.recycle();
+ }
+
+ @Override
+ public void draw(Canvas canvas)
+ {
+ boolean shouldDraw = blurController.draw(canvas);
+ if (shouldDraw) {
+ super.draw(canvas);
+ }
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh)
+ {
+ super.onSizeChanged(w, h, oldw, oldh);
+ blurController.updateBlurViewSize();
+ }
+
+ @Override
+ protected void onDetachedFromWindow()
+ {
+ super.onDetachedFromWindow();
+ blurController.setBlurAutoUpdate(false);
+ }
+
+ @Override
+ protected void onAttachedToWindow()
+ {
+ super.onAttachedToWindow();
+ if (!isHardwareAccelerated()) {
+ Log.e(TAG, "BlurView can't be used in not hardware-accelerated window!");
+ } else {
+ blurController.setBlurAutoUpdate(true);
+ }
+ }
+
+ /**
+ * @param rootView root to start blur from.
+ * Can be Activity's root content layout (android.R.id.content)
+ * or (preferably) some of your layouts. The lower amount of Views are in the root, the better for performance.
+ * @param algorithm sets the blur algorithm
+ * @return {@link BlurView} to setup needed params.
+ */
+ public BlurViewFacade setupWith(@NonNull ViewGroup rootView, BlurAlgorithm algorithm)
+ {
+ this.blurController.destroy();
+ BlurController blurController = new PreDrawBlurController(this, rootView, overlayColor, algorithm);
+ this.blurController = blurController;
+
+ return blurController;
+ }
+
+ /**
+ * @param rootView root to start blur from.
+ * Can be Activity's root content layout (android.R.id.content)
+ * or (preferably) some of your layouts. The lower amount of Views are in the root, the better for performance.
+ *
+ * BlurAlgorithm is automatically picked based on the API version.
+ * It uses RenderEffectBlur on API 31+, and RenderScriptBlur on older versions.
+ * @return {@link BlurView} to setup needed params.
+ */
+ @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
+ public BlurViewFacade setupWith(@NonNull ViewGroup rootView)
+ {
+ return setupWith(rootView, getBlurAlgorithm());
+ }
+
+ // Setters duplicated to be able to conveniently change these settings outside of setupWith chain
+
+ /**
+ * @see BlurViewFacade#setBlurRadius(float)
+ */
+ public BlurViewFacade setBlurRadius(float radius)
+ {
+ return blurController.setBlurRadius(radius);
+ }
+
+ /**
+ * @see BlurViewFacade#setOverlayColor(int)
+ */
+ public BlurViewFacade setOverlayColor(@ColorInt int overlayColor)
+ {
+ this.overlayColor = overlayColor;
+ return blurController.setOverlayColor(overlayColor);
+ }
+
+ /**
+ * @see BlurViewFacade#setBlurAutoUpdate(boolean)
+ */
+ public BlurViewFacade setBlurAutoUpdate(boolean enabled)
+ {
+ return blurController.setBlurAutoUpdate(enabled);
+ }
+
+ /**
+ * @see BlurViewFacade#setBlurEnabled(boolean)
+ */
+ public BlurViewFacade setBlurEnabled(boolean enabled)
+ {
+ return blurController.setBlurEnabled(enabled);
+ }
+
+ @NonNull
+ @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
+ private BlurAlgorithm getBlurAlgorithm()
+ {
+ BlurAlgorithm algorithm;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ algorithm = new RenderEffectBlur();
+ } else {
+ algorithm = new RenderScriptBlur(getContext());
+ }
+ return algorithm;
+ }
+}
diff --git a/android/titanium/src/java/eightbitlab/com/blurview/BlurViewCanvas.java b/android/titanium/src/java/eightbitlab/com/blurview/BlurViewCanvas.java
new file mode 100644
index 00000000000..8d3c12459d4
--- /dev/null
+++ b/android/titanium/src/java/eightbitlab/com/blurview/BlurViewCanvas.java
@@ -0,0 +1,16 @@
+package eightbitlab.com.blurview;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+
+import androidx.annotation.NonNull;
+
+// Serves purely as a marker of a Canvas used in BlurView
+// to skip drawing itself and other BlurViews on the View hierarchy snapshot
+public class BlurViewCanvas extends Canvas
+{
+ public BlurViewCanvas(@NonNull Bitmap bitmap)
+ {
+ super(bitmap);
+ }
+}
diff --git a/android/titanium/src/java/eightbitlab/com/blurview/BlurViewFacade.java b/android/titanium/src/java/eightbitlab/com/blurview/BlurViewFacade.java
new file mode 100644
index 00000000000..814eef2a20e
--- /dev/null
+++ b/android/titanium/src/java/eightbitlab/com/blurview/BlurViewFacade.java
@@ -0,0 +1,48 @@
+package eightbitlab.com.blurview;
+
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.Nullable;
+
+public interface BlurViewFacade {
+
+ /**
+ * Enables/disables the blur. Enabled by default
+ *
+ * @param enabled true to enable, false otherwise
+ * @return {@link BlurViewFacade}
+ */
+ BlurViewFacade setBlurEnabled(boolean enabled);
+
+ /**
+ * Can be used to stop blur auto update or resume if it was stopped before.
+ * Enabled by default.
+ *
+ * @return {@link BlurViewFacade}
+ */
+ BlurViewFacade setBlurAutoUpdate(boolean enabled);
+
+ /**
+ * @param frameClearDrawable sets the drawable to draw before view hierarchy.
+ * Can be used to draw Activity's window background if your root layout doesn't provide any background
+ * Optional, by default frame is cleared with a transparent color.
+ * @return {@link BlurViewFacade}
+ */
+ BlurViewFacade setFrameClearDrawable(@Nullable Drawable frameClearDrawable);
+
+ /**
+ * @param radius sets the blur radius
+ * Default value is {@link BlurController#DEFAULT_BLUR_RADIUS}
+ * @return {@link BlurViewFacade}
+ */
+ BlurViewFacade setBlurRadius(float radius);
+
+ /**
+ * Sets the color overlay to be drawn on top of blurred content
+ *
+ * @param overlayColor int color
+ * @return {@link BlurViewFacade}
+ */
+ BlurViewFacade setOverlayColor(@ColorInt int overlayColor);
+}
diff --git a/android/titanium/src/java/eightbitlab/com/blurview/NoOpController.java b/android/titanium/src/java/eightbitlab/com/blurview/NoOpController.java
new file mode 100644
index 00000000000..b62a0bf7912
--- /dev/null
+++ b/android/titanium/src/java/eightbitlab/com/blurview/NoOpController.java
@@ -0,0 +1,56 @@
+package eightbitlab.com.blurview;
+
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.Nullable;
+
+// Used in edit mode and in case if no BlurController was set
+public class NoOpController implements BlurController
+{
+ @Override
+ public boolean draw(Canvas canvas)
+ {
+ return true;
+ }
+
+ @Override
+ public void updateBlurViewSize()
+ {
+ }
+
+ @Override
+ public void destroy()
+ {
+ }
+
+ @Override
+ public BlurViewFacade setBlurRadius(float radius)
+ {
+ return this;
+ }
+
+ @Override
+ public BlurViewFacade setOverlayColor(int overlayColor)
+ {
+ return this;
+ }
+
+ @Override
+ public BlurViewFacade setFrameClearDrawable(@Nullable Drawable windowBackground)
+ {
+ return this;
+ }
+
+ @Override
+ public BlurViewFacade setBlurEnabled(boolean enabled)
+ {
+ return this;
+ }
+
+ @Override
+ public BlurViewFacade setBlurAutoUpdate(boolean enabled)
+ {
+ return this;
+ }
+}
diff --git a/android/titanium/src/java/eightbitlab/com/blurview/PreDrawBlurController.java b/android/titanium/src/java/eightbitlab/com/blurview/PreDrawBlurController.java
new file mode 100644
index 00000000000..29da15c99f3
--- /dev/null
+++ b/android/titanium/src/java/eightbitlab/com/blurview/PreDrawBlurController.java
@@ -0,0 +1,253 @@
+package eightbitlab.com.blurview;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Blur Controller that handles all blur logic for the attached View.
+ * It honors View size changes, View animation and Visibility changes.
+ *
+ * The basic idea is to draw the view hierarchy on a bitmap, excluding the attached View,
+ * then blur and draw it on the system Canvas.
+ *
+ * It uses {@link ViewTreeObserver.OnPreDrawListener} to detect when
+ * blur should be updated.
+ *
+ */
+public final class PreDrawBlurController implements BlurController
+{
+
+ @ColorInt
+ public static final int TRANSPARENT = 0;
+
+ private float blurRadius = DEFAULT_BLUR_RADIUS;
+
+ private final BlurAlgorithm blurAlgorithm;
+ private BlurViewCanvas internalCanvas;
+ private Bitmap internalBitmap;
+
+ @SuppressWarnings("WeakerAccess")
+ final View blurView;
+ private int overlayColor;
+ private final ViewGroup rootView;
+ private final int[] rootLocation = new int[2];
+ private final int[] blurViewLocation = new int[2];
+
+ private final ViewTreeObserver.OnPreDrawListener drawListener = new ViewTreeObserver.OnPreDrawListener()
+ {
+ @Override
+ public boolean onPreDraw()
+ {
+ // Not invalidating a View here, just updating the Bitmap.
+ // This relies on the HW accelerated bitmap drawing behavior in Android
+ // If the bitmap was drawn on HW accelerated canvas, it holds a reference to it and on next
+ // drawing pass the updated content of the bitmap will be rendered on the screen
+ updateBlur();
+ return true;
+ }
+ };
+
+ private boolean blurEnabled = true;
+ private boolean initialized;
+
+ @Nullable
+ private Drawable frameClearDrawable;
+
+ /**
+ * @param blurView View which will draw it's blurred underlying content
+ * @param rootView Root View where blurView's underlying content starts drawing.
+ * Can be Activity's root content layout (android.R.id.content)
+ * @param algorithm sets the blur algorithm
+ */
+ public PreDrawBlurController(@NonNull View blurView, @NonNull ViewGroup rootView,
+ @ColorInt int overlayColor, BlurAlgorithm algorithm)
+ {
+ this.rootView = rootView;
+ this.blurView = blurView;
+ this.overlayColor = overlayColor;
+ this.blurAlgorithm = algorithm;
+ if (algorithm instanceof RenderEffectBlur) {
+ // noinspection NewApi
+ ((RenderEffectBlur) algorithm).setContext(blurView.getContext());
+ }
+
+ int measuredWidth = blurView.getMeasuredWidth();
+ int measuredHeight = blurView.getMeasuredHeight();
+
+ init(measuredWidth, measuredHeight);
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ void init(int measuredWidth, int measuredHeight)
+ {
+ setBlurAutoUpdate(true);
+ SizeScaler sizeScaler = new SizeScaler(blurAlgorithm.scaleFactor());
+ if (sizeScaler.isZeroSized(measuredWidth, measuredHeight)) {
+ // Will be initialized later when the View reports a size change
+ blurView.setWillNotDraw(true);
+ return;
+ }
+
+ blurView.setWillNotDraw(false);
+ SizeScaler.Size bitmapSize = sizeScaler.scale(measuredWidth, measuredHeight);
+ internalBitmap = Bitmap.createBitmap(bitmapSize.width, bitmapSize.height,
+ blurAlgorithm.getSupportedBitmapConfig());
+ internalCanvas = new BlurViewCanvas(internalBitmap);
+ initialized = true;
+ // Usually it's not needed, because `onPreDraw` updates the blur anyway.
+ // But it handles cases when the PreDraw listener is attached to a different Window, for example
+ // when the BlurView is in a Dialog window, but the root is in the Activity.
+ // Previously it was done in `draw`, but it was causing potential side effects and Jetpack Compose crashes
+ updateBlur();
+ }
+
+ @SuppressWarnings("WeakerAccess")
+ void updateBlur()
+ {
+ if (!blurEnabled || !initialized) {
+ return;
+ }
+
+ if (frameClearDrawable == null) {
+ internalBitmap.eraseColor(Color.TRANSPARENT);
+ } else {
+ frameClearDrawable.draw(internalCanvas);
+ }
+
+ internalCanvas.save();
+ setupInternalCanvasMatrix();
+ rootView.draw(internalCanvas);
+ internalCanvas.restore();
+
+ blurAndSave();
+ }
+
+ /**
+ * Set up matrix to draw starting from blurView's position
+ */
+ private void setupInternalCanvasMatrix()
+ {
+ rootView.getLocationOnScreen(rootLocation);
+ blurView.getLocationOnScreen(blurViewLocation);
+
+ int left = blurViewLocation[0] - rootLocation[0];
+ int top = blurViewLocation[1] - rootLocation[1];
+
+ // https://github.com/Dimezis/BlurView/issues/128
+ float scaleFactorH = (float) blurView.getHeight() / internalBitmap.getHeight();
+ float scaleFactorW = (float) blurView.getWidth() / internalBitmap.getWidth();
+
+ float scaledLeftPosition = -left / scaleFactorW;
+ float scaledTopPosition = -top / scaleFactorH;
+
+ internalCanvas.translate(scaledLeftPosition, scaledTopPosition);
+ internalCanvas.scale(1 / scaleFactorW, 1 / scaleFactorH);
+ }
+
+ @Override
+ public boolean draw(Canvas canvas)
+ {
+ if (!blurEnabled || !initialized) {
+ return true;
+ }
+ // Not blurring itself or other BlurViews to not cause recursive draw calls
+ // Related: https://github.com/Dimezis/BlurView/issues/110
+ if (canvas instanceof BlurViewCanvas) {
+ return false;
+ }
+
+ // https://github.com/Dimezis/BlurView/issues/128
+ float scaleFactorH = (float) blurView.getHeight() / internalBitmap.getHeight();
+ float scaleFactorW = (float) blurView.getWidth() / internalBitmap.getWidth();
+
+ canvas.save();
+ canvas.scale(scaleFactorW, scaleFactorH);
+ blurAlgorithm.render(canvas, internalBitmap);
+ canvas.restore();
+ if (overlayColor != TRANSPARENT) {
+ canvas.drawColor(overlayColor);
+ }
+ return true;
+ }
+
+ private void blurAndSave()
+ {
+ internalBitmap = blurAlgorithm.blur(internalBitmap, blurRadius);
+ if (!blurAlgorithm.canModifyBitmap()) {
+ internalCanvas.setBitmap(internalBitmap);
+ }
+ }
+
+ @Override
+ public void updateBlurViewSize()
+ {
+ int measuredWidth = blurView.getMeasuredWidth();
+ int measuredHeight = blurView.getMeasuredHeight();
+
+ init(measuredWidth, measuredHeight);
+ }
+
+ @Override
+ public void destroy()
+ {
+ setBlurAutoUpdate(false);
+ blurAlgorithm.destroy();
+ initialized = false;
+ }
+
+ @Override
+ public BlurViewFacade setBlurRadius(float radius)
+ {
+ this.blurRadius = radius;
+ return this;
+ }
+
+ @Override
+ public BlurViewFacade setFrameClearDrawable(@Nullable Drawable frameClearDrawable)
+ {
+ this.frameClearDrawable = frameClearDrawable;
+ return this;
+ }
+
+ @Override
+ public BlurViewFacade setBlurEnabled(boolean enabled)
+ {
+ this.blurEnabled = enabled;
+ setBlurAutoUpdate(enabled);
+ blurView.invalidate();
+ return this;
+ }
+
+ public BlurViewFacade setBlurAutoUpdate(final boolean enabled)
+ {
+ rootView.getViewTreeObserver().removeOnPreDrawListener(drawListener);
+ blurView.getViewTreeObserver().removeOnPreDrawListener(drawListener);
+ if (enabled) {
+ rootView.getViewTreeObserver().addOnPreDrawListener(drawListener);
+ // Track changes in the blurView window too, for example if it's in a bottom sheet dialog
+ if (rootView.getWindowId() != blurView.getWindowId()) {
+ blurView.getViewTreeObserver().addOnPreDrawListener(drawListener);
+ }
+ }
+ return this;
+ }
+
+ @Override
+ public BlurViewFacade setOverlayColor(int overlayColor)
+ {
+ if (this.overlayColor != overlayColor) {
+ this.overlayColor = overlayColor;
+ blurView.invalidate();
+ }
+ return this;
+ }
+}
diff --git a/android/titanium/src/java/eightbitlab/com/blurview/RenderEffectBlur.java b/android/titanium/src/java/eightbitlab/com/blurview/RenderEffectBlur.java
new file mode 100644
index 00000000000..e4f06f33bb8
--- /dev/null
+++ b/android/titanium/src/java/eightbitlab/com/blurview/RenderEffectBlur.java
@@ -0,0 +1,103 @@
+package eightbitlab.com.blurview;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.RenderEffect;
+import android.graphics.RenderNode;
+import android.graphics.Shader;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+/**
+ * Leverages the new RenderEffect.createBlurEffect API to perform blur.
+ * Hardware accelerated.
+ * Blur is performed on a separate thread - native RenderThread.
+ * It doesn't block the Main thread, however it can still cause an FPS drop,
+ * because it's just in a different part of the rendering pipeline.
+ */
+@RequiresApi(Build.VERSION_CODES.S)
+public class RenderEffectBlur implements BlurAlgorithm
+{
+
+ private final RenderNode node = new RenderNode("BlurViewNode");
+
+ private int height, width;
+ private float lastBlurRadius = 1f;
+
+ @Nullable
+ public BlurAlgorithm fallbackAlgorithm;
+ private Context context;
+
+ public RenderEffectBlur()
+ {
+ }
+
+ @Override
+ public Bitmap blur(@NonNull Bitmap bitmap, float blurRadius)
+ {
+ lastBlurRadius = blurRadius;
+
+ if (bitmap.getHeight() != height || bitmap.getWidth() != width) {
+ height = bitmap.getHeight();
+ width = bitmap.getWidth();
+ node.setPosition(0, 0, width, height);
+ }
+ Canvas canvas = node.beginRecording();
+ canvas.drawBitmap(bitmap, 0, 0, null);
+ node.endRecording();
+ node.setRenderEffect(RenderEffect.createBlurEffect(blurRadius, blurRadius, Shader.TileMode.MIRROR));
+ // returning not blurred bitmap, because the rendering relies on the RenderNode
+ return bitmap;
+ }
+
+ @Override
+ public void destroy()
+ {
+ node.discardDisplayList();
+ if (fallbackAlgorithm != null) {
+ fallbackAlgorithm.destroy();
+ }
+ }
+
+ @Override
+ public boolean canModifyBitmap()
+ {
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public Bitmap.Config getSupportedBitmapConfig()
+ {
+ return Bitmap.Config.ARGB_8888;
+ }
+
+ @Override
+ public float scaleFactor()
+ {
+ return BlurController.DEFAULT_SCALE_FACTOR;
+ }
+
+ @Override
+ public void render(@NonNull Canvas canvas, @NonNull Bitmap bitmap)
+ {
+ if (canvas.isHardwareAccelerated()) {
+ canvas.drawRenderNode(node);
+ } else {
+ if (fallbackAlgorithm == null) {
+ fallbackAlgorithm = new RenderScriptBlur(context);
+ }
+ fallbackAlgorithm.blur(bitmap, lastBlurRadius);
+ fallbackAlgorithm.render(canvas, bitmap);
+ }
+ }
+
+ void setContext(@NonNull Context context)
+ {
+ this.context = context;
+ }
+}
diff --git a/android/titanium/src/java/eightbitlab/com/blurview/RenderScriptBlur.java b/android/titanium/src/java/eightbitlab/com/blurview/RenderScriptBlur.java
new file mode 100644
index 00000000000..e1db404cc6b
--- /dev/null
+++ b/android/titanium/src/java/eightbitlab/com/blurview/RenderScriptBlur.java
@@ -0,0 +1,116 @@
+package eightbitlab.com.blurview;
+
+import static eightbitlab.com.blurview.BlurController.DEFAULT_SCALE_FACTOR;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.os.Build;
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.RenderScript;
+import android.renderscript.ScriptIntrinsicBlur;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+/**
+ * Blur using RenderScript, processed on GPU when device drivers support it.
+ * Requires API 17+
+ *
+ * @deprecated because RenderScript is deprecated and its hardware acceleration is not guaranteed.
+ * RenderEffectBlur is the best alternative at the moment.
+ */
+@Deprecated
+public class RenderScriptBlur implements BlurAlgorithm
+{
+ private final Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG);
+ private final RenderScript renderScript;
+ private final ScriptIntrinsicBlur blurScript;
+ private Allocation outAllocation;
+
+ private int lastBitmapWidth = -1;
+ private int lastBitmapHeight = -1;
+
+ /**
+ * @param context Context to create the {@link RenderScript}
+ */
+ @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
+ public RenderScriptBlur(@NonNull Context context)
+ {
+ renderScript = RenderScript.create(context);
+ blurScript = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));
+ }
+
+ private boolean canReuseAllocation(@NonNull Bitmap bitmap)
+ {
+ return bitmap.getHeight() == lastBitmapHeight && bitmap.getWidth() == lastBitmapWidth;
+ }
+
+ /**
+ * @param bitmap bitmap to blur
+ * @param blurRadius blur radius (1..25)
+ * @return blurred bitmap
+ */
+ @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
+ @Override
+ public Bitmap blur(@NonNull Bitmap bitmap, float blurRadius)
+ {
+ //Allocation will use the same backing array of pixels as bitmap if created with USAGE_SHARED flag
+ Allocation inAllocation = Allocation.createFromBitmap(renderScript, bitmap);
+
+ if (!canReuseAllocation(bitmap)) {
+ if (outAllocation != null) {
+ outAllocation.destroy();
+ }
+ outAllocation = Allocation.createTyped(renderScript, inAllocation.getType());
+ lastBitmapWidth = bitmap.getWidth();
+ lastBitmapHeight = bitmap.getHeight();
+ }
+
+ blurScript.setRadius(blurRadius);
+ blurScript.setInput(inAllocation);
+ //do not use inAllocation in forEach. it will cause visual artifacts on blurred Bitmap
+ blurScript.forEach(outAllocation);
+ outAllocation.copyTo(bitmap);
+
+ inAllocation.destroy();
+ return bitmap;
+ }
+
+ @Override
+ public final void destroy()
+ {
+ blurScript.destroy();
+ renderScript.destroy();
+ if (outAllocation != null) {
+ outAllocation.destroy();
+ }
+ }
+
+ @Override
+ public boolean canModifyBitmap()
+ {
+ return true;
+ }
+
+ @NonNull
+ @Override
+ public Bitmap.Config getSupportedBitmapConfig()
+ {
+ return Bitmap.Config.ARGB_8888;
+ }
+
+ @Override
+ public float scaleFactor()
+ {
+ return DEFAULT_SCALE_FACTOR;
+ }
+
+ @Override
+ public void render(@NonNull Canvas canvas, @NonNull Bitmap bitmap)
+ {
+ canvas.drawBitmap(bitmap, 0f, 0f, paint);
+ }
+}
diff --git a/android/titanium/src/java/eightbitlab/com/blurview/SizeScaler.java b/android/titanium/src/java/eightbitlab/com/blurview/SizeScaler.java
new file mode 100644
index 00000000000..7949f6d4f27
--- /dev/null
+++ b/android/titanium/src/java/eightbitlab/com/blurview/SizeScaler.java
@@ -0,0 +1,97 @@
+package eightbitlab.com.blurview;
+
+/**
+ * Scales width and height by [scaleFactor],
+ * and then rounds the size proportionally so the width is divisible by [ROUNDING_VALUE]
+ */
+public class SizeScaler
+{
+
+ // Bitmap size should be divisible by ROUNDING_VALUE to meet stride requirement.
+ // This will help avoiding an extra bitmap allocation when passing the bitmap to RenderScript for blur.
+ // Usually it's 16, but on Samsung devices it's 64 for some reason.
+ private static final int ROUNDING_VALUE = 64;
+ private final float scaleFactor;
+
+ public SizeScaler(float scaleFactor)
+ {
+ this.scaleFactor = scaleFactor;
+ }
+
+ Size scale(int width, int height)
+ {
+ int nonRoundedScaledWidth = downscaleSize(width);
+ int scaledWidth = roundSize(nonRoundedScaledWidth);
+ //Only width has to be aligned to ROUNDING_VALUE
+ float roundingScaleFactor = (float) width / scaledWidth;
+ //Ceiling because rounding or flooring might leave empty space on the View's bottom
+ int scaledHeight = (int) Math.ceil(height / roundingScaleFactor);
+
+ return new Size(scaledWidth, scaledHeight, roundingScaleFactor);
+ }
+
+ boolean isZeroSized(int measuredWidth, int measuredHeight)
+ {
+ return downscaleSize(measuredHeight) == 0 || downscaleSize(measuredWidth) == 0;
+ }
+
+ /**
+ * Rounds a value to the nearest divisible by {@link #ROUNDING_VALUE} to meet stride requirement
+ */
+ private int roundSize(int value)
+ {
+ if (value % ROUNDING_VALUE == 0) {
+ return value;
+ }
+ return value - (value % ROUNDING_VALUE) + ROUNDING_VALUE;
+ }
+
+ private int downscaleSize(float value)
+ {
+ return (int) Math.ceil(value / scaleFactor);
+ }
+
+ static class Size
+ {
+
+ final int width;
+ final int height;
+ // TODO this is probably not needed anymore
+ final float scaleFactor;
+
+ Size(int width, int height, float scaleFactor)
+ {
+ this.width = width;
+ this.height = height;
+ this.scaleFactor = scaleFactor;
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Size size = (Size) o;
+
+ if (width != size.width) return false;
+ if (height != size.height) return false;
+ return Float.compare(size.scaleFactor, scaleFactor) == 0;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int result = width;
+ result = 31 * result + height;
+ result = 31 * result + (scaleFactor != +0.0f ? Float.floatToIntBits(scaleFactor) : 0);
+ return result;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "Size{ width=" + width + ", height=" + height + ", scaleFactor=" + scaleFactor + '}';
+ }
+ }
+}
diff --git a/apidoc/Titanium/UI/BlurView.yml b/apidoc/Titanium/UI/BlurView.yml
new file mode 100644
index 00000000000..c7b8f8c8d03
--- /dev/null
+++ b/apidoc/Titanium/UI/BlurView.yml
@@ -0,0 +1,177 @@
+---
+name: Titanium.UI.BlurView
+summary: |
+ A blur view providing a background blur effect similar to iOS' UIVisualEffectView.
+
+ The blur is applied to content behind the blur view within the window. Place it above
+ other views to blur anything visible underneath within its bounds.
+extends: Titanium.UI.View
+platforms: [android, iphone, ipad, macos]
+since: { android: "13.0.0" }
+properties:
+
+ - name: effect
+ summary: The blur effect to apply to the effect view.
+ description: |
+ On iOS, you can also use the Titanium.UI.iOS.BLUR_EFFECT_STYLE_* effects
+ that are platform-specific.
+ On Android it will add a default blurRadius of 16px. If you want to change the value you have to use `blurRadius` and `overlayColor` instead of the `effect` preset.
+ type: Number
+ constants: Titanium.UI.BLUR_EFFECT_STYLE_*
+ default: undefined (no preset; uses current `blurRadius`/`overlayColor`) on Android
+
+ - name: blurRadius
+ summary: Blur radius intensity in pixels.
+ type: Number
+ default: 16
+ platforms: [android]
+
+ - name: overlayColor
+ summary: Color overlay/tint drawn over the blur.
+ description: Hex string, color name, or rgba() string.
+ type: String
+ default: transparent
+ platforms: [android]
+
+ - name: glassEffect
+ summary: The glass effect configuration to apply to the effect view.
+ description: |
+ Glass effects are an alternative to classic blur views introduced in iOS 26, providing
+ a more modern and visually appealing blur effect with additional customization options.
+
+ When the glass effect is enabled, it will override any value set to the `effect` property.
+ See for the available configuration options.
+ type: GlassEffectConfiguration
+ platforms: [iphone, ipad, macos]
+ osver: {ios: {min: "26.0"}}
+
+examples:
+ - title: Basic Blur View
+ example: |
+ ``` js
+ const win = Ti.UI.createWindow({ backgroundColor: '#fff' });
+
+ // Content behind the blur
+ const bg = Ti.UI.createImageView({
+ image: '/default_app_logo.png'
+ });
+
+ // Blur view placed above content, blurs everything behind within its bounds
+ const blur = Ti.UI.createBlurView({
+ width: 200,
+ height: 200,
+ effect: Ti.UI.BLUR_EFFECT_STYLE_LIGHT
+ });
+
+ win.add([bg, blur]);
+ win.open();
+ ```
+
+ - title: Full Featured Blur View (different styles)
+ example: |
+ The following example shows how to create a simple blur view:
+
+ ``` js
+ const win = Ti.UI.createWindow({ backgroundColor: '#fff' });
+ const target = Ti.UI.createView({ width: Ti.UI.FILL, height: Ti.UI.FILL });
+
+ target.add(Ti.UI.createImageView({
+ image: '/DefaultIcon.png'
+ }));
+
+ const effects = [
+ Ti.UI.BLUR_EFFECT_STYLE_EXTRA_LIGHT,
+ Ti.UI.BLUR_EFFECT_STYLE_LIGHT,
+ Ti.UI.BLUR_EFFECT_STYLE_DARK,
+ ];
+
+ const blur = Ti.UI.createBlurView({
+ width: 200,
+ height: 200,
+ effect: effects[0]
+ });
+
+ // Create circular animation to live-test the blur effect
+ const density = Ti.Platform.osname === 'android' ? Ti.Platform.displayCaps.logicalDensityFactor : 1;
+ const centerX = Ti.Platform.displayCaps.platformWidth / density / 2;
+ const centerY = Ti.Platform.displayCaps.platformHeight / density / 2;
+
+ const radius = 100;
+ let angle = 0;
+
+ const animateCircle = () => {
+ angle += 0.1;
+ const x = centerX + Math.cos(angle) * radius - 100; // subtract half width
+ const y = centerY + Math.sin(angle) * radius - 100; // subtract half height
+
+ blur.animate({
+ left: x,
+ top: y,
+ duration: 50
+ }, () => {
+ animateCircle();
+ });
+ };
+
+ animateCircle();
+
+ const tabs = Ti.UI.createTabbedBar({
+ labels: ['Extra light', 'Light', 'Dark'],
+ bottom: 100
+ });
+
+ tabs.addEventListener('click', ({ index }) => {
+ blur.effect = effects[index];
+ });
+
+ win.add([target, blur, tabs]);
+ win.open();
+ ```
+
+ - title: Glass Effect View (iOS 26+)
+ example: |
+ The following example shows how to create a blur view with the new glass effect:
+
+ ``` js
+ const win = Ti.UI.createWindow({
+ backgroundColor: "#fff"
+ });
+
+ const backgroundView = Ti.UI.createImageView({
+ image: 'https://s1.directupload.eu/images/250817/ufgix5hy.jpg',
+ width: Ti.UI.FILL,
+ height: Ti.UI.FILL,
+ scalingMode: Ti.Media.IMAGE_SCALING_ASPECT_FILL
+ });
+
+ const effectView = Ti.UI.createBlurView({
+ width: 250,
+ height: 250,
+ glassEffect: {
+ style: Ti.UI.iOS.GLASS_EFFECT_STYLE_CLEAR,
+ interactive: true
+ }
+ });
+
+ backgroundView.add(effectView);
+
+ const toggleButton = Ti.UI.createButton({
+ title: "Toggle Glass Effect",
+ bottom: 50
+ });
+
+ let isEnabled = true;
+ toggleButton.addEventListener("click", () => {
+ isEnabled = !isEnabled;
+ if (isEnabled) {
+ backgroundView.add(effectView)
+ } else {
+ backgroundView.remove(effectView)
+ }
+ });
+
+ win.add(backgroundView);
+ win.add(toggleButton);
+ win.open();
+ ```
+
diff --git a/apidoc/Titanium/UI/UI.yml b/apidoc/Titanium/UI/UI.yml
index c65fc46c6e8..8143cffab56 100644
--- a/apidoc/Titanium/UI/UI.yml
+++ b/apidoc/Titanium/UI/UI.yml
@@ -2667,6 +2667,31 @@ properties:
permission: read-only
since: "2.0.0"
+ - name: BLUR_EFFECT_STYLE_EXTRA_LIGHT
+ summary: Use with [BlurView.effect](Titanium.UI.BlurView.effect) to specify a blur effect.
+ description: |
+ Creates a blurring effect in the view. The area of the view is lighter in hue than the underlying view.
+ type: Number
+ permission: read-only
+ since: "13.0.0"
+
+ - name: BLUR_EFFECT_STYLE_LIGHT
+ summary: Use with [BlurView.effect](Titanium.UI.BlurView.effect) to specify a blur effect.
+ description: |
+ Creates a blurring effect in the view. The area of the view is the same approximate hue
+ of the underlying view.
+ type: Number
+ permission: read-only
+ since: "13.0.0"
+
+ - name: BLUR_EFFECT_STYLE_DARK
+ summary: Use with [BlurView.effect](Titanium.UI.BlurView.effect) to specify a blur effect.
+ description: |
+ Creates a blurring effect in the view. The area of the view is darker in hue than the underlying view.
+ type: Number
+ permission: read-only
+ since: "13.0.0"
+
- name: UNKNOWN
summary: Orientation constant representing an unknown orientation.
description: |
diff --git a/apidoc/Titanium/UI/iOS/BlurView.yml b/apidoc/Titanium/UI/iOS/BlurView.yml
index 36e8b09a849..dd1627fc62e 100644
--- a/apidoc/Titanium/UI/iOS/BlurView.yml
+++ b/apidoc/Titanium/UI/iOS/BlurView.yml
@@ -11,6 +11,11 @@ summary: |
extends: Titanium.UI.View
platforms: [iphone, ipad, macos]
since: {iphone: "5.4.0", ipad: "5.4.0", macos: "9.2.0"}
+deprecated:
+ since: "13.0.0"
+ removed: "14.0.0"
+ notes: Use the API instead - it has the exact same API for iOS and also supports Android!
+
properties:
- name: effect
summary: The blur effect to apply to the effect view.
diff --git a/apidoc/Titanium/UI/iOS/iOS.yml b/apidoc/Titanium/UI/iOS/iOS.yml
index 23da962be6f..93bb8047984 100644
--- a/apidoc/Titanium/UI/iOS/iOS.yml
+++ b/apidoc/Titanium/UI/iOS/iOS.yml
@@ -161,6 +161,10 @@ properties:
type: Number
permission: read-only
since: "5.4.0"
+ deprecated:
+ since: "13.0.0"
+ removed: "14.0.0"
+ notes: Use instead.
- name: BLUR_EFFECT_STYLE_LIGHT
summary: |
@@ -171,6 +175,10 @@ properties:
type: Number
permission: read-only
since: "5.4.0"
+ deprecated:
+ since: "13.0.0"
+ removed: "14.0.0"
+ notes: Use instead.
- name: BLUR_EFFECT_STYLE_DARK
summary: |
@@ -180,6 +188,10 @@ properties:
type: Number
permission: read-only
since: "5.4.0"
+ deprecated:
+ since: "13.0.0"
+ removed: "14.0.0"
+ notes: Use instead.
- name: BLUR_EFFECT_STYLE_REGULAR
summary: |
diff --git a/iphone/Classes/TiUIiOSBlurView.h b/iphone/Classes/TiUIBlurView.h
similarity index 85%
rename from iphone/Classes/TiUIiOSBlurView.h
rename to iphone/Classes/TiUIBlurView.h
index 39b9c04305d..c3db829f32b 100644
--- a/iphone/Classes/TiUIiOSBlurView.h
+++ b/iphone/Classes/TiUIBlurView.h
@@ -4,10 +4,10 @@
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/
-#ifdef USE_TI_UIIOSBLURVIEW
+#ifdef USE_TI_UIBLURVIEW
#import
-@interface TiUIiOSBlurView : TiUIView {
+@interface TiUIBlurView : TiUIView {
UIVisualEffectView *blurView;
TiDimension width;
diff --git a/iphone/Classes/TiUIiOSBlurView.m b/iphone/Classes/TiUIBlurView.m
similarity index 97%
rename from iphone/Classes/TiUIiOSBlurView.m
rename to iphone/Classes/TiUIBlurView.m
index 13d13d29339..7deff6b962a 100644
--- a/iphone/Classes/TiUIiOSBlurView.m
+++ b/iphone/Classes/TiUIBlurView.m
@@ -5,11 +5,11 @@
* Please see the LICENSE included with this distribution for details.
*/
-#ifdef USE_TI_UIIOSBLURVIEW
-#import "TiUIiOSBlurView.h"
-#import "TiUIiOSBlurViewProxy.h"
+#ifdef USE_TI_UIBLURVIEW
+#import "TiUIBlurView.h"
+#import "TiUIBlurViewProxy.h"
-@implementation TiUIiOSBlurView
+@implementation TiUIBlurView
- (UIVisualEffectView *)blurView
{
diff --git a/iphone/Classes/TiUIiOSBlurViewProxy.h b/iphone/Classes/TiUIBlurViewProxy.h
similarity index 78%
rename from iphone/Classes/TiUIiOSBlurViewProxy.h
rename to iphone/Classes/TiUIBlurViewProxy.h
index 095959db2d1..b119c98e32d 100644
--- a/iphone/Classes/TiUIiOSBlurViewProxy.h
+++ b/iphone/Classes/TiUIBlurViewProxy.h
@@ -4,10 +4,10 @@
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/
-#ifdef USE_TI_UIIOSBLURVIEW
+#ifdef USE_TI_UIBLURVIEW
#import
-@interface TiUIiOSBlurViewProxy : TiViewProxy {
+@interface TiUIBlurViewProxy : TiViewProxy {
}
@end
diff --git a/iphone/Classes/TiUIiOSBlurViewProxy.m b/iphone/Classes/TiUIBlurViewProxy.m
similarity index 63%
rename from iphone/Classes/TiUIiOSBlurViewProxy.m
rename to iphone/Classes/TiUIBlurViewProxy.m
index 95e3d53e019..e531462e28e 100644
--- a/iphone/Classes/TiUIiOSBlurViewProxy.m
+++ b/iphone/Classes/TiUIBlurViewProxy.m
@@ -5,18 +5,18 @@
* Please see the LICENSE included with this distribution for details.
*/
-#ifdef USE_TI_UIIOSBLURVIEW
-#import "TiUIiOSBlurViewProxy.h"
-#import "TiUIiOSBlurView.h"
+#ifdef USE_TI_UIBLURVIEW
+#import "TiUIBlurViewProxy.h"
+#import "TiUIBlurView.h"
#import
-@implementation TiUIiOSBlurViewProxy
+@implementation TiUIBlurViewProxy
#pragma mark Proxy lifecycle
- (NSString *)apiName
{
- return @"Ti.UI.iOS.BlurView";
+ return @"Ti.UI.BlurView";
}
- (void)dealloc
@@ -26,9 +26,9 @@ - (void)dealloc
#pragma mark Public APIs
-- (TiUIiOSBlurView *)blurView
+- (TiUIBlurView *)blurView
{
- return (TiUIiOSBlurView *)self.view;
+ return (TiUIBlurView *)self.view;
}
@end
diff --git a/iphone/Classes/TiUIiOSProxy.h b/iphone/Classes/TiUIiOSProxy.h
index b238e1762b5..3dee633b3c6 100644
--- a/iphone/Classes/TiUIiOSProxy.h
+++ b/iphone/Classes/TiUIiOSProxy.h
@@ -156,12 +156,6 @@
@property (nonatomic, readonly) NSNumber *MODAL_TRANSITION_STYLE_PARTIAL_CURL;
-#ifdef USE_TI_UIIOSBLURVIEW
-@property (nonatomic, readonly) NSNumber *BLUR_EFFECT_STYLE_EXTRA_LIGHT;
-@property (nonatomic, readonly) NSNumber *BLUR_EFFECT_STYLE_LIGHT;
-@property (nonatomic, readonly) NSNumber *BLUR_EFFECT_STYLE_DARK;
-#endif
-
@property (nonatomic, readonly) NSNumber *LARGE_TITLE_DISPLAY_MODE_AUTOMATIC;
@property (nonatomic, readonly) NSNumber *LARGE_TITLE_DISPLAY_MODE_ALWAYS;
@property (nonatomic, readonly) NSNumber *LARGE_TITLE_DISPLAY_MODE_NEVER;
@@ -220,7 +214,7 @@
#ifdef USE_TI_UIIOSMENUPOPUP
- (id)createMenuPopup:(id)args;
#endif
-#ifdef USE_TI_UIIOSBLURVIEW
+#ifdef USE_TI_UIBLURVIEW
- (id)createBlurView:(id)args;
#endif
#ifdef USE_TI_UIIOSAPPLICATIONSHORTCUTS
diff --git a/iphone/Classes/TiUIiOSProxy.m b/iphone/Classes/TiUIiOSProxy.m
index d6fa78c6bd7..f48837aae2b 100644
--- a/iphone/Classes/TiUIiOSProxy.m
+++ b/iphone/Classes/TiUIiOSProxy.m
@@ -76,8 +76,8 @@
#import
#endif
-#ifdef USE_TI_UIIOSBLURVIEW
-#import "TiUIiOSBlurViewProxy.h"
+#ifdef USE_TI_UIBLURVIEW
+#import "TiUIBlurViewProxy.h"
#endif
#ifdef USE_TI_UIIOSSTEPPER
@@ -467,9 +467,9 @@ - (void)setAppSupportsShakeToEdit:(NSNumber *)shake
END_UI_THREAD_PROTECTED_VALUE(appSupportsShakeToEdit)
#ifdef USE_TI_UIIOSBLURVIEW
-MAKE_SYSTEM_PROP(BLUR_EFFECT_STYLE_EXTRA_LIGHT, UIBlurEffectStyleExtraLight);
-MAKE_SYSTEM_PROP(BLUR_EFFECT_STYLE_LIGHT, UIBlurEffectStyleLight);
-MAKE_SYSTEM_PROP(BLUR_EFFECT_STYLE_DARK, UIBlurEffectStyleDark);
+MAKE_SYSTEM_PROP_DEPRECATED_REPLACED(BLUR_EFFECT_STYLE_EXTRA_LIGHT, UIBlurEffectStyleExtraLight, @"UI.iOS.BLUR_EFFECT_STYLE_EXTRA_LIGHT", @"13.0.0", @"UI.BLUR_EFFECT_STYLE_EXTRA_LIGHT");
+MAKE_SYSTEM_PROP_DEPRECATED_REPLACED(BLUR_EFFECT_STYLE_LIGHT, UIBlurEffectStyleLight, @"UI.iOS.BLUR_EFFECT_STYLE_LIGHT", @"13.0.0", @"UI.BLUR_EFFECT_STYLE_LIGHT");
+MAKE_SYSTEM_PROP_DEPRECATED_REPLACED(BLUR_EFFECT_STYLE_DARK, UIBlurEffectStyleDark, @"UI.iOS.BLUR_EFFECT_STYLE_DARK", @"13.0.0", @"UI.BLUR_EFFECT_STYLE_DARK");
MAKE_SYSTEM_PROP(BLUR_EFFECT_STYLE_REGULAR, UIBlurEffectStyleRegular);
MAKE_SYSTEM_PROP(BLUR_EFFECT_STYLE_PROMINENT, UIBlurEffectStyleProminent);
MAKE_SYSTEM_PROP(BLUR_EFFECT_STYLE_SYSTEM_ULTRA_THIN_MATERIAL, UIBlurEffectStyleSystemUltraThinMaterial);
@@ -578,10 +578,12 @@ - (id)createMenuPopup:(id)args
}
#endif
-#ifdef USE_TI_UIIOSBLURVIEW
+#ifdef USE_TI_UIBLURVIEW
- (id)createBlurView:(id)args
{
- return [[[TiUIiOSBlurViewProxy alloc] _initWithPageContext:[self executionContext] args:args] autorelease];
+ DEPRECATED_REPLACED(@"UI.iOS.BlurView", @"13.0.0", @"UI.BlurView (now cross platform!)");
+
+ return [[[TiUIBlurViewProxy alloc] _initWithPageContext:[self executionContext] args:args] autorelease];
}
#endif
diff --git a/iphone/Classes/UIModule.m b/iphone/Classes/UIModule.m
index 41d1c8ed3ee..9957e523d38 100644
--- a/iphone/Classes/UIModule.m
+++ b/iphone/Classes/UIModule.m
@@ -276,6 +276,12 @@ - (TiUIActivityIndicatorStyleProxy *)ActivityIndicatorStyle
MAKE_SYSTEM_PROP(LIST_ACCESSORY_TYPE_DETAIL, UITableViewCellAccessoryDetailDisclosureButton);
MAKE_SYSTEM_PROP(LIST_ACCESSORY_TYPE_DISCLOSURE, UITableViewCellAccessoryDisclosureIndicator);
+#ifdef USE_TI_UIBLURVIEW
+MAKE_SYSTEM_PROP(BLUR_EFFECT_STYLE_EXTRA_LIGHT, UIBlurEffectStyleExtraLight);
+MAKE_SYSTEM_PROP(BLUR_EFFECT_STYLE_LIGHT, UIBlurEffectStyleLight);
+MAKE_SYSTEM_PROP(BLUR_EFFECT_STYLE_DARK, UIBlurEffectStyleDark);
+#endif
+
- (void)setBackgroundColor:(id)color
{
TiRootViewController *controller = [[TiApp app] controller];
diff --git a/iphone/iphone/Titanium.xcodeproj/project.pbxproj b/iphone/iphone/Titanium.xcodeproj/project.pbxproj
index df66078f38d..b327f388c0b 100644
--- a/iphone/iphone/Titanium.xcodeproj/project.pbxproj
+++ b/iphone/iphone/Titanium.xcodeproj/project.pbxproj
@@ -166,8 +166,8 @@
3A527EB327E0F77700A470D6 /* TiUITableViewScrollPositionProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A527EB227E0F77700A470D6 /* TiUITableViewScrollPositionProxy.m */; };
3A5AD7261BB9A6E4005B408B /* TiUIiOSPreviewActionGroupProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A5AD7251BB9A6E4005B408B /* TiUIiOSPreviewActionGroupProxy.m */; };
3A5AD7291BB9BEA8005B408B /* TiUIiOSPreviewContextProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A5AD7281BB9BEA8005B408B /* TiUIiOSPreviewContextProxy.m */; };
- 3A811CD01C2C21E50023468C /* TiUIiOSBlurViewProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A811CCF1C2C21E50023468C /* TiUIiOSBlurViewProxy.m */; };
- 3A811CD31C2C21FE0023468C /* TiUIiOSBlurView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A811CD21C2C21FE0023468C /* TiUIiOSBlurView.m */; };
+ 3A811CD01C2C21E50023468C /* TiUIBlurViewProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A811CCF1C2C21E50023468C /* TiUIBlurViewProxy.m */; };
+ 3A811CD31C2C21FE0023468C /* TiUIBlurView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A811CD21C2C21FE0023468C /* TiUIBlurView.m */; };
3AA4EC262320352B00703A20 /* TiUIListView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AA4EC1D2320352B00703A20 /* TiUIListView.m */; };
3AA4EC272320352B00703A20 /* TiUIListItemProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AA4EC212320352B00703A20 /* TiUIListItemProxy.m */; };
3AA4EC282320352B00703A20 /* TiUIListSectionProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AA4EC232320352B00703A20 /* TiUIListSectionProxy.m */; };
@@ -620,10 +620,10 @@
3A5AD7251BB9A6E4005B408B /* TiUIiOSPreviewActionGroupProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TiUIiOSPreviewActionGroupProxy.m; sourceTree = ""; };
3A5AD7271BB9BEA8005B408B /* TiUIiOSPreviewContextProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TiUIiOSPreviewContextProxy.h; sourceTree = ""; };
3A5AD7281BB9BEA8005B408B /* TiUIiOSPreviewContextProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TiUIiOSPreviewContextProxy.m; sourceTree = ""; };
- 3A811CCE1C2C21E50023468C /* TiUIiOSBlurViewProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TiUIiOSBlurViewProxy.h; sourceTree = ""; };
- 3A811CCF1C2C21E50023468C /* TiUIiOSBlurViewProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TiUIiOSBlurViewProxy.m; sourceTree = ""; };
- 3A811CD11C2C21FE0023468C /* TiUIiOSBlurView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TiUIiOSBlurView.h; sourceTree = ""; };
- 3A811CD21C2C21FE0023468C /* TiUIiOSBlurView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TiUIiOSBlurView.m; sourceTree = ""; };
+ 3A811CCE1C2C21E50023468C /* TiUIBlurViewProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TiUIBlurViewProxy.h; sourceTree = ""; };
+ 3A811CCF1C2C21E50023468C /* TiUIBlurViewProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TiUIBlurViewProxy.m; sourceTree = ""; };
+ 3A811CD11C2C21FE0023468C /* TiUIBlurView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TiUIBlurView.h; sourceTree = ""; };
+ 3A811CD21C2C21FE0023468C /* TiUIBlurView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TiUIBlurView.m; sourceTree = ""; };
3AA4EC1C2320352B00703A20 /* TiUIListView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TiUIListView.h; sourceTree = ""; };
3AA4EC1D2320352B00703A20 /* TiUIListView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TiUIListView.m; sourceTree = ""; };
3AA4EC1E2320352B00703A20 /* TiUIListItemProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TiUIListItemProxy.h; sourceTree = ""; };
@@ -1085,6 +1085,7 @@
isa = PBXGroup;
children = (
DBF4B138200FD90600777136 /* Application Shortcuts */,
+ 3A811CCD1C2C21CA0023468C /* Blur View */,
DB4E9B7020B2E9510064ADF9 /* Navigation Window */,
DB0D54921F3B491300E2B771 /* Toolbar */,
BBDD81351A2C71E5003CDA10 /* AttributedString */,
@@ -1460,7 +1461,6 @@
isa = PBXGroup;
children = (
0BF933871CA0929A0091C7EC /* Stepper */,
- 3A811CCD1C2C21CA0023468C /* BlurView */,
3A0B56721C0CD7FE00709DA4 /* LivePhotoView */,
3A0E54341BE81111003EE654 /* MenuPopup */,
CAC225581BB79659008A332B /* ApplicationShortcuts */,
@@ -1719,15 +1719,15 @@
name = MenuPopup;
sourceTree = "";
};
- 3A811CCD1C2C21CA0023468C /* BlurView */ = {
+ 3A811CCD1C2C21CA0023468C /* Blur View */ = {
isa = PBXGroup;
children = (
- 3A811CCE1C2C21E50023468C /* TiUIiOSBlurViewProxy.h */,
- 3A811CCF1C2C21E50023468C /* TiUIiOSBlurViewProxy.m */,
- 3A811CD11C2C21FE0023468C /* TiUIiOSBlurView.h */,
- 3A811CD21C2C21FE0023468C /* TiUIiOSBlurView.m */,
+ 3A811CCE1C2C21E50023468C /* TiUIBlurViewProxy.h */,
+ 3A811CCF1C2C21E50023468C /* TiUIBlurViewProxy.m */,
+ 3A811CD11C2C21FE0023468C /* TiUIBlurView.h */,
+ 3A811CD21C2C21FE0023468C /* TiUIBlurView.m */,
);
- name = BlurView;
+ name = "Blur View";
sourceTree = "";
};
3AA4EC1B232034FE00703A20 /* ListView */ = {
@@ -2167,7 +2167,7 @@
24CA8B7C111161FE0084E2DE /* TiUITextArea.m in Sources */,
3A5AD7261BB9A6E4005B408B /* TiUIiOSPreviewActionGroupProxy.m in Sources */,
24CA8B7D111161FE0084E2DE /* TiUITabProxy.m in Sources */,
- 3A811CD01C2C21E50023468C /* TiUIiOSBlurViewProxy.m in Sources */,
+ 3A811CD01C2C21E50023468C /* TiUIBlurViewProxy.m in Sources */,
3A3BBAF51D3E2F0F008450DF /* TiAppiOSUserNotificationCenterProxy.m in Sources */,
CA0D39E51B7F55C6009D534C /* TiAppiOSSearchableItemAttributeSetProxy.m in Sources */,
24CA8B80111161FE0084E2DE /* TiUITableViewProxy.m in Sources */,
@@ -2289,7 +2289,7 @@
24E50E1C1160666300AF54AF /* LauncherView.m in Sources */,
24E50E27116066A800AF54AF /* TiUIDashboardViewProxy.m in Sources */,
24E50E30116066B400AF54AF /* TiUIDashboardView.m in Sources */,
- 3A811CD31C2C21FE0023468C /* TiUIiOSBlurView.m in Sources */,
+ 3A811CD31C2C21FE0023468C /* TiUIBlurView.m in Sources */,
3A0858432915C3C5002D5E3A /* TiActivityAttributes.swift in Sources */,
24E50F911160792D00AF54AF /* TiUIDashboardItemProxy.m in Sources */,
24596694118E70D300519F79 /* ApplicationRouting.m in Sources */,
diff --git a/iphone/iphone/project.xcconfig b/iphone/iphone/project.xcconfig
index 8df768982a5..2b4c9f65e82 100644
--- a/iphone/iphone/project.xcconfig
+++ b/iphone/iphone/project.xcconfig
@@ -1,5 +1,5 @@
TI_VERSION=0.0.0
JSCORE_LD_FLAGS=-weak_framework JavaScriptCore
GCC_DEFINITIONS=
-TI_SYMBOL_MACROS=USE_JSCORE_FRAMEWORK USE_TI_STREAM USE_TI_CODEC USE_TI_UTILS USE_TI_XML USE_TI_ACCELEROMETER USE_TI_API USE_TI_APP USE_TI_APPTRACKUSERINTERACTION USE_TI_CALENDAR USE_TI_CONTACTS USE_TI_DATABASE USE_TI_FILESYSTEM USE_TI_GEOLOCATION USE_TI_GESTURE USE_TI_MEDIA USE_TI_NETWORK USE_TI_NETWORKSOCKET USE_TI_PLATFORM USE_TI_PLATFORMIDENTIFIERFORADVERTISING USE_TI_PLATFORMGETIDENTIFIERFORADVERTISING USE_TI_WATCHSESSION USE_TI_UI USE_TI_UITAB USE_TI_UILABEL USE_TI_UIBUTTON USE_TI_UIPROGRESSBAR USE_TI_UISEARCHBAR USE_TI_UIACTIVITYINDICATOR USE_TI_UIOPTIONBAR USE_TI_UISLIDER USE_TI_UISWITCH USE_TI_UIPICKER USE_TI_UITEXTAREA USE_TI_UITEXTFIELD USE_TI_UIIMAGEVIEW USE_TI_UIMASKEDIMAGE USE_TI_UIWEBVIEW USE_TI_UIWINDOW USE_TI_UIVIEW USE_TI_UIOPTIONDIALOG USE_TI_UIEMAILDIALOG USE_TI_UIDASHBOARDVIEW USE_TI_UISCROLLVIEW USE_TI_UISCROLLABLEVIEW USE_TI_UITABLEVIEW USE_TI_UILISTVIEW USE_TI_UIANIMATION USE_TI_UIATTRIBUTEDSTRING USE_TI_UIACTIVITYINDICATORSTYLE USE_TI_UITOOLBAR USE_TI_UITABBEDBAR USE_TI_UIAPPLICATIONSHORTCUTS USE_TI_UINAVIGATIONWINDOW USE_TI_UICLIPBOARD USE_TI_UIIPAD USE_TI_UIIPADPOPOVER USE_TI_UIIPADSPLITWINDOW USE_TI_UIIPADSPLITWINDOWBUTTON USE_TI_UIIOS USE_TI_UIIOSADVIEW USE_TI_UIIOSCOVERFLOWVIEW USE_TI_UIIOSTOOLBAR USE_TI_UIIOSTABBEDBAR USE_TI_UIIOSDOCUMENTVIEWER USE_TI_UIIOSNAVIGATIONWINDOW USE_TI_UIIOSSPLITWINDOW USE_TI_UIIOSPREVIEWCONTEXT USE_TI_UIIOSMENUPOPUP USE_TI_UIIOSLIVEPHOTOVIEW USE_TI_UIIOSLIVEPHOTOBADGE USE_TI_UIIOSLIVEPHOTO_BADGE_OPTIONS_OVER_CONTENT USE_TI_UIIOSLIVEPHOTO_BADGE_OPTIONS_LIVE_OFF USE_TI_UIIOSALERTDIALOGSTYLE USE_TI_UIIOSANIMATIONSTYLE USE_TI_UIIOSLISTVIEWCELLSELECTIONSTYLE USE_TI_UIIOSTABLEVIEWCELLSELECTIONSTYLE USE_TI_UIIOSTABLEVIEWSCROLLPOSITION USE_TI_UIIOSLISTVIEWSCROLLPOSITION USE_TI_UIIOSTABLEVIEWSTYLE USE_TI_UIIOSLISTVIEWSTYLE USE_TI_UIIOSPROGRESSBARSTYLE USE_TI_UIIOSROWANIMATIONSTYLE USE_TI_UIIOSSCROLLINDICATORSTYLE USE_TI_UIIOSSTATUSBAR USE_TI_UIIOSBUTTONCONFIGURATION USE_TI_UIIOSSYSTEMBUTTON USE_TI_UIIOSSYSTEMICON USE_TI_UIIOSFEEDBACKGENERATOR USE_TI_UIIOSSTEPPER USE_TI_APPIOS USE_TI_APPIOSSEARCHABLEINDEX USE_TI_APPIOSSEARCHABLEITEM USE_TI_APPIOSSEARCHABLEITEMATTRIBUTESET USE_TI_APPIOSSEARCHQUERY USE_TI_APPIOSUSERACTIVITY USE_TI_APPIOSUSERNOTIFICATIONCENTER USE_TI_UIIOSANIMATOR USE_TI_UIIOSSNAPBEHAVIOR USE_TI_UIIOSPUSHBEHAVIOR USE_TI_UIIOSGRAVITYBEHAVIOR USE_TI_UIIOSANCHORATTACHMENTBEHAVIOR USE_TI_UIIOSVIEWATTACHMENTBEHAVIOR USE_TI_UIIOSCOLLISIONBEHAVIOR USE_TI_UIIOSDYNAMICITEMBEHAVIOR USE_TI_UIIOSTRANSITIONANIMATION USE_TI_UIREFRESHCONTROL USE_TI_UIIOSAPPLICATIONSHORTCUTS USE_TI_UISHORTCUT USE_TI_UISHORTCUTITEM USE_TI_UIIOSBLURVIEW USE_TI_NETWORKREGISTERFORPUSHNOTIFICATIONS USE_TI_SILENTPUSH USE_TI_FETCH USE_TI_MEDIASHOWCAMERA USE_TI_MEDIAHIDECAMERA USE_TI_MEDIAOPENPHOTOGALLERY USE_TI_MEDIATAKEPICTURE USE_TI_MEDIASTARTVIDEOCAPTURE USE_TI_MEDIASTOPVIDEOCAPTURE USE_TI_MEDIASWITCHCAMERA USE_TI_MEDIAREQUESTCAMERAPERMISSIONS USE_TI_MEDIAHASCAMERAPERMISSIONS USE_TI_MEDIAHASPHOTOGALLERYPERMISSIONS USE_TI_MEDIAREQUESTPHOTOGALLERYPERMISSIONS USE_TI_MEDIAOPENMUSICLIBRARY USE_TI_MEDIAHIDEMUSICLIBRARY USE_TI_MEDIAQUERYMUSICLIBRARY USE_TI_MEDIAREQUESTAUDIORECORDERPERMISSIONS USE_TI_MEDIAHASAUDIORECORDERPERMISSIONS USE_TI_MEDIAHASAUDIOPERMISSIONS USE_TI_MEDIAHASMUSICLIBRARYPERMISSIONS USE_TI_MEDIAREQUESTMUSICLIBRARYPERMISSIONS USE_TI_MEDIACANRECORD USE_TI_MEDIAISCAMERASUPPORTED USE_TI_MEDIAISMEDIATYPESUPPORTED USE_TI_MEDIASAVETOPHOTOGALLERY USE_TI_MEDIASTARTVIDEOEDITING USE_TI_MEDIASTOPVIDEOEDITING USE_TI_MEDIAAUDIOPLAYER USE_TI_MEDIAAUDIORECORDER USE_TI_MEDIAMUSICPLAYER USE_TI_MEDIASYSTEMMUSICPLAYER USE_TI_MEDIASYSTEMALERT USE_TI_MEDIAGETSYSTEMMUSICPLAYER USE_TI_MEDIAAPPMUSICPLAYER USE_TI_MEDIAGETAPPMUSICPLAYER USE_TI_MEDIAVIDEOPLAYER USE_TI_MEDIASOUND USE_TI_MEDIACAMERA_AUTHORIZATION_AUTHORIZED USE_TI_MEDIACAMERA_AUTHORIZATION_DENIED USE_TI_MEDIACAMERA_AUTHORIZATION_RESTRICTED USE_TI_MEDIACAMERA_AUTHORIZATION_UNKNOWN USE_TI_MEDIACAMERA_FRONT USE_TI_MEDIACAMERA_REAR USE_TI_MEDIACAMERA_FLASH_OFF USE_TI_MEDIACAMERA_FLASH_AUTO USE_TI_MEDIACAMERA_FLASH_ON USE_TI_MEDIACAMERAFLASHMODE USE_TI_MEDIAAVAILABLECAMERAMEDIATYPES USE_TI_MEDIAAVAILABLEPHOTOMEDIATYPES USE_TI_MEDIAAVAILABLEPHOTOGALLERYMEDIATYPES USE_TI_MEDIAAVAILABLECAMERAS USE_TI_MEDIACAMERAAUTHORIZATION USE_TI_MEDIAVOLUME USE_TI_MEDIAAUDIOPLAYING USE_TI_MEDIACURRENTROUTE USE_TI_MEDIAVIBRATE USE_TI_MEDIABEEP USE_TI_MEDIASTARTMICROPHONEMONITOR USE_TI_MEDIASTOPMICROPHONEMONITOR USE_TI_MEDIAPEAKMICROPHONEPOWER USE_TI_MEDIAGETPEAKMICROPHONEPOWER USE_TI_MEDIAAVERAGEMICROPHONEPOWER USE_TI_MEDIAGETAVERAGEMICROPHONEPOWER USE_TI_UITABLEVIEWSCROLLPOSITION USE_TI_UILISTVIEWSCROLLPOSITION USE_TI_PLATFORMUPTIME
+TI_SYMBOL_MACROS=USE_JSCORE_FRAMEWORK USE_TI_STREAM USE_TI_CODEC USE_TI_UTILS USE_TI_XML USE_TI_ACCELEROMETER USE_TI_API USE_TI_APP USE_TI_APPTRACKUSERINTERACTION USE_TI_CALENDAR USE_TI_CONTACTS USE_TI_DATABASE USE_TI_FILESYSTEM USE_TI_GEOLOCATION USE_TI_GESTURE USE_TI_MEDIA USE_TI_NETWORK USE_TI_NETWORKSOCKET USE_TI_PLATFORM USE_TI_PLATFORMIDENTIFIERFORADVERTISING USE_TI_PLATFORMGETIDENTIFIERFORADVERTISING USE_TI_WATCHSESSION USE_TI_UI USE_TI_UITAB USE_TI_UILABEL USE_TI_UIBUTTON USE_TI_UIPROGRESSBAR USE_TI_UISEARCHBAR USE_TI_UIACTIVITYINDICATOR USE_TI_UIOPTIONBAR USE_TI_UISLIDER USE_TI_UISWITCH USE_TI_UIPICKER USE_TI_UITEXTAREA USE_TI_UITEXTFIELD USE_TI_UIIMAGEVIEW USE_TI_UIMASKEDIMAGE USE_TI_UIWEBVIEW USE_TI_UIWINDOW USE_TI_UIVIEW USE_TI_UIOPTIONDIALOG USE_TI_UIEMAILDIALOG USE_TI_UIDASHBOARDVIEW USE_TI_UISCROLLVIEW USE_TI_UISCROLLABLEVIEW USE_TI_UITABLEVIEW USE_TI_UILISTVIEW USE_TI_UIANIMATION USE_TI_UIATTRIBUTEDSTRING USE_TI_UIACTIVITYINDICATORSTYLE USE_TI_UITOOLBAR USE_TI_UITABBEDBAR USE_TI_UIAPPLICATIONSHORTCUTS USE_TI_UINAVIGATIONWINDOW USE_TI_UICLIPBOARD USE_TI_UIIPAD USE_TI_UIIPADPOPOVER USE_TI_UIIPADSPLITWINDOW USE_TI_UIIPADSPLITWINDOWBUTTON USE_TI_UIIOS USE_TI_UIIOSADVIEW USE_TI_UIIOSCOVERFLOWVIEW USE_TI_UIIOSTOOLBAR USE_TI_UIIOSTABBEDBAR USE_TI_UIIOSDOCUMENTVIEWER USE_TI_UIIOSNAVIGATIONWINDOW USE_TI_UIIOSSPLITWINDOW USE_TI_UIIOSPREVIEWCONTEXT USE_TI_UIIOSMENUPOPUP USE_TI_UIIOSLIVEPHOTOVIEW USE_TI_UIIOSLIVEPHOTOBADGE USE_TI_UIIOSLIVEPHOTO_BADGE_OPTIONS_OVER_CONTENT USE_TI_UIIOSLIVEPHOTO_BADGE_OPTIONS_LIVE_OFF USE_TI_UIIOSALERTDIALOGSTYLE USE_TI_UIIOSANIMATIONSTYLE USE_TI_UIIOSLISTVIEWCELLSELECTIONSTYLE USE_TI_UIIOSTABLEVIEWCELLSELECTIONSTYLE USE_TI_UIIOSTABLEVIEWSCROLLPOSITION USE_TI_UIIOSLISTVIEWSCROLLPOSITION USE_TI_UIIOSTABLEVIEWSTYLE USE_TI_UIIOSLISTVIEWSTYLE USE_TI_UIIOSPROGRESSBARSTYLE USE_TI_UIIOSROWANIMATIONSTYLE USE_TI_UIIOSSCROLLINDICATORSTYLE USE_TI_UIIOSSTATUSBAR USE_TI_UIIOSBUTTONCONFIGURATION USE_TI_UIIOSSYSTEMBUTTON USE_TI_UIIOSSYSTEMICON USE_TI_UIIOSFEEDBACKGENERATOR USE_TI_UIIOSSTEPPER USE_TI_APPIOS USE_TI_APPIOSSEARCHABLEINDEX USE_TI_APPIOSSEARCHABLEITEM USE_TI_APPIOSSEARCHABLEITEMATTRIBUTESET USE_TI_APPIOSSEARCHQUERY USE_TI_APPIOSUSERACTIVITY USE_TI_APPIOSUSERNOTIFICATIONCENTER USE_TI_UIIOSANIMATOR USE_TI_UIIOSSNAPBEHAVIOR USE_TI_UIIOSPUSHBEHAVIOR USE_TI_UIIOSGRAVITYBEHAVIOR USE_TI_UIIOSANCHORATTACHMENTBEHAVIOR USE_TI_UIIOSVIEWATTACHMENTBEHAVIOR USE_TI_UIIOSCOLLISIONBEHAVIOR USE_TI_UIIOSDYNAMICITEMBEHAVIOR USE_TI_UIIOSTRANSITIONANIMATION USE_TI_UIREFRESHCONTROL USE_TI_UIIOSAPPLICATIONSHORTCUTS USE_TI_UISHORTCUT USE_TI_UISHORTCUTITEM USE_TI_UIIOSBLURVIEW USE_TI_UIBLURVIEW USE_TI_NETWORKREGISTERFORPUSHNOTIFICATIONS USE_TI_SILENTPUSH USE_TI_FETCH USE_TI_MEDIASHOWCAMERA USE_TI_MEDIAHIDECAMERA USE_TI_MEDIAOPENPHOTOGALLERY USE_TI_MEDIATAKEPICTURE USE_TI_MEDIASTARTVIDEOCAPTURE USE_TI_MEDIASTOPVIDEOCAPTURE USE_TI_MEDIASWITCHCAMERA USE_TI_MEDIAREQUESTCAMERAPERMISSIONS USE_TI_MEDIAHASCAMERAPERMISSIONS USE_TI_MEDIAHASPHOTOGALLERYPERMISSIONS USE_TI_MEDIAREQUESTPHOTOGALLERYPERMISSIONS USE_TI_MEDIAOPENMUSICLIBRARY USE_TI_MEDIAHIDEMUSICLIBRARY USE_TI_MEDIAQUERYMUSICLIBRARY USE_TI_MEDIAREQUESTAUDIORECORDERPERMISSIONS USE_TI_MEDIAHASAUDIORECORDERPERMISSIONS USE_TI_MEDIAHASAUDIOPERMISSIONS USE_TI_MEDIAHASMUSICLIBRARYPERMISSIONS USE_TI_MEDIAREQUESTMUSICLIBRARYPERMISSIONS USE_TI_MEDIACANRECORD USE_TI_MEDIAISCAMERASUPPORTED USE_TI_MEDIAISMEDIATYPESUPPORTED USE_TI_MEDIASAVETOPHOTOGALLERY USE_TI_MEDIASTARTVIDEOEDITING USE_TI_MEDIASTOPVIDEOEDITING USE_TI_MEDIAAUDIOPLAYER USE_TI_MEDIAAUDIORECORDER USE_TI_MEDIAMUSICPLAYER USE_TI_MEDIASYSTEMMUSICPLAYER USE_TI_MEDIASYSTEMALERT USE_TI_MEDIAGETSYSTEMMUSICPLAYER USE_TI_MEDIAAPPMUSICPLAYER USE_TI_MEDIAGETAPPMUSICPLAYER USE_TI_MEDIAVIDEOPLAYER USE_TI_MEDIASOUND USE_TI_MEDIACAMERA_AUTHORIZATION_AUTHORIZED USE_TI_MEDIACAMERA_AUTHORIZATION_DENIED USE_TI_MEDIACAMERA_AUTHORIZATION_RESTRICTED USE_TI_MEDIACAMERA_AUTHORIZATION_UNKNOWN USE_TI_MEDIACAMERA_FRONT USE_TI_MEDIACAMERA_REAR USE_TI_MEDIACAMERA_FLASH_OFF USE_TI_MEDIACAMERA_FLASH_AUTO USE_TI_MEDIACAMERA_FLASH_ON USE_TI_MEDIACAMERAFLASHMODE USE_TI_MEDIAAVAILABLECAMERAMEDIATYPES USE_TI_MEDIAAVAILABLEPHOTOMEDIATYPES USE_TI_MEDIAAVAILABLEPHOTOGALLERYMEDIATYPES USE_TI_MEDIAAVAILABLECAMERAS USE_TI_MEDIACAMERAAUTHORIZATION USE_TI_MEDIAVOLUME USE_TI_MEDIAAUDIOPLAYING USE_TI_MEDIACURRENTROUTE USE_TI_MEDIAVIBRATE USE_TI_MEDIABEEP USE_TI_MEDIASTARTMICROPHONEMONITOR USE_TI_MEDIASTOPMICROPHONEMONITOR USE_TI_MEDIAPEAKMICROPHONEPOWER USE_TI_MEDIAGETPEAKMICROPHONEPOWER USE_TI_MEDIAAVERAGEMICROPHONEPOWER USE_TI_MEDIAGETAVERAGEMICROPHONEPOWER USE_TI_UITABLEVIEWSCROLLPOSITION USE_TI_UILISTVIEWSCROLLPOSITION USE_TI_PLATFORMUPTIME