How to make notification lights work in react-native on android? - android

I have a simple react-native camera app and I would like to make notification LED light up at the bottom of the phone, when app is recording. I was not able to find it on the official docs.
I removed unnecessary code (like the styles and template) for ease of readability. My index.android.js is the following.
import React from 'react';
import {
View,
Image,
StatusBar,
StyleSheet,
AppRegistry,
TouchableOpacity,
} from 'react-native';
import Camera from 'react-native-camera';
const styles = StyleSheet.create({
//...
});
export default class DashCam extends React.Component {
constructor(props) {
super(props);
this.camera = null;
this.state = {
camera: {
aspect: Camera.constants.Aspect.fill,
captureTarget: Camera.constants.CaptureTarget.cameraRoll,
type: Camera.constants.Type.back,
orientation: Camera.constants.Orientation.auto,
},
isRecording: false
};
this.switchCam = this.switchCam.bind(this);
this.recording = this.recording.bind(this);
}
recording() {
console.log(!this.state.isRecording);
if(!this.state.isRecording) {
if (this.camera) {
this.camera.capture({mode: Camera.constants.CaptureMode.video})
.then((data) => console.log(data))
.catch(err => console.error(err));
this.setState({ isRecording: true });
}
console.log('recording');
} else {
if (this.camera) {
this.camera.stopCapture();
this.setState({ isRecording: false });
}
console.log('stopped ');
}
}
switchCam() {
//...
}
get typeIcon() {
//...
}
get camButton() {
//...
}
render() {
return (
//...
);
}
}
AppRegistry.registerComponent('DashCam', () => DashCam);
My package.json if you need it:
{
"name": "DashCam",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",
"test": "jest"
},
"dependencies": {
"react": "15.3.2",
"react-native": "0.37.0",
"react-native-camera": "git+https://github.com/lwansbrough/react-native-camera.git"
},
"jest": {
"preset": "jest-react-native"
},
"devDependencies": {
"babel-jest": "17.0.2",
"babel-preset-react-native": "1.9.0",
"jest": "17.0.3",
"jest-react-native": "17.0.3",
"react-test-renderer": "15.3.2"
}
}

As you point out, this functionality is not included in RN, but the good thing is you can easily implement it yourself in Android code. Maybe something like this can help you turn the LED on/off (by basically creating a dummy notification) and then you can build an Android module, which is actually fairly simple. You can have a look at the Toast tutorial in the official docs.

Related

Switch from react-native 0.59.9 to 0.66.0 and native-base in a new instance

