diff --git a/android/build.gradle b/android/build.gradle index aaae11c7..5ab8633d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,10 +2,14 @@ buildscript { repositories { jcenter() + maven { + url 'https://maven.google.com/' + name 'Google' + } } dependencies { - classpath 'com.android.tools.build:gradle:1.3.1' + classpath 'com.android.tools.build:gradle:4.0.1' } } @@ -28,6 +32,10 @@ android { repositories { mavenCentral() + maven { + url 'https://maven.google.com/' + name 'Google' + } } dependencies { diff --git a/android/src/main/java/com/reactnativecommunity/progressview/BUCK b/android/src/main/java/com/reactnativecommunity/progressview/BUCK new file mode 100755 index 00000000..ad2fd405 --- /dev/null +++ b/android/src/main/java/com/reactnativecommunity/progressview/BUCK @@ -0,0 +1,19 @@ +load("//tools/build_defs/oss:rn_defs.bzl", "YOGA_TARGET", "react_native_dep", "react_native_target", "rn_android_library") + +rn_android_library( + name = "progressview", + srcs = glob(["*.java"]), + visibility = [ + "PUBLIC", + ], + deps = [ + YOGA_TARGET, + react_native_dep("third-party/java/infer-annotations:infer-annotations"), + react_native_dep("third-party/java/jsr-305:jsr-305"), + react_native_target("java/com/facebook/react/bridge:bridge"), + react_native_target("java/com/facebook/react/common:common"), + react_native_target("java/com/facebook/react/module/annotations:annotations"), + react_native_target("java/com/facebook/react/uimanager:uimanager"), + react_native_target("java/com/facebook/react/uimanager/annotations:annotations"), + ], +) diff --git a/android/src/main/java/com/reactnativecommunity/progressview/RNCProgressViewContainerView.java b/android/src/main/java/com/reactnativecommunity/progressview/RNCProgressViewContainerView.java new file mode 100755 index 00000000..a02b0c3e --- /dev/null +++ b/android/src/main/java/com/reactnativecommunity/progressview/RNCProgressViewContainerView.java @@ -0,0 +1,98 @@ +// Copyright (c) Facebook, Inc. and its affiliates. + +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +package com.reactnativecommunity.progressview; + +import javax.annotation.Nullable; + +import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ProgressBar; + +import com.facebook.react.bridge.JSApplicationIllegalArgumentException; + +/** + * Controls an enclosing ProgressBar. Exists so that the ProgressBar can be recreated if + * the style would change. + */ +/* package */ class RNCProgressViewContainerView extends FrameLayout { + private static final int MAX_PROGRESS = 1000; + + private @Nullable Integer mColor; + private boolean mIndeterminate = true; + private boolean mAnimating = true; + private double mProgress; + private @Nullable ProgressBar mProgressBar; + + public RNCProgressViewContainerView(Context context) { + super(context); + } + + public void setStyle(@Nullable String styleName) { + int style = RNCProgressViewViewManager.getStyleFromString(styleName); + mProgressBar = RNCProgressViewViewManager.createProgressBar(getContext(), style); + mProgressBar.setMax(MAX_PROGRESS); + removeAllViews(); + addView( + mProgressBar, + new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + } + + public void setColor(@Nullable Integer color) { + this.mColor = color; + } + + public void setIndeterminate(boolean indeterminate) { + mIndeterminate = indeterminate; + } + + public void setProgress(double progress) { + mProgress = progress; + } + + public void setAnimating(boolean animating) { + mAnimating = animating; + } + + public void apply() { + if (mProgressBar == null) { + throw new JSApplicationIllegalArgumentException("setStyle() not called"); + } + + mProgressBar.setIndeterminate(mIndeterminate); + setColor(mProgressBar); + mProgressBar.setProgress((int) (mProgress * MAX_PROGRESS)); + if (mAnimating) { + mProgressBar.setVisibility(View.VISIBLE); + } else { + mProgressBar.setVisibility(View.GONE); + } + } + + private void setColor(ProgressBar progressBar) { + Drawable drawable; + if (progressBar.isIndeterminate()) { + drawable = progressBar.getIndeterminateDrawable(); + } else { + drawable = progressBar.getProgressDrawable(); + } + + if (drawable == null) { + return; + } + + if (mColor != null) { + drawable.setColorFilter(mColor, PorterDuff.Mode.SRC_IN); + } else { + drawable.clearColorFilter(); + } + } +} diff --git a/android/src/main/java/com/reactnativecommunity/progressview/RNCProgressViewModule.java b/android/src/main/java/com/reactnativecommunity/progressview/RNCProgressViewModule.java deleted file mode 100644 index 467b16d9..00000000 --- a/android/src/main/java/com/reactnativecommunity/progressview/RNCProgressViewModule.java +++ /dev/null @@ -1,22 +0,0 @@ - -package com.reactnativecommunity.progressview; - -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.Callback; - -public class RNCProgressViewModule extends ReactContextBaseJavaModule { - - private final ReactApplicationContext reactContext; - - public RNCProgressViewModule(ReactApplicationContext reactContext) { - super(reactContext); - this.reactContext = reactContext; - } - - @Override - public String getName() { - return "RNCProgressView"; - } -} \ No newline at end of file diff --git a/android/src/main/java/com/reactnativecommunity/progressview/RNCProgressViewPackage.java b/android/src/main/java/com/reactnativecommunity/progressview/RNCProgressViewPackage.java index 20919b51..0f00d640 100644 --- a/android/src/main/java/com/reactnativecommunity/progressview/RNCProgressViewPackage.java +++ b/android/src/main/java/com/reactnativecommunity/progressview/RNCProgressViewPackage.java @@ -1,4 +1,3 @@ - package com.reactnativecommunity.progressview; import java.util.Arrays; @@ -6,23 +5,25 @@ import java.util.List; import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ViewManager; -import com.facebook.react.bridge.JavaScriptModule; + public class RNCProgressViewPackage implements ReactPackage { - @Override - public List createNativeModules(ReactApplicationContext reactContext) { - return Arrays.asList(new RNCProgressViewModule(reactContext)); - } + @Override + public List createNativeModules(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } - // Deprecated from RN 0.47 - public List> createJSModules() { - return Collections.emptyList(); - } + // Deprecated from RN 0.47 + public List> createJSModules() { + return Collections.emptyList(); + } - @Override - public List createViewManagers(ReactApplicationContext reactContext) { - return Collections.emptyList(); - } -} \ No newline at end of file + @Override + @SuppressWarnings("rawtypes") + public List createViewManagers(ReactApplicationContext reactContext) { + return Collections.singletonList(new RNCProgressViewViewManager()); + } +} diff --git a/android/src/main/java/com/reactnativecommunity/progressview/RNCProgressViewShadowNode.java b/android/src/main/java/com/reactnativecommunity/progressview/RNCProgressViewShadowNode.java new file mode 100755 index 00000000..e61cdff3 --- /dev/null +++ b/android/src/main/java/com/reactnativecommunity/progressview/RNCProgressViewShadowNode.java @@ -0,0 +1,78 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.reactnativecommunity.progressview; + +import android.util.SparseIntArray; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import com.facebook.react.uimanager.LayoutShadowNode; +import com.facebook.react.uimanager.annotations.ReactProp; +import com.facebook.yoga.YogaMeasureFunction; +import com.facebook.yoga.YogaMeasureMode; +import com.facebook.yoga.YogaMeasureOutput; +import com.facebook.yoga.YogaNode; +import java.util.HashSet; +import java.util.Set; +import javax.annotation.Nullable; + +/** + * Node responsible for holding the style of the ProgressBar, see under + * {@link android.R.attr.progressBarStyle} for possible styles. ReactProgressBarViewManager + * manages how this style is applied to the ProgressBar. + */ +public class RNCProgressViewShadowNode extends LayoutShadowNode implements YogaMeasureFunction { + + private String mStyle = RNCProgressViewViewManager.DEFAULT_STYLE; + + private final SparseIntArray mHeight; + private final SparseIntArray mWidth; + private final Set mMeasured; + + public RNCProgressViewShadowNode() { + mHeight = new SparseIntArray(); + mWidth = new SparseIntArray(); + mMeasured = new HashSet<>(); + initMeasureFunction(); + } + + private void initMeasureFunction() { + setMeasureFunction(this); + } + + public @Nullable String getStyle() { + return mStyle; + } + + @ReactProp(name = RNCProgressViewViewManager.PROP_STYLE) + public void setStyle(@Nullable String style) { + mStyle = style == null ? RNCProgressViewViewManager.DEFAULT_STYLE : style; + } + + @Override + public long measure( + YogaNode node, + float width, + YogaMeasureMode widthMode, + float height, + YogaMeasureMode heightMode) { + final int style = RNCProgressViewViewManager.getStyleFromString(getStyle()); + if (!mMeasured.contains(style)) { + ProgressBar progressBar = RNCProgressViewViewManager.createProgressBar(getThemedContext(), style); + final int spec = View.MeasureSpec.makeMeasureSpec( + ViewGroup.LayoutParams.WRAP_CONTENT, + View.MeasureSpec.UNSPECIFIED); + progressBar.measure(spec, spec); + mHeight.put(style, progressBar.getMeasuredHeight()); + mWidth.put(style, progressBar.getMeasuredWidth()); + mMeasured.add(style); + } + + return YogaMeasureOutput.make(mWidth.get(style), mHeight.get(style)); + } +} diff --git a/android/src/main/java/com/reactnativecommunity/progressview/RNCProgressViewViewManager.java b/android/src/main/java/com/reactnativecommunity/progressview/RNCProgressViewViewManager.java new file mode 100755 index 00000000..609cf32a --- /dev/null +++ b/android/src/main/java/com/reactnativecommunity/progressview/RNCProgressViewViewManager.java @@ -0,0 +1,131 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.reactnativecommunity.progressview; + +import javax.annotation.Nullable; + +import android.content.Context; +import android.widget.ProgressBar; + +import com.facebook.react.bridge.JSApplicationIllegalArgumentException; +import com.facebook.react.module.annotations.ReactModule; +import com.facebook.react.uimanager.BaseViewManager; +import com.facebook.react.uimanager.annotations.ReactProp; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.ViewProps; + +/** + * Manages instances of ProgressBar. ProgressBar is wrapped in a + * ProgressBarContainerView because the style of the ProgressBar can only be set + * in the constructor; whenever the style of a ProgressBar changes, we have to + * drop the existing ProgressBar (if there is one) and create a new one with the + * style given. + */ +@ReactModule(name = RNCProgressViewViewManager.REACT_CLASS) +public class RNCProgressViewViewManager extends BaseViewManager { + + public static final String REACT_CLASS = "RNCProgressView"; + + /* package */ static final String PROP_STYLE = "styleAttr"; + /* package */ static final String PROP_INDETERMINATE = "indeterminate"; + /* package */ static final String PROP_PROGRESS = "progress"; + /* package */ static final String PROP_ANIMATING = "animating"; + + /* package */ static final String DEFAULT_STYLE = "Normal"; + + private static final Object sProgressBarCtorLock = new Object(); + + /** + * We create ProgressBars on both the UI and shadow threads. There is a race + * condition in the ProgressBar constructor that may cause crashes when two + * ProgressBars are constructed at the same time on two different threads. This + * static ctor wrapper protects against that. + */ + public static ProgressBar createProgressBar(Context context, int style) { + synchronized (sProgressBarCtorLock) { + return new ProgressBar(context, null, style); + } + } + + @Override + public String getName() { + return REACT_CLASS; + } + + @Override + protected RNCProgressViewContainerView createViewInstance(ThemedReactContext context) { + return new RNCProgressViewContainerView(context); + } + + @ReactProp(name = PROP_STYLE) + public void setStyle(RNCProgressViewContainerView view, @Nullable String styleName) { + view.setStyle(styleName); + } + + @ReactProp(name = ViewProps.COLOR, customType = "Color") + public void setColor(RNCProgressViewContainerView view, @Nullable Integer color) { + view.setColor(color); + } + + @ReactProp(name = PROP_INDETERMINATE) + public void setIndeterminate(RNCProgressViewContainerView view, boolean indeterminate) { + view.setIndeterminate(indeterminate); + } + + @ReactProp(name = PROP_PROGRESS) + public void setProgress(RNCProgressViewContainerView view, double progress) { + view.setProgress(progress); + } + + @ReactProp(name = PROP_ANIMATING) + public void setAnimating(RNCProgressViewContainerView view, boolean animating) { + view.setAnimating(animating); + } + + @Override + public RNCProgressViewShadowNode createShadowNodeInstance() { + return new RNCProgressViewShadowNode(); + } + + @Override + public Class getShadowNodeClass() { + return RNCProgressViewShadowNode.class; + } + + @Override + public void updateExtraData(RNCProgressViewContainerView root, Object extraData) { + // do nothing + } + + @Override + protected void onAfterUpdateTransaction(RNCProgressViewContainerView view) { + view.apply(); + } + + /* package */ static int getStyleFromString(@Nullable String styleStr) { + if (styleStr == null) { + throw new JSApplicationIllegalArgumentException("ProgressBar needs to have a style, null received"); + } else if (styleStr.equals("Horizontal")) { + return android.R.attr.progressBarStyleHorizontal; + } else if (styleStr.equals("Small")) { + return android.R.attr.progressBarStyleSmall; + } else if (styleStr.equals("Large")) { + return android.R.attr.progressBarStyleLarge; + } else if (styleStr.equals("Inverse")) { + return android.R.attr.progressBarStyleInverse; + } else if (styleStr.equals("SmallInverse")) { + return android.R.attr.progressBarStyleSmallInverse; + } else if (styleStr.equals("LargeInverse")) { + return android.R.attr.progressBarStyleLargeInverse; + } else if (styleStr.equals("Normal")) { + return android.R.attr.progressBarStyle; + } else { + throw new JSApplicationIllegalArgumentException("Unknown ProgressBar style: " + styleStr); + } + } +} diff --git a/js/ProgressView.android.js b/js/ProgressView.android.js index 0cf16f07..ae9a7a42 100644 --- a/js/ProgressView.android.js +++ b/js/ProgressView.android.js @@ -1,43 +1,74 @@ + /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * + * @flow */ 'use strict'; -import * as React from 'react'; -import {Text, View, StyleSheet} from 'react-native'; - -class DummyProgressViewIOS extends React.Component { - render() { - return ( - - - ProgressViewIOS is not supported on this platform! - - - ); - } -} - -const styles = StyleSheet.create({ - dummy: { - width: 120, - height: 20, - backgroundColor: '#ffbcbc', - borderWidth: 1, - borderColor: 'red', - alignItems: 'center', - justifyContent: 'center', - }, - text: { - color: '#333333', - margin: 5, - fontSize: 10, - }, -}); - -export default DummyProgressViewIOS; +import React from 'react'; + +import ProgressViewAndroidNativeComponent from './RNCProgressViewNativeComponent'; + +import type {ViewProps} from 'react-native/Libraries/Components/View/ViewPropTypes'; + +export type ProgressViewAndroidProps = $ReadOnly<{| + ...ViewProps, + + /** + * Style of the ProgressBar and whether it shows indeterminate progress (e.g. spinner). + * + * `indeterminate` can only be false if `styleAttr` is Horizontal, and requires a + * `progress` value. + */ + ... + | {| + styleAttr: 'Horizontal', + indeterminate: false, + progress: number, + |} + | {| + typeAttr: + | 'Horizontal' + | 'Normal' + | 'Small' + | 'Large' + | 'Inverse' + | 'SmallInverse' + | 'LargeInverse', + indeterminate: true, + |}, + /** + * Whether to show the ProgressBar (true, the default) or hide it (false). + */ + animating?: ?boolean, + /** + * Color of the progress bar. + */ + color?: ?string, + /** + * Used to locate this view in end-to-end tests. + */ + testID?: ?string, +|}>; + +const ProgressViewAndroid = ( + props: ProgressViewAndroidProps, + forwardedRef: ?React.Ref, +) => { + return ; +}; + +const ProgressViewAndroidToExport = React.forwardRef(ProgressViewAndroid); + +ProgressViewAndroidToExport.defaultProps = { + styleAttr: 'Normal', + indeterminate: true, + animating: true, +}; + +export default (ProgressViewAndroidToExport: ProgressViewAndroidNativeComponent); \ No newline at end of file diff --git a/js/RNCProgressViewNativeComponent.android.js b/js/RNCProgressViewNativeComponent.android.js new file mode 100644 index 00000000..3d0967e8 --- /dev/null +++ b/js/RNCProgressViewNativeComponent.android.js @@ -0,0 +1,34 @@ + +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +'use strict'; + +import {requireNativeComponent} from 'react-native'; +import type {HostComponent} from 'react-native/Libraries/Renderer/shims/ReactNativeTypes'; +import type {ViewProps} from 'react-native/Libraries/Components/View/ViewPropTypes'; + +type NativeProps = $ReadOnly<{| + ...ViewProps, + + //Props + styleAttr?: string, + typeAttr?: string, + indeterminate: boolean, + progress?: number, + animating?: boolean, + color?: ?string, + testID?: ?string, +|}>; + +type NativeProgressViewAndroid = HostComponent; + +export default ((requireNativeComponent( + 'RNCProgressView', + ): any): NativeProgressViewIOS); \ No newline at end of file