Skip to content

Commit 0da1d71

Browse files
committed
Init lib
1 parent 0706bbe commit 0da1d71

16 files changed

+4343
-0
lines changed

.gitignore

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# OSX
2+
#
3+
.DS_Store
4+
5+
# Xcode
6+
#
7+
build/
8+
*.pbxuser
9+
!default.pbxuser
10+
*.mode1v3
11+
!default.mode1v3
12+
*.mode2v3
13+
!default.mode2v3
14+
*.perspectivev3
15+
!default.perspectivev3
16+
xcuserdata
17+
*.xccheckout
18+
*.moved-aside
19+
DerivedData
20+
*.hmap
21+
*.ipa
22+
*.xcuserstate
23+
ios/.xcode.env.local
24+
25+
# Android/IntelliJ
26+
#
27+
build/
28+
.idea
29+
.gradle
30+
local.properties
31+
*.iml
32+
*.hprof
33+
.cxx/
34+
*.keystore
35+
!debug.keystore
36+
37+
# node.js
38+
#
39+
node_modules/
40+
npm-debug.log
41+
yarn-error.log
42+
43+
# fastlane
44+
#
45+
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
46+
# screenshots whenever they are needed.
47+
# For more information about the recommended setup visit:
48+
# https://docs.fastlane.tools/best-practices/source-control/
49+
50+
**/fastlane/report.xml
51+
**/fastlane/Preview.html
52+
**/fastlane/screenshots
53+
**/fastlane/test_output
54+
55+
# Bundle artifact
56+
*.jsbundle
57+
58+
# Ruby / CocoaPods
59+
/ios/Pods/
60+
/vendor/bundle/
61+
62+
# Temporary files created by Metro to check the health of the file watcher
63+
.metro-health-check*
64+
65+
# testing
66+
/coverage
67+
/dist

README.md