Since I had a very dated app, react-native 0.59.9 + native-base 2.10.0, or decided to update it starting from a clean project using the classic:
npx react-native init AwesomeProject
at this point I also installed the new native-base 3.2.1 and I overturned all the files and updated their dependencies, many things were different, especially those related to react-navigation.
Once the dependencies have been resolved, the compilation takes place correctly, as seen from the metro bundler (react-native start) but the application immediately crashes reporting:
Require cycles are allowed, but can result in uninitialized values. Consider refactoring to remove the need for a cycle.
ERROR TypeError: undefined is not an object (evaluating 'Comp.displayName')
ERROR Invariant Violation: Module AppRegistry is not a registered callable module (calling runApplication). A frequent cause of the error is that the application entry file path is incorrect.
This can also happen when the JS bundle is corrupt or there is an early initialization error when loading React Native.
ERROR Invariant Violation: Module AppRegistry is not a registered callable module (calling runApplication). A frequent cause of the error is that the application entry file path is incorrect.
This can also happen when the JS bundle is corrupt or there is an early initialization error when loading React Native.
I tried to clear the cache, delete node_modules and recreate it but the error always appears.
From what I understand there is some problem with the appRegistry in the early stage, these are my files where I assume something abnormal is happening.
app.json
{
"name": "MyApp",
"displayName": "MyApp"
}
index.js
import {AppRegistry, Text, TextInput } from 'react-native';
import App from './App';
import {name as appName} from './app.json';
//Disable the FontScaling set by the device (Component "Text").
Text.defaultProps = Text.defaultProps || {};
Text.defaultProps.allowFontScaling = false;
//Disable the FontScaling set by the device (Component "TextInput").
TextInput.defaultProps = Text.defaultProps || {};
TextInput.defaultProps.allowFontScaling = false;
AppRegistry.registerComponent(appName, () => App);
App.js
import React from 'react';
import Setup from './src/boot/setup.js';
export default class App extends React.Component {
render() {
return (
<Setup/>
);
}
}
./src/boot/setup.js (here I inserted the NativeBaseProvider component which was not used before.)
import React from 'react';
import { NativeBaseProvider } from 'native-base';
import App from '../App.js';
export default class Setup extends React.Component {
render() {
return (
<NativeBaseProvider>
<App />
</NativeBaseProvider>
);
}
}
./src/App.js
import React from 'react';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import { createDrawerNavigator } from '#react-navigation/drawer';
import { createSwitchNavigator } from "#react-navigation/compat";
import Home from './components/Home/homeScreen';
import Login from './components/Login/loginScreen';
import ResetPassword from './components/Login/resetPassword';
import News from './components/News/newsScreen';
import Chat from './components/Chat/chatScreen';
import ProfileIndex from './components/Profile/profileIndex';
import SideBar from './components/sidebar/sidebar';
import NewsDetails from './components/News/newsScreenDetails';
import Privacy from './components/Privacy/privacyScreen';
import SignUpWorker from './components/SignUpWorker/signUpWorker';
import AuthLoadingScreen from './utils/authLoadingScreen';
const SignedInDrawer = createDrawerNavigator(
{
Home: { screen: Home },
ProfileIndex: { screen: ProfileIndex },
Chat: { screen: Chat },
News: { screen: News },
NewsDetails: { screen: NewsDetails },
Privacy: { screen: Privacy },
},
{
initialRouteName: 'Home',
contentComponent: props => <SideBar {...props} />,
}
);
const SignedOutDrawer = createDrawerNavigator(
{
Home: { screen: Home },
Login: { screen: Login },
ResetPassword: { screen: ResetPassword },
SignUp: { screen: SignUp },
News: { screen: News },
NewsDetails: { screen: NewsDetails },
Privacy: { screen: Privacy },
},
{
initialRouteName: 'Home',
contentComponent: props => <SideBar {...props} />,
}
);
const SignedInStack = createNativeStackNavigator(
{
DrawerIn: { screen: SignedInDrawer },
},
{
initialRouteName: 'DrawerIn',
headerMode: 'none',
}
);
const SignedOutStack = createNativeStackNavigator(
{
DrawerOut: { screen: SignedOutDrawer },
},
{
initialRouteName: 'DrawerOut',
headerMode: 'none',
}
);
export default createSwitchNavigator(
{
AuthLoading: AuthLoadingScreen,
SignedIn: SignedInStack,
SignedOut: SignedOutStack,
},
{
initialRouteName:'AuthLoading'
}
);
./src/utils/authLoadingScreen.js
import { View, ActivityIndicator, StyleSheet, StatusBar, NetInfo } from 'react-native';
import AsyncStorage from '#react-native-async-storage/async-storage';
import React from 'react';
export let globalUserToken;
export let globalNetworkOnline;
// this function is used to set the globalUserToken from other pages
export function setglobalUserToken(newValue: boolean) {
globalUserToken = newValue;
}
export async function checkNetwork() {
await NetInfo.getConnectionInfo().then(connectionInfo => {
if (connectionInfo.type === 'none') {
globalNetworkOnline = false;
} else {
globalNetworkOnline = true;
}
});
}
export default class AuthLoadingScreen extends React.Component {
constructor() {
super();
this._bootstrapAsync();
}
// Fetch the token from storage then navigate to our appropriate place
_bootstrapAsync = async () => {
globalUserToken = await AsyncStorage.getItem('id_token');
// This will switch to the App screen or Auth screen and this loading
// screen will be unmounted and thrown away.
this.props.navigation.navigate(globalUserToken ? 'SignedIn' : 'SignedOut');
};
render() {
return (
<View style={styles.container}>
<ActivityIndicator />
<StatusBar barStyle="default" />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
The problem most likely starts from src/App.js as i have updated the functions for createNativeStackNavigator, createDrawerNavigator, createSwitchNavigator which were previously simply StackNavigator, DrawerNavigator, SwitchNavigator from 'react-navigation'.
Or the error resides in ./src/utils/authLoadingScreen.js where it is checked if a login token exists... maybe something has changed here too?
Finally I leave you my package.json:
{
"name": "MyApp",
"version": "0.0.1",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"start": "react-native start",
"test": "jest",
"lint": "eslint .",
"build:ios": "react-native bundle --entry-file index.js --platform ios --dev false --bundle-output ios/main.jsbundle --assets-dest ios",
"build:android": "react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res"
},
"dependencies": {
"#react-native-async-storage/async-storage": "^1.15.9",
"#react-navigation/compat": "^5.3.20",
"#react-navigation/drawer": "^6.1.8",
"#react-navigation/native": "^6.0.6",
"#react-navigation/native-stack": "^6.2.4",
"#types/react-native-dotenv": "^0.2.0",
"native-base": "^3.2.1",
"react": "17.0.2",
"react-native": "0.66.0",
"react-native-dotenv": "^3.2.0",
"react-native-gesture-handler": "^1.10.3",
"react-native-htmlview": "^0.16.0",
"react-native-reanimated": "2.3.0-beta.2",
"react-native-safe-area-context": "^3.3.2",
"react-native-screens": "^3.8.0",
"react-native-size-matters": "^0.4.0",
"react-native-svg": "^12.1.1",
"rn-fetch-blob": "^0.12.0",
"styled-components": "^5.3.1",
"styled-system": "^5.1.5",
"tcomb-form-native": "^0.6.20"
},
"devDependencies": {
"#babel/core": "^7.12.9",
"#babel/runtime": "^7.15.4",
"#react-native-community/eslint-config": "^2.0.0",
"babel-jest": "^27.2.5",
"eslint": "^8.0.1",
"jest": "^27.2.5",
"metro-react-native-babel-preset": "^0.66.2",
"react-test-renderer": "17.0.2"
},
"jest": {
"preset": "react-native"
}
}
Has anyone had this problem or do you know if I have correctly changed the components of react-navigation no longer used in the new version?

Error setting up firebase with react possible unhandled promise rejection

I am currently creating a food ordering system and i am trying to set up firebase on a react-native project
The application lets me use authentication from firebase i can succesfully sign in and also register new users but it does not let me use firestore
i am trying to do a console log in the below code in my to see if the firestore will run with the application
below is a list of the app.js/the screen where i am calling the collection from firestore
the error reads as so
firebase error
//error reads on android emulator
the build.gradle app level and the
//list of packages used on for react
package.json
"name": "projectFinal",
"version": "0.0.1",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"start": "react-native start",
"test": "jest",
"lint": "eslint ."
},
"dependencies": {
"#react-native-community/masked-view": "^0.1.7",
"#react-native-firebase/app": "^6.3.4",
"#react-native-firebase/auth": "^6.3.4",
"#react-native-firebase/firestore": "^6.3.4",
"firebase": "^7.9.3",
"react": "16.9.0",
"react-native": "0.60.5",
"react-native-elements": "^1.2.7",
"react-native-gesture-handler": "^1.6.0",
"react-native-reanimated": "^1.7.0",
"react-native-safe-area-context": "^0.7.3",
"react-native-safe-area-view": "^1.0.0",
"react-native-screens": "^2.2.0",
"react-native-vector-icons": "^6.6.0",
"react-navigation": "^4.2.2",
"react-navigation-header-buttons": "^3.0.5",
"react-navigation-stack": "^2.2.2",
"react-navigation-tabs": "^2.8.2"
},
"devDependencies": {
"#babel/core": "^7.8.6",
"#babel/runtime": "^7.8.4",
"#react-native-community/eslint-config": "^0.0.7",
"babel-jest": "^25.1.0",
"eslint": "^6.8.0",
"jest": "^25.1.0",
"metro-react-native-babel-preset": "^0.58.0",
"react-test-renderer": "16.9.0"
},
"jest": {
"preset": "react-native"
}
}
app.js
import React, { Component } from 'react';
import { View, StyleSheet, Text } from 'react-native';
import {createAppContainer, createSwitchNavigator} from 'react-navigation';
import {createStackNavigator} from 'react-navigation-stack';
import {createBottomTabNavigator, createTabNavigator} from 'react-navigation-tabs'
import Icon from 'react-native-vector-icons/Ionicons'
import { HeaderButtons, HeaderButton, Item } from 'react-navigation-header-buttons';
import Home from './src/Screens/Home'
//import MyAccount from './Screens/MyAccount';
import Register from './src/Screens/Register'
import Login from './src/Screens/Login';
//import Menus from './Screens/Menus';
import LoadingScreen from './src/Screens/LoadingScreen';
// import Jitters from './Screens/Jitters';
// import Scullery from './Screens/Scullery';
// import Bunker from './Screens/TheBunker';
// import OrderDetails from './Screens/OrderDetails';
import ViewOrder from './src/Screens/ViewOrder';
import "firebase/firestore"
import firebase from "firebase"
import firestore from "#react-native-firebase/firestore"
//this is where firebase is initializedd the google.services json is also in the relevant file
const firebaseConfig = {
apiKey: "AIza<<redacted>>",
authDomain: "<<redacted>>.firebaseapp.com",
databaseURL: "https://<<redacted>>.firebaseio.com",
projectId: "<<redacted>>",
storageBucket: "<<redacted>>.appspot.com",
messagingSenderId: "<<redacted>>",
appId: "1:8<<redacted>>",
measurementId: "G-T<<redacted>>"
};
// Instantiate a Firebase app.
const firebaseApp = firebase.initializeApp(firebaseConfig);
const db = firebaseApp.firestore()
const TabNavigator = createBottomTabNavigator(
{
Home: {
screen: Home,
navigationOptions: {
TabBarIcon: ({ tintColor}) => <Ionicons name="ios-home" size={24} color={tintColor}/>
}
},
// Menus: {
// screen: Menus,
// navigationOptions: {
// TabBarIcon: ({ tintColor}) => <Ionicons name="ios-chatbox" size={24} color={tintColor}/>
// }
ViewOrder: {
screen: ViewOrder,
navigationOptions: {
TabBarIcon: ({ tintColor}) => <Ionicons name="ios-add-circle" size={24} color={tintColor}/>
},
}
},
{
tabBarOptions: {
activeTintColor: "#161F3D",
inactiveTintColor: "#E9446A",
showLabel: true
}
}
);
const authStack = createStackNavigator(
{
Home: Home,
Login: Login,
Register: Register,
// Menu: Menus,
// Jitters: Jitters,
// Scullery: Scullery,
// TheBunker: Bunker,
// MyAccount: MyAccount,
// OrderDetails: OrderDetails,
ViewOrder: ViewOrder
});
const App = createAppContainer(TabNavigator);
export default createAppContainer(
(
createSwitchNavigator({
Loading: LoadingScreen,
App: TabNavigator,
Auth: authStack
},
{
initalRouteName: "Loading Page"
}
)
)
);
##Screen to call a collection of firestore##
//this is a console log to see if firestore will initialize
import React, {useState} from 'react'
import {View, Text} from 'react-native'
import firebase from "firebase"
import firestore from "#react-native-firebase/firestore"
async function myFunction() {
var user = firebase.auth().currentUser;
let x = await firestore().collection("Users").doc("MmjyeGlhFPu0g6pK0GQ2").get();
// console.log(x)
}
export default class ViewOrder extends React.Component {
componentDidMount() {
myFunction(
)
}
render() {
return(
<View>
<Text>Hello</Text>
</View>
)}
**//error reads on android emulator**
as possible unhandled promise rejection (id:0)
error: no firebase app ['default] has been created - call
firebase.initalize.app() getapp#http://10.0.2.2.8081/index.bundle
?platform
It seems by the error, mainly for this below part:
Error: No Firebase App '[DEFAULT]' has been created - call firebase.initializeApp()
That you have not initialized your Firebase database. As informed in the documentation firebase. app. App, you need to call firebase.initializeApp() to create an app. Besides that, I would change your import to import * as firebase from "firebase", so you are importing all packages needed for Firebase.
I have checked all these below posts from the Community and these possible solutions that I provided you, are just some options and alternatives that might solve your case.
I would recommend you take a look at them, so you can get more information and check further since there might be some other structure and environment configurations that can be affecting you and these cases can assist you with it.
No Firebase App '[DEFAULT]' has been created - call Firebase App.initializeApp()
Firebase: No Firebase App '[DEFAULT]' has been created (Firestore)
Error: Firebase: No Firebase App '[DEFAULT]' has been created
Error: No Firebase App '[DEFAULT]' has been created - call Firebase App.initializeApp()
Let me know if the information helped you!

UI doesn't update until tap on the screen when setState is called inside a realm listener callback

UPDATED
DESCRIPTION
I have a listener on a Realm Object for getting updates. When there is an update on the server (or in the client) the function provided to the listener calls setState({}).
The strange part is that even if the console says that everything is ok, and it shows that the render method was called with correct data, I can't see any updates to my app.
If I tap on the screen randomly (after 1s,2s, 20s...) the UI magically updates and everything is correct.
If I do the same setState with a function called from a button it works, I guess because the animation of the button triggers the UI update.
Thanks for reading this.
STEP TO REPRODUCE
You have to update the server_url and credential in order to work.
react-native init test
npm install realm
react-native link realm
Since realm is not ready for 64-bit you must also be sure t compile only in 32bit in order to avoid app crashing when launched
use this code:
App.js
import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native';
import Realm from 'realm'
import { SERVER_URL } from "./config/realm";
import { Utente } from "./config/schema";
export default class App extends Component {
loginAsync = async () => {
var realm_user = Realm.Sync.User.current
if(!realm_user){
const credentials = Realm.Sync.Credentials.usernamePassword('admin', '******' ,false);
realm_user = await Realm.Sync.User.login(SERVER_URL, credentials);
}
const config = realm_user.createConfiguration({
schema: [
Utente,
Realm.Permissions.Permission,
Realm.Permissions.User,
Realm.Permissions.Role],
schemaVersion: 1,
});
this.realm = new Realm(config);
var connectedUserData = this.realm.objects("Utente").filtered("id = $0", realm_user.identity)
connectedUserData.subscribe()
connectedUserData.addListener((connectedUserData)=>{
if(connectedUserData[0]){
this.setState({
connectedUserData: connectedUserData[0]
})
}
})
}
constructor(props){
super(props)
this.loginAsync()
this.state = {
connectedUserData: {
nome: 'not loaded'
}
}
}
render() {
return (
<View style={styles.container}>
<Text>{ this.state.connectedUserData.nome }</Text>
</View>
);
}
}
Schema.js
export const Utente = {
name: "Utente",
primaryKey: "id",
properties: {
id: "string",
nome: 'string?',
permissions: '__Permission[]'
}
}
Package.json
{
"name": "testBaseRealm",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",
"test": "jest"
},
"dependencies": {
"react": "16.6.3",
"react-native": "0.57.7",
"realm": "^2.27.0-rc.3"
},
"devDependencies": {
"#babel/core": "7.4.4",
"#babel/runtime": "7.4.4",
"babel-jest": "24.8.0",
"jest": "24.8.0",
"metro-react-native-babel-preset": "0.54.1",
"react-test-renderer": "16.6.3"
},
"jest": {
"preset": "react-native"
}
}
Some other strange things :
If I remote debug js to react native debugger (on Windows, but I guess is the same) the problem disappears.
The same thing happens on 3 different devices( 2 real, 1 emulator)
In my case, I just stop the debugger (CMD+D), and that weird behavior went away.
UPDATE 2
setState({}) doesn't work when is inside the listener callback. I've
just done a test changing the code in componentDidMount of Home.js,
and in this way, it works.
It doesnt work because you are not binding the method that is calling it. it is beyond component context so setState is not there.
Do
openRealmAndLogin = (realm_user) => {...}
instead of regular function as this one will bind the function to context. e.g. you can also bind it in constructor (but from what i've seen you are already doing something similar for other function - so better to keep it consistent)
I would suggest that you change the key of the element; this will force it to reload whatever happens.
Ex:
{
articoli.map(articolo => {
const isLoved = connectedUserData.loved_articles.filtered("id = $0", articolo.id ).length
const isLiked = connectedUserData.liked_articles.filtered("id = $0", articolo.id ).length
const numCommenti = articolo.commenti.length
return (
<SchedaArticolo
key={ `ALL_${articolo.id}_${isLoved}_${isLiked}_${numCommenti}` }
articolo={articolo}
isLoved={isLoved}
isLiked={isLiked}
numCommenti={numCommenti}
/>
)
})
}

Platform specific testing using Jest on a React Native app

This is the react native component
//Test.js
export default class Test extends Component {
render() {
return(
<View>
</Animated.View> {
Platform.OS === 'android' &&
<TouchableNativeFeedback>
// some android specific code
</TouchableNativeFeedback>
}</Animated.View>
</View>
)
}
}
THis is the test case;
//testcases.js
import { Text, TouchableOpacity, Platform } from 'react-native';
import React from 'react';
import ReactDOM from 'react-dom';
import { shallow } from 'enzyme';
import Test from '../Test'
describe('Testing an App', () => {
let snapshot;
beforeAll(() => {
snapshot = shallow(<Test />);
Platform.OS = 'android';
});
it(' -- must return 1', () => {
expect(snapshot.find('TouchableNativeFeedback').length).toBe(1);
});
});
While the expected return is 1; The received value is 0; I have set up Platform as 'android' and yet no luck.
I am new to Mobile automation testing; Hence I could be missing something obvious. Should I use react-dom to render and test further?
I feel it's something very obvious, but can't get my finger on it.
This is my package.json
{
"name": "exampleApp",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start",
"test": "jest"
},
"dependencies": {
"react": "15.4.1",
"react-native": "0.42.3"
},
"devDependencies": {
"babel-jest": "20.0.3",
"babel-preset-react-native": "2.0.0",
"jest": "20.0.4",
"enzyme" : "2.8.0",
"react-dom": "15.4.1",
"react-addons-test-utils":"15.4.1",
"react-test-renderer": "15.4.1"
},
"jest": {
"preset": "react-native"
}
}
You can not change your platform directly in your test. You need to set the desired platform in your settings. Take a look at this documentation to see how to do it.

How to get livereload to work with Gulp and Ripple Emulator?

I have tried various options and finally ended with the following messed version of gulpfile.js but I just can't get it to work :(.
I am using the ripple-emulator npm package instead of the Chrome plugin. This does start the emulator and everything works except for the livereload ( manual refresh works though ).
var gulp = require('gulp');
var path = require('path');
var o = require('open');
var ripple = require('ripple-emulator');
var connect = require('gulp-connect');
var injectReload = require('gulp-inject-reload');
var webPath = function(p) {
return path.join('./www/', p);
};
gulp.task('connect', function() {
connect.server({
root: 'www',
livereload: true
});
});
gulp.task('html', function() {
gulp.src('./www/*.html')
.pipe(injectReload({
host: 'http://localhost'
}))
.pipe(gulp.dest(webPath('build')))
.pipe(connect.reload());
});
gulp.task('watch', function() {
gulp.watch(['./www/*.html'], ['html']);
});
// The default task
gulp.task('default', ['connect', 'watch'], function() {
var options = {
keepAlive: false,
open: true,
port: 4400
};
// Start the ripple server
ripple.emulate.start(options);
if (options.open) {
o('http://localhost:' + options.port + '?enableripple=true');
}
});
Dependencies that I am using..
// package.json
{
"name": "servicepromobile",
"version": "0.0.0",
"dependencies": {},
"devDependencies": {
"gulp": "latest",
"open": "latest",
"ripple-emulator": "latest",
"gulp-livereload": "^2.1.1",
"connect-livereload": "^0.5.0",
"tiny-lr": "^0.1.4",
"gulp-connect": "^2.0.6",
"gulp-inject-reload": "0.0.2"
},
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"test": "grunt test"
}
}

Categories

Resources