How can I pass the current theme of the app into StyleSheet.create()? - react-native

Due to various limitations, I cannot store the current theme in props. I have to use a custom props that I cannot modify at the moment. I have been able to get around this by using React Context to store the current theme.
I have a class called pageStyle. In it, I have a pageContainer that I'd like to be able to set backgroundColor either to dark or light values.
I am able to get the current theme in a separate class R by using this.context.theme. R is also where I am rendering the FlatList object and passing style={pageStyle.pageContainer}.
I could create two separate pageContainers, called pageContainerDark and pageContainerLight, and have the correct respective backgroundColors for both. However, this feels messy to me. This would also require having a method for each style page to determine which pageContainer to use and is not scalable whatsoever. I have also tried using a global variable but was having some issues with this.
Is there a cleaner way to set pageContainer's backgroundColor based on the current theme?
I have attached the pageStyle.ts file below.
//pageStyle.ts
import { StyleSheet} from 'react-native';
const pageStyles = StyleSheet.create({
pageContainer: {
flex: 1,
backgroundColor: "#232323"
},
// pageContainerDark: {
// flex: 1,
// backgroundColor: "#232323"
// },
// pageContainerLight: {
// flex: 1,
// backgroundColor: "#FFFFFF"
// },
pageContentContainer: {
paddingBottom: 0
}
});
// export function getPageContainerFromPageStyles(theme: string) {
// if (theme == 'light') {
// return pageStyle.pageContainerForLightTheme
// } else {
// return pageStyle.pageContainerForDarkTheme
// }
// }
export { pageStyle };

Related

Animated state changes without cycling values using interpolation

I'm trying to implement a simple color transition in react native. The current use case is for an Animated.TextInput, I want the backgroundColor to transition depending on the state. Let's say the component can have the following visual states:
state
description
normal
unfocussed input field
error
unfocussed input field with an error
warning
unfocussed input field with a warning
focus
focussed input field disregarding the error/warning flags
A simplified version of such component:
import React, { useState, useEffect } from 'react'
import { Animated, TextInput as RNTextInput } from 'react-native'
const AnimatedTextInput = Animated.createAnimatedComponent(RNTextInput)
interface Props {
errorMessage?: string
warningMessage?: string
}
enum IndicatorState {
Normal,
Error,
Warning,
Focus
}
export const TextInput: React.FunctionComponent<Props> = (props) => {
const [isInFocus, setIsInFocus] = useState(false)
useEffect(() => {
Animated.timing(indicatorStateAnim, {
toValue: getIndicatorState(),
duration: 100,
useNativeDriver: false
})
}, [props.errorMessage, props.warningMessage, isInFocus])
const getIndicatorState = (): IndicatorState => {
if (isInFocus) return IndicatorState.Focus
if (props.errorMessage) return IndicatorState.Error
if (props.warningMessage) return IndicatorState.Error
return IndicatorState.Normal
}
const indicatorStateAnim = new Animated.Value(getIndicatorState())
return (
<AnimatedTextInput
onFocus={() => setIsInFocus(true)}
onBlur={() => setIsInFocus(false)}
style={{
backgroundColor: indicatorStateAnim.interpolate({
inputRange: [
IndicatorState.Normal,
IndicatorState.Error,
IndicatorState.Warning,
IndicatorState.Focus
],
outputRange: [
'#e3e3e3',
'#ff0000',
'#ffff00',
'#0000ff'
]
})
}}
/>
)
}
Using this method switching between two states does transition but it does so by cycling through the interpolated values (as expected of course). A little diagram showing the logic and difference between the current – expected – situation and the desired outcome of transitions.
Possible transition graphed in current and desired situation:
Example of transition between states in current and desired situation:
In short; I'm looking for a solution for transitioning between multiple visual states using an animation without cycling through interpolated values (a.k.a. disco input fields)

How to style new #fluentui/react-button [v8 beta]?

Is there any documentation on how to apply style customisations to the new Button? The styling using the IButtonStyles interface is broken on the new button, as per code below.
import { PrimaryButton } from '#fluentui/react';
import { Button as FluentButton } from '#fluentui/react-button';
const btnStyles = {
rootHovered: {
background: "red"
}
};
return (
<div>
<PrimaryButton styles={btnStyles}/>
<FluentButton styles={btnStyles}/>
</div>
)
Have looked into the file where the new react-button is defined, seems to need a ButtonTokens. Guessing this is along with the wider Fluent UI trend of moving to style tokens.
const btnStyle : ButtonTokens = { borderColor: 'red' }
return (
<FluentButton tokens={btnStyle} />
)

React-Native HighCharts Boost Module Usage