+52
Original file line numberDiff line numberDiff line change
@@ -1 +1,53 @@
11
# react-native-widget-picker
2+
3+
[![react-native-widget-picker on npm](https://badgen.net/npm/v/react-native-widget-picker)](https://www.npmjs.com/package/react-native-widget-picker)
4+
[![react-native-widget-picker downloads](https://badgen.net/npm/dm/react-native-widget-picker)](https://www.npmtrends.com/react-native-widget-picker)
5+
[![react-native-widget-picker install size](https://packagephobia.com/badge?p=react-native-widget-picker)](https://packagephobia.com/result?p=react-native-widget-picker)
6+
[![CI status](https://github.com/retyui/react-native-widget-picker/actions/workflows/android_ios.yaml/badge.svg)](https://github.com/retyui/react-native-widget-picker/actions/workflows/android_ios.yaml)
7+
8+
Let users pin a widget. On devices running Android 8.0 (API level 26) and higher, launchers that
9+
let [pin widgets](https://developer.android.com/develop/ui/views/appwidgets/configuration) onto their home screen
10+
11+
https://github.com/retyui/react-quick-pinch-zoom/assets/4661784/e7aff504-4299-4034-9b9f-8f7f6c854578
12+
13+
## Getting started
14+
15+
* Android only
16+
* support React Native's New & Old Architecture
17+
18+
```shell
19+
yarn add react-native-widget-picker
20+
# or
21+
npm install react-native-widget-picker
22+
```
23+
24+
Edit `android/app/src/main/java/com/.../MainActivity.java` and add:
25+
26+
```diff
27+
public class MainActivity extends ReactActivity {
28+
29+
@Override
30+
public void onCreate(Bundle savedInstanceState) {
31+
super.onCreate(savedInstanceState);
32+
+ WidgetPickerModuleImpl.registerWidgetClass("MyAppWidget", MyAppWidget.class);
33+
// you can register multiple widgets ^^^
34+
}
35+
```
36+
37+
## Usage
38+
39+
```tsx
40+
import {WidgetPicker} from 'react-native-widget-picker';
41+
42+
WidgetPicker.isRequestPinAppWidgetSupported() // true or false
43+
44+
WidgetPicker.requestPinAppWidget("MyAppWidget").then((value) => {
45+
if (value.message === "success") {
46+
// success
47+
}
48+
});
49+
```
50+
51+
## License
52+
53+
MIT

android/build.gradle

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
buildscript {
2+
ext.safeExtGet = {prop, fallback ->
3+
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
4+
}
5+
6+
def kotlin_version = safeExtGet('kotlinVersion', '1.7.0')
7+
8+
repositories {
9+
google()
10+
gradlePluginPortal()
11+
}
12+
dependencies {
13+
classpath("com.android.tools.build:gradle:7.2.1")
14+
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
15+
}
16+
}
17+
18+
def isNewArchitectureEnabled() {
19+
return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
20+
}
21+
22+
apply plugin: 'com.android.library'
23+
apply plugin: 'kotlin-android'
24+
25+
if (isNewArchitectureEnabled()) {
26+
apply plugin: "com.facebook.react"
27+
}
28+
29+
def supportsNamespace() {
30+
def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
31+
def major = parsed[0].toInteger()
32+
def minor = parsed[1].toInteger()
33+
34+
// Namespace support was added in 7.3.0
35+
if (major == 7 && minor >= 3) {
36+
return true
37+
}
38+
39+
return major >= 8
40+
}
41+
42+
android {
43+
compileSdkVersion safeExtGet('compileSdkVersion', 33)
44+
45+
if (supportsNamespace()) {
46+
namespace "com.retyui.widgetpicker"
47+
}
48+
49+
defaultConfig {
50+
minSdkVersion safeExtGet('minSdkVersion', 21)
51+
targetSdkVersion safeExtGet('targetSdkVersion', 33)
52+
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
53+
}
54+
55+
lintOptions {
56+
disable "GradleCompatible"
57+
}
58+
59+
buildTypes {
60+
release {
61+
minifyEnabled false
62+
}
63+
}
64+
65+
compileOptions {
66+
sourceCompatibility JavaVersion.VERSION_1_8
67+
targetCompatibility JavaVersion.VERSION_1_8
68+
}
69+
70+
sourceSets {
71+
main {
72+
if (isNewArchitectureEnabled()) {
73+
java.srcDirs += ['src/newarch/java', "${project.buildDir}/generated/source/codegen/java"]
74+
} else {
75+
java.srcDirs += ['src/oldarch/java']
76+
}
77+
if (!supportsNamespace()) {
78+
manifest.srcFile "src/main/AndroidManifest.xml"
79+
}
80+
}
81+
}
82+
}
83+
84+
repositories {
85+
maven {
86+
url "$projectDir/../node_modules/react-native/android"
87+
}
88+
mavenCentral()
89+
google()
90+
}
91+
92+
apply from: "$projectDir/react-native-helpers.gradle"
93+
94+
def kotlin_ver = safeExtGet('kotlinVersion', '1.7.0')
95+
96+
dependencies {
97+
if (project.ext.shouldConsumeReactNativeFromMavenCentral()) {
98+
implementation "com.facebook.react:react-android" // Set by the React Native Gradle Plugin
99+
} else {
100+
implementation 'com.facebook.react:react-native:+' // From node_modules
101+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_ver"
102+
}
103+
}
104+
105+
if (isNewArchitectureEnabled()) {
106+
react {
107+
jsRootDir = file("../src/")
108+
libraryName = "WidgetPicker"
109+
codegenJavaPackageName = "com.retyui.widgetpicker"
110+
}
111+
}

android/react-native-helpers.gradle

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
def safeAppExtGet(prop, fallback) {
2+
def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') }
3+
appProject?.ext?.has(prop) ? appProject.ext.get(prop) : fallback
4+
}
5+
6+
// Let's detect react-native's directory, it will be used to determine RN's version
7+
// https://github.com/software-mansion/react-native-reanimated/blob/cda4627c3337c33674f05f755b7485165c6caca9/android/build.gradle#L88
8+
def resolveReactNativeDirectory() {
9+
def reactNativeLocation = safeAppExtGet("REACT_NATIVE_NODE_MODULES_DIR", null)
10+
if (reactNativeLocation != null) {
11+
return file(reactNativeLocation)
12+
}
13+
14+
// monorepo workaround
15+
// react-native can be hoisted or in project's own node_modules
16+
def reactNativeFromProjectNodeModules = file("${rootProject.projectDir}/../node_modules/react-native")
17+
if (reactNativeFromProjectNodeModules.exists()) {
18+
return reactNativeFromProjectNodeModules
19+
}
20+
21+
def reactNativeFromNodeModules = file("${projectDir}/../../react-native")
22+
if (reactNativeFromNodeModules.exists()) {
23+
return reactNativeFromNodeModules
24+
}
25+
26+
throw new GradleException(
27+
"[react-native-widget-picker] Unable to resolve react-native location in " +
28+
"node_modules. You should project extension property (in app/build.gradle) " +
29+
"`REACT_NATIVE_NODE_MODULES_DIR` with path to react-native."
30+
)
31+
}
32+
33+
// https://github.com/software-mansion/react-native-reanimated/blob/cda4627c3337c33674f05f755b7485165c6caca9/android/build.gradle#L199#L205
34+
def reactNativeRootDir = resolveReactNativeDirectory()
35+
36+
def reactProperties = new Properties()
37+
file("$reactNativeRootDir/ReactAndroid/gradle.properties").withInputStream { reactProperties.load(it) }
38+
39+
def REACT_NATIVE_VERSION = reactProperties.getProperty("VERSION_NAME")
40+
def REACT_NATIVE_MINOR_VERSION = REACT_NATIVE_VERSION.startsWith("0.0.0-") ? 1000 : REACT_NATIVE_VERSION.split("\\.")[1].toInteger()
41+
42+
project.ext.shouldConsumeReactNativeFromMavenCentral = { ->
43+
return REACT_NATIVE_MINOR_VERSION >= 71
44+
}

android/src/main/AndroidManifest.xml

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2+
package="com.retyui.widgetpicker">
3+
</manifest>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.retyui.widgetpicker
2+
3+
import android.appwidget.AppWidgetManager
4+
import android.content.ComponentName
5+
import android.os.Build
6+
import androidx.annotation.RequiresApi
7+
import com.facebook.react.bridge.Promise
8+
import com.facebook.react.bridge.ReactApplicationContext
9+
10+
import com.facebook.react.bridge.Arguments
11+
12+
class WidgetPickerModuleImpl(private val reactContext: ReactApplicationContext) {
13+
fun isRequestPinAppWidgetSupported(): Boolean {
14+
try {
15+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
16+
val appWidgetManager: AppWidgetManager = reactContext.getSystemService(AppWidgetManager::class.java)
17+
return appWidgetManager.isRequestPinAppWidgetSupported
18+
}
19+
return false
20+
} catch (ignored: Exception) {
21+
return false
22+
}
23+
}
24+
25+
@RequiresApi(Build.VERSION_CODES.O)
26+
fun requestPinAppWidget(widgetClassKey: String, promise: Promise) {
27+
try {
28+
val appWidgetManager: AppWidgetManager = reactContext.getSystemService(AppWidgetManager::class.java)
29+
val myProvider = ComponentName(reactContext, widgets[widgetClassKey]!!)
30+
val result = appWidgetManager.requestPinAppWidget(myProvider, null, null)
31+
if(result){
32+
promise.resolve(Arguments.createMap().apply {
33+
putString("message", "success")
34+
})
35+
}else{
36+
promise.resolve(Arguments.createMap().apply {
37+
// launcher doesn't support this feature
38+
// or widget not found
39+
putString("message", "cannot pin widget")
40+
})
41+
}
42+
43+
} catch (e: Exception) {
44+
promise.reject(e)
45+
}
46+
}
47+
48+
companion object {
49+
const val NAME = "WidgetPickerModule"
50+
51+
private var widgets = mutableMapOf<String, Class<*>>()
52+
53+
@JvmStatic
54+
fun registerWidgetClass(key: String, classValue: Class<*>) {
55+
widgets.remove(key);
56+
widgets[key] = classValue;
57+
}
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.retyui.widgetpicker
2+
3+
import com.facebook.react.TurboReactPackage
4+
import com.facebook.react.bridge.NativeModule
5+
import com.facebook.react.bridge.ReactApplicationContext
6+
import com.facebook.react.module.annotations.ReactModule
7+
import com.facebook.react.module.model.ReactModuleInfo
8+
import com.facebook.react.module.model.ReactModuleInfoProvider
9+
import com.facebook.react.turbomodule.core.interfaces.TurboModule
10+
11+
class WidgetPickerTurboPackage : TurboReactPackage() {
12+
/**
13+
* Initialize and export modules based on the name of the required module
14+
*/
15+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
16+
return when (name) {
17+
WidgetPickerModule.NAME -> WidgetPickerModule(reactContext)
18+
else -> null
19+
}
20+
}
21+
22+
/**
23+
* Declare info about exported modules
24+
*/
25+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
26+
/**
27+
* Here declare the array of exported modules
28+
*/
29+
val moduleList: Array<Class<out NativeModule?>> = arrayOf(
30+
WidgetPickerModule::class.java
31+
)
32+
val reactModuleInfoMap: MutableMap<String, ReactModuleInfo> = HashMap()
33+
/**
34+
* And here just iterate on that array and produce the info provider instance
35+
*/
36+
for (moduleClass in moduleList) {
37+
val reactModule = moduleClass.getAnnotation(ReactModule::class.java) ?: continue
38+
reactModuleInfoMap[reactModule.name] =
39+
ReactModuleInfo(
40+
reactModule.name,
41+
moduleClass.name,
42+
true,
43+
reactModule.needsEagerInit,
44+
reactModule.hasConstants,
45+
reactModule.isCxxModule,
46+
TurboModule::class.java.isAssignableFrom(moduleClass)
47+
)
48+
}
49+
return ReactModuleInfoProvider { reactModuleInfoMap }
50+
}
51+
}

0 commit comments

Comments
 (0)