Skip to content

Commit

Permalink
feat(webapis): introduce @react-native-webapis/battery-status (#2590)
Browse files Browse the repository at this point in the history
  • Loading branch information
tido64 committed Oct 10, 2023
1 parent 81c667b commit c549a8c
Show file tree
Hide file tree
Showing 60 changed files with 1,215 additions and 75 deletions.
2 changes: 2 additions & 0 deletions .changeset/smart-vans-compete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
18 changes: 10 additions & 8 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
'chore':
"chore":
- .github/**/*
- .gitignore
- .npmrc
- .yarnrc
- package.json
- scripts/**/*
'feature: align-deps':
"feature: align-deps":
- packages/align-deps/**/*
'feature: cli':
"feature: cli":
- packages/cli/**/*
- packages/config/**/*
'feature: eslint':
"feature: eslint":
- packages/eslint-*/**/*
'feature: jest':
"feature: jest":
- packages/jest-*/**/*
'feature: metro':
"feature: metro":
- packages/babel-plugin-import-path-remapper/**/*
- packages/babel-preset-metro-react-native/**/*
- packages/metro-*/**/*
- packages/typescript-service/**/*
'feature: sdk':
"feature: sdk":
- packages/react-native-*/**/*
'feature: third party notices':
"feature: third party notices":
- packages/third-party-notices/**/*
"feature: webapis":
- "incubator/@react-native-webapis/**/*"
10 changes: 8 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
**/target/CACHEDIR.TAG
**/target/debug/
**/target/release/
**/windows/AppPackages/
**/windows/Generated Files/
**/windows/obj/
**/windows/x64/
*.log
*.tgz
*.xcworkspace/
Expand All @@ -19,17 +23,19 @@
!.yarn/releases/
/incubator/*/dist/
/incubator/*/lib/
/incubator/@react-native-webapis/*/lib/
/packages/*/*.LICENSE.txt
/packages/*/*/rnx-build/
/packages/*/dist/
/packages/*/ios/build
/packages/*/ios/build/
/packages/*/lib/
/packages/*/macos/build
/packages/*/macos/build/
/packages/template/CHANGELOG.md
/scripts/bin/
/scripts/lib/
Pods/
coverage/
local.properties
msbuild.binlog
node_modules/
!**/__fixtures__/**/node_modules/
97 changes: 97 additions & 0 deletions incubator/@react-native-webapis/battery-status/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# @react-native-webapis/battery-status

[![Build](https://github.com/microsoft/rnx-kit/actions/workflows/build.yml/badge.svg)](https://github.com/microsoft/rnx-kit/actions/workflows/build.yml)
[![npm version](https://img.shields.io/npm/v/@react-native-webapis/battery-status)](https://www.npmjs.com/package/@react-native-webapis/battery-status)

🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧

### THIS TOOL IS EXPERIMENTAL — USE WITH CAUTION

🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧

[Battery Status API](https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API)
for React Native.

> **Note**
>
> This is purely a prototype for the
> [React Native WebAPIs RFC](https://github.com/microsoft/rnx-kit/pull/2504). It
> currently does not implement the Battery Status API to spec, e.g. it's missing
> events and its properties are not updated live. Rather, its purpose is to show
> that it's possible to have a native module polyfill a web API, enabling direct
> use of previously web-only code in a React Native app.
## Installation

```sh
yarn add @rnx-kit/polyfills --dev
yarn add @react-native-webapis/battery-status
```

or if you're using npm

```sh
npm add --save-dev @rnx-kit/polyfills
npm add @react-native-webapis/battery-status
```

## Usage

```diff
diff --git a/packages/test-app/babel.config.js b/packages/test-app/babel.config.js
index 69ebd557..a012b7f5 100644
--- a/packages/test-app/babel.config.js
+++ b/packages/test-app/babel.config.js
@@ -13,6 +13,7 @@ module.exports = {
{ runtime: "automatic" },
],
[require("@babel/plugin-transform-react-jsx-source")],
+ [require("@rnx-kit/polyfills")],
],
},
],
diff --git a/packages/test-app/src/App.native.tsx b/packages/test-app/src/App.native.tsx
index 599634a9..a9b493ab 100644
--- a/packages/test-app/src/App.native.tsx
+++ b/packages/test-app/src/App.native.tsx
@@ -1,3 +1,5 @@
+// @react-native-webapis
+
import { acquireTokenWithScopes } from "@rnx-kit/react-native-auth";
// Both `internal` imports are used to verify that `metro-resolver-symlinks`
// resolves them correctly when `experimental_retryResolvingFromDisk` is
@@ -7,7 +9,7 @@ import {
getRemoteDebuggingAvailability,
} from "internal";
import { getHermesVersion } from "internal/hermes";
-import React, { useCallback, useMemo, useState } from "react";
+import React, { useCallback, useEffect, useMemo, useState } from "react";
import type { LayoutChangeEvent } from "react-native";
import {
NativeModules,
@@ -186,6 +188,14 @@ function App({ concurrentRoot }: { concurrentRoot?: boolean }) {
[setFabric]
);

+ const [batteryLevel, setBatteryLevel] = useState(-1);
+ useEffect(() => {
+ // @ts-expect-error FIXME
+ navigator.getBattery().then((status) => {
+ setBatteryLevel(status.level);
+ });
+ }, []);
+
return (
<SafeAreaView style={styles.body}>
<StatusBar barStyle={isDarkMode ? "light-content" : "dark-content"} />
@@ -195,6 +205,9 @@ function App({ concurrentRoot }: { concurrentRoot?: boolean }) {
style={styles.body}
>
<Header />
+ <View style={styles.group}>
+ <Feature value={batteryLevel.toFixed(2)}>Battery Level</Feature>
+ </View>
<View style={styles.group}>
<Button onPress={startAcquireToken}>Acquire Token</Button>
</View>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
require 'json'

package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
version = package['version']
repository = package['repository']

Pod::Spec.new do |s|
s.name = 'RNWBatteryStatus'
s.version = version
s.author = { package['author']['name'] => package['author']['email'] }
s.license = package['license']
s.homepage = package['homepage']
s.source = { :git => repository['url'], :tag => "#{package['name']}@#{version}" }
s.summary = package['description']

s.ios.deployment_target = '13.0'
s.osx.deployment_target = '10.15'

s.dependency 'React-Core'

s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }

# Include both package and repository relative paths to allow the podspec to
# be consumed from both a local path, and as a podspec outside a spec
# repository.
s.ios.source_files = 'ios/*.{h,m}', # :path
"#{repository['directory']}/ios/*.{h,m}" # :podspec
s.ios.public_header_files = 'ios/*.h', # :path
"#{repository['directory']}/ios/*.h" # :podspec
s.osx.source_files = 'macos/*.{h,m}', # :path
"#{repository['directory']}/macos/*.{h,m}" # :podspec
s.osx.public_header_files = 'macos/*.h', # :path
"#{repository['directory']}/macos/*.h" # :podspec
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import java.nio.file.Paths

buildscript {
ext.findFile = { fileName ->
def currentDirPath = rootDir == null ? null : rootDir.toString()

while (currentDirPath != null) {
def currentDir = file(currentDirPath);
def requestedFile = Paths.get(currentDirPath, fileName).toFile()

if (requestedFile.exists()) {
return requestedFile
}

currentDirPath = currentDir.getParent()
}

return null
}

ext.findNodeModulesPath = { packageName ->
return findFile(Paths.get("node_modules", packageName).toString())
}

ext.getExtProp = { prop, defaultValue ->
return rootProject.ext.has(prop) ? rootProject.ext.get(prop) : defaultValue
}

repositories {
google()
mavenCentral()
}

dependencies {
def kotlinVersion = getExtProp("kotlinVersion", "1.7.21")
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
}

plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
}

repositories {
maven {
url("${findNodeModulesPath('react-native')}/android")
}

google()
mavenCentral()
}

android {
compileSdkVersion getExtProp("compileSdkVersion", 33)
defaultConfig {
minSdkVersion getExtProp("minSdkVersion", 23)
targetSdkVersion getExtProp("targetSdkVersion", 29)
}
lintOptions {
abortOnError false
}
}

dependencies {
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# These properties are required to enable AndroidX for the test app.
android.useAndroidX=true
android.enableJetifier=true
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.reactnativewebapis.batterystatus">
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.reactnativewebapis.batterystatus

import android.content.Context
import android.os.BatteryManager
import android.os.Build
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReactModuleWithSpec

class BatteryStatusModule(context: ReactApplicationContext?) :
ReactContextBaseJavaModule(context), ReactModuleWithSpec {

companion object {
const val NAME = "RNWBatteryStatus"
}

override fun getName(): String = NAME

@ReactMethod
fun getStatus(promise: Promise) {
val batteryManager =
reactApplicationContext.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
promise.resolve(
Arguments.createMap().also { map ->
map.putBoolean("charging", batteryManager.isCharging)
map.putInt("chargingTime", batteryManager.getChargingTime())
map.putInt("dischargingTime", -1)
map.putDouble("level", batteryManager.getBatteryLevel())
}
)
}
}

fun BatteryManager.getBatteryLevel(): Double {
return getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY) / 100.0
}

fun BatteryManager.getChargingTime(): Int {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
(computeChargeTimeRemaining() / 1000).toInt()
} else {
-1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.reactnativewebapis.batterystatus

import com.facebook.react.TurboReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.react.module.model.ReactModuleInfoProvider

class BatteryStatusPackage : TurboReactPackage() {
override fun getModule(name: String?, reactContext: ReactApplicationContext?): NativeModule {
return when (name) {
BatteryStatusModule.NAME -> BatteryStatusModule(reactContext)
else -> throw IllegalArgumentException("No module named '$name'")
}
}

override fun getReactModuleInfoProvider(): ReactModuleInfoProvider =
ReactModuleInfoProvider {
val info = ReactModuleInfo(
BatteryStatusModule.NAME,
BatteryStatusModule::class.java.name,
false,
false,
false,
false,
false
)
mapOf(info.name() to info).toMutableMap()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#import <Foundation/Foundation.h>

#import <React/RCTBridgeModule.h>

NS_ASSUME_NONNULL_BEGIN

@interface RNWBatteryStatus : NSObject <RCTBridgeModule>
@end

NS_ASSUME_NONNULL_END
Loading

0 comments on commit c549a8c

Please sign in to comment.