I have a data set of four series each with 250000 points so approximately one million in total. I am creating an app with React Native. I tried using minimal HighCharts Wrapper but couldn't make the boost module work. Here is my App.js:
import React from 'react';
import { StyleSheet, View } from 'react-native';
import HighchartsReactNative from '#highcharts/highcharts-react-native'
const modules = ['boost']
export default class App extends React.Component {
constructor(props) {
super(props);
let chartSeries = []
for(let i = 0; i < 4 ; i ++){
let series = {data:[]};
for(let i= 0; i < 2500; i++){
let point = [i+1,Math.floor(220-Math.random(0,120)*10)]
series.data.push(point)
}
chartSeries.push(series);
}
this.state = {
chartOptions: {
boost:{
enabled:true,
seriesThreshold:2000
},
chart:{
zoomType:'xy'
},
series: chartSeries
}
};
}
render() {
return (
<View style={styles.container}>
<HighchartsReactNative
styles={styles.container}
options={this.state.chartOptions}
modules={modules}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#fff',
justifyContent: 'center',
flex: 1
}
});
It is really simple and it renders eventually but it takes too much time. Boost module doesn't seem to work correctly. Is my usage wrong here?
It does not work correctly, because you set the boost.seriesThreshold equal to 2000 what means that the chart does not switch into the boost module until it has got at least 2000 series.
I think you had intention to set the plotOptions.series.boostThreshold to configure the boost so that it will turn on when certain series has 2000 points, didn't you?
In sum, you just need to set the mentioned seriesThreshold to 1, remove the boost: true (as it is redundant, and true by default), and then set the plotOptions.series.boostThreshold parameter equal to 2000.
API References:
https://api.highcharts.com/highcharts/boost.seriesThreshold
https://api.highcharts.com/highcharts/plotOptions.series.boostThreshold

Trying to load obj & mtl file with Three.js in React Native

Main objective : Load animated models exported from Maya into React Native app
Exported files : obj, mtl & png file
I have setup https://github.com/react-community/react-native-webgl in my React Native project and it is working properly.
Now, when I am trying to load the MTL file using the MTLLoader, I am getting following error:
Can't find variable: document
Apparently, the MTLLoader is calling TextureLoader which internally calls some load function which has 'document' reference. So what could be the solution to this ?
Here are the two files that I am using:
three.js
const THREE = require("three");
global.THREE = THREE;
if (!window.addEventListener)
window.addEventListener = () => { };
// require("three/examples/js/renderers/Projector");
require("three/examples/js/loaders/MTLLoader");
require("three/examples/js/loaders/OBJLoader");
export default THREE;
ThreeView.js
import React, { Component } from "react";
import { StyleSheet, View } from "react-native";
import { WebGLView } from "react-native-webgl";
import THREE from "./three";
import { image } from "src/res/image";
export default class ThreeView extends Component {
requestId: *;
componentWillUnmount() {
cancelAnimationFrame(this.requestId);
}
onContextCreate = (gl: WebGLRenderingContext) => {
const rngl = gl.getExtension("RN");
const { drawingBufferWidth: width, drawingBufferHeight: height } = gl;
const renderer = new THREE.WebGLRenderer({
canvas: {
width,
height,
style: {},
addEventListener: () => { },
removeEventListener: () => { },
clientHeight: height
},
context: gl
});
renderer.setSize(width, height);
renderer.setClearColor(0xffffff, 1);
let camera, scene;
let cube;
function init() {
camera = new THREE.PerspectiveCamera(75, width / height, 1, 1100);
camera.position.y = 150;
camera.position.z = 500;
scene = new THREE.Scene();
var mtlLoader = new THREE.MTLLoader();
mtlLoader.load('female-croupier-2013-03-26.mtl', function (materials) {
materials.preload();
var objLoader = new THREE.OBJLoader();
objLoader.setMaterials(materials);
objLoader.load('female-croupier-2013-03-26.obj', function (object) {
scene.add(object);
}, onLoading, onErrorLoading);
}, onLoading, onErrorLoading);
}
const onLoading = (xhr) => {
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
};
const onErrorLoading = (error) => {
console.log('An error happened', error);
};
const animate = () => {
this.requestId = requestAnimationFrame(animate);
renderer.render(scene, camera);
// cube.rotation.y += 0.05;
gl.flush();
rngl.endFrame();
};
init();
animate();
};
render() {
return (
<View style={styles.container}>
<WebGLView
style={styles.webglView}
onContextCreate={this.onContextCreate}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center"
},
webglView: {
width: 300,
height: 300
}
});
This error is as others have said caused by threejs trying to use features from a browser which react-native does not have.
I've gotten so far as to be able to load the textures (which is the stage you're getting the error from) by monkey patching the texture loader to use the loader in react-native-webgl. Add this in your init function (right near the top preferably).
//make sure you have defined renderer and rngl
/*
const renderer = new THREE.WebGLRenderer(...)
const rngl = gl.getExtension("RN");
*/
const loadTexture = async function(url, onLoad, onProgress, onError) {
let textureObject = new THREE.Texture();
console.log("loading",url,'with fancy texture loader');
let properties = renderer.properties.get(textureObject);
var texture = await rngl.loadTexture({yflip: false, image: url});
/*
rngl.loadTexture({ image: url })
.then(({ texture }) => {
*/
console.log("Texture [" + url + "] Loaded!")
texture.needsUpdate = true;
properties.__webglTexture = texture;
properties.__webglInit = true;
console.log(texture);
if (onLoad !== undefined) {
//console.warn('loaded tex', texture);
onLoad(textureObject);
}
//});
return textureObject;
}
THREE.TextureLoader.prototype.load = loadTexture;
This solves the problem of loading textures and I can see them load in Charles but they still don't render on a model so I'm stuck past that point. Technically a correct answer but you'll be stuck as soon as you've implemented it. I'm hoping you can comment back and tell me you've gotten further.
I had a similar setup and encountered same issue. My option was to switch to JSONLoader which doesn’t need document to render in react-native. So, I just loaded my model in Blender with a three-js addon, then exported it as json. Just check out this process of adding a three-js adon to Blender
https://www.youtube.com/watch?v=mqjwgTAGQRY
All the best
this might get you closer:
The GLTF format supports embedding texture images (as base64). If your asset pipeline allows it, you could convert to GLTF and then load into three/react-native.
I had to provide some "window" polyfills for "decodeUriComponent" and "atob" because GLTFLoader uses FileLoader to parse the base64:
I've successfully loaded embedded buffers, but you'll need more polyfills to load textures. TextureLoader uses ImageLoader, which uses document.createElementNS
You are using the MTLLoader which uses TextureLoader, and the TextureLoader uses the ImageLoader.
The imageloader uses the document.createElementNS() function.
what i did to solve this was to directly call the THREEjs TextureLoader:
let texture = new THREE.Texture(
url //URL = a base 64 JPEG string in this case
);
(for the use of Texture check the Texture documentation)
Then i used the Image class from React native (instead of the THREEjs Image, which requires the DOM to be constructed) to give that to the Texture as a property:
import { Image } from 'react-native';
var img = new Image(128, 128);
img.src = url;
texture.normal = img;
And then finally map the texture over the target material:
const mat = new THREE.MeshPhongMaterial();
mat.map = texture;
In the react native documentation it will explain how the react native Image element can be used, it supports base64 encoded JPEG.
Maybe there's a way for you to single out the part where it calls for the TextureLoader and replace that part with this answer. Let me know how it works out.
side note, i havent tried to display this yet in my webGLView, but in the logs it looked like normal threejs objects, it's worth the try
Use TextureLoader from expo-three
import { TextureLoader } from "expo-three";
export function loadTexture(resource) {
if (textureCache[resource]) {
return textureCache[resource].clone();
}
const texture = new TextureLoader().load(resource);
texture.magFilter = NearestFilter;
texture.minFilter = NearestFilter;
textureCache[resource] = texture;
return texture;
}
Source: https://github.com/EvanBacon/Expo-Crossy-Road/blob/master/src/Node/Generic.js

Alloy and require external JS

Right now I have a function in my alloy.js file that is a global.
Alloy.Globals.indicator = function(parent)
{
var view = Ti.UI.createView({
width: '100%',
height: '100%',
backgroundColor: '#000',
opacity: 0.6,
visible: false
});
function osIndicatorStyle()
{
style = Ti.UI.iPhone.ActivityIndicatorStyle.PLAIN;
if ('iPhone OS' !== Ti.Platform.name) style = Ti.UI.ActivityIndicatorStyle.DARK;
return style;
};
var activityIndicator = Ti.UI.createActivityIndicator({
style: osIndicatorStyle(),
height: Ti.UI.FILL,
width: 100
});
view.add(activityIndicator);
parent.add(view);
function openIndicator()
{
view.visible = true;
activityIndicator.show();
}
view.openIndicator = openIndicator;
function closeIndicator()
{
activityIndicator.hide();
view.visible = false;
}
view.closeIndicator = closeIndicator;
return view;
};
I'd rather not have this large function as a global and instead import it to the files I need using require.
I have searched and cannot figure out first, where to place this file and second how to actually "require" it.
All this simply does is create a view that acts as a modal view with a activity indicator. The function also includes two function to show and hide it.
Make a folder called "lib" inside the "app" folder.
Inside this folder, make a file called whatever you like e.g. functions.js:
var functionName = function(){
//your function code here
}
exports.functionName = functionName;
In your controller:
var functions = require('functions');
functions.functionName();
You might want to also look at Widgets which are re-usable components complete with view/controller/styles as I think this would fit your requirement slightly better.
Link to docs