Skip to content

Commit

Permalink
Init lib
Browse files Browse the repository at this point in the history
  • Loading branch information
retyui committed Jul 31, 2023
1 parent 0706bbe commit 0da1d71
Show file tree
Hide file tree
Showing 16 changed files with 4,343 additions and 0 deletions.
67 changes: 67 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# OSX
#
.DS_Store

# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
ios/.xcode.env.local

# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
*.hprof
.cxx/
*.keystore
!debug.keystore

# node.js
#
node_modules/
npm-debug.log
yarn-error.log

# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/

**/fastlane/report.xml
**/fastlane/Preview.html
**/fastlane/screenshots
**/fastlane/test_output

# Bundle artifact
*.jsbundle

# Ruby / CocoaPods
/ios/Pods/
/vendor/bundle/

# Temporary files created by Metro to check the health of the file watcher
.metro-health-check*

# testing
/coverage
/dist
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,53 @@
# react-native-widget-picker

[![react-native-widget-picker on npm](https://badgen.net/npm/v/react-native-widget-picker)](https://www.npmjs.com/package/react-native-widget-picker)
[![react-native-widget-picker downloads](https://badgen.net/npm/dm/react-native-widget-picker)](https://www.npmtrends.com/react-native-widget-picker)
[![react-native-widget-picker install size](https://packagephobia.com/badge?p=react-native-widget-picker)](https://packagephobia.com/result?p=react-native-widget-picker)
[![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)

Let users pin a widget. On devices running Android 8.0 (API level 26) and higher, launchers that
let [pin widgets](https://developer.android.com/develop/ui/views/appwidgets/configuration) onto their home screen

https://github.com/retyui/react-quick-pinch-zoom/assets/4661784/e7aff504-4299-4034-9b9f-8f7f6c854578

## Getting started

* Android only
* support React Native's New & Old Architecture

```shell
yarn add react-native-widget-picker
# or
npm install react-native-widget-picker
```

Edit `android/app/src/main/java/com/.../MainActivity.java` and add:

```diff
public class MainActivity extends ReactActivity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ WidgetPickerModuleImpl.registerWidgetClass("MyAppWidget", MyAppWidget.class);
// you can register multiple widgets ^^^
}
```

## Usage

```tsx
import {WidgetPicker} from 'react-native-widget-picker';

WidgetPicker.isRequestPinAppWidgetSupported() // true or false

WidgetPicker.requestPinAppWidget("MyAppWidget").then((value) => {
if (value.message === "success") {
// success
}
});
```

## License

MIT
111 changes: 111 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
buildscript {
ext.safeExtGet = {prop, fallback ->
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}

def kotlin_version = safeExtGet('kotlinVersion', '1.7.0')

repositories {
google()
gradlePluginPortal()
}
dependencies {
classpath("com.android.tools.build:gradle:7.2.1")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
}
}

def isNewArchitectureEnabled() {
return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
}

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

if (isNewArchitectureEnabled()) {
apply plugin: "com.facebook.react"
}

def supportsNamespace() {
def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
def major = parsed[0].toInteger()
def minor = parsed[1].toInteger()

// Namespace support was added in 7.3.0
if (major == 7 && minor >= 3) {
return true
}

return major >= 8
}

android {
compileSdkVersion safeExtGet('compileSdkVersion', 33)

if (supportsNamespace()) {
namespace "com.retyui.widgetpicker"
}

defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', 21)
targetSdkVersion safeExtGet('targetSdkVersion', 33)
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
}

lintOptions {
disable "GradleCompatible"
}

buildTypes {
release {
minifyEnabled false
}
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

sourceSets {
main {
if (isNewArchitectureEnabled()) {
java.srcDirs += ['src/newarch/java', "${project.buildDir}/generated/source/codegen/java"]
} else {
java.srcDirs += ['src/oldarch/java']
}
if (!supportsNamespace()) {
manifest.srcFile "src/main/AndroidManifest.xml"
}
}
}
}

repositories {
maven {
url "$projectDir/../node_modules/react-native/android"
}
mavenCentral()
google()
}

apply from: "$projectDir/react-native-helpers.gradle"

def kotlin_ver = safeExtGet('kotlinVersion', '1.7.0')

dependencies {
if (project.ext.shouldConsumeReactNativeFromMavenCentral()) {
implementation "com.facebook.react:react-android" // Set by the React Native Gradle Plugin
} else {
implementation 'com.facebook.react:react-native:+' // From node_modules
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_ver"
}
}

if (isNewArchitectureEnabled()) {
react {
jsRootDir = file("../src/")
libraryName = "WidgetPicker"
codegenJavaPackageName = "com.retyui.widgetpicker"
}
}
44 changes: 44 additions & 0 deletions android/react-native-helpers.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
def safeAppExtGet(prop, fallback) {
def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') }
appProject?.ext?.has(prop) ? appProject.ext.get(prop) : fallback
}

// Let's detect react-native's directory, it will be used to determine RN's version
// https://github.com/software-mansion/react-native-reanimated/blob/cda4627c3337c33674f05f755b7485165c6caca9/android/build.gradle#L88
def resolveReactNativeDirectory() {
def reactNativeLocation = safeAppExtGet("REACT_NATIVE_NODE_MODULES_DIR", null)
if (reactNativeLocation != null) {
return file(reactNativeLocation)
}

// monorepo workaround
// react-native can be hoisted or in project's own node_modules
def reactNativeFromProjectNodeModules = file("${rootProject.projectDir}/../node_modules/react-native")
if (reactNativeFromProjectNodeModules.exists()) {
return reactNativeFromProjectNodeModules
}

def reactNativeFromNodeModules = file("${projectDir}/../../react-native")
if (reactNativeFromNodeModules.exists()) {
return reactNativeFromNodeModules
}

throw new GradleException(
"[react-native-widget-picker] Unable to resolve react-native location in " +
"node_modules. You should project extension property (in app/build.gradle) " +
"`REACT_NATIVE_NODE_MODULES_DIR` with path to react-native."
)
}

// https://github.com/software-mansion/react-native-reanimated/blob/cda4627c3337c33674f05f755b7485165c6caca9/android/build.gradle#L199#L205
def reactNativeRootDir = resolveReactNativeDirectory()

def reactProperties = new Properties()
file("$reactNativeRootDir/ReactAndroid/gradle.properties").withInputStream { reactProperties.load(it) }

def REACT_NATIVE_VERSION = reactProperties.getProperty("VERSION_NAME")
def REACT_NATIVE_MINOR_VERSION = REACT_NATIVE_VERSION.startsWith("0.0.0-") ? 1000 : REACT_NATIVE_VERSION.split("\\.")[1].toInteger()

project.ext.shouldConsumeReactNativeFromMavenCentral = { ->
return REACT_NATIVE_MINOR_VERSION >= 71
}
3 changes: 3 additions & 0 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.retyui.widgetpicker">
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.retyui.widgetpicker

import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.os.Build
import androidx.annotation.RequiresApi
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext

import com.facebook.react.bridge.Arguments

class WidgetPickerModuleImpl(private val reactContext: ReactApplicationContext) {
fun isRequestPinAppWidgetSupported(): Boolean {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val appWidgetManager: AppWidgetManager = reactContext.getSystemService(AppWidgetManager::class.java)
return appWidgetManager.isRequestPinAppWidgetSupported
}
return false
} catch (ignored: Exception) {
return false
}
}

@RequiresApi(Build.VERSION_CODES.O)
fun requestPinAppWidget(widgetClassKey: String, promise: Promise) {
try {
val appWidgetManager: AppWidgetManager = reactContext.getSystemService(AppWidgetManager::class.java)
val myProvider = ComponentName(reactContext, widgets[widgetClassKey]!!)
val result = appWidgetManager.requestPinAppWidget(myProvider, null, null)
if(result){
promise.resolve(Arguments.createMap().apply {
putString("message", "success")
})
}else{
promise.resolve(Arguments.createMap().apply {
// launcher doesn't support this feature
// or widget not found
putString("message", "cannot pin widget")
})
}

} catch (e: Exception) {
promise.reject(e)
}
}

companion object {
const val NAME = "WidgetPickerModule"

private var widgets = mutableMapOf<String, Class<*>>()

@JvmStatic
fun registerWidgetClass(key: String, classValue: Class<*>) {
widgets.remove(key);
widgets[key] = classValue;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.retyui.widgetpicker

import com.facebook.react.TurboReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.react.module.model.ReactModuleInfoProvider
import com.facebook.react.turbomodule.core.interfaces.TurboModule

class WidgetPickerTurboPackage : TurboReactPackage() {
/**
* Initialize and export modules based on the name of the required module
*/
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
return when (name) {
WidgetPickerModule.NAME -> WidgetPickerModule(reactContext)
else -> null
}
}

/**
* Declare info about exported modules
*/
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
/**
* Here declare the array of exported modules
*/
val moduleList: Array<Class<out NativeModule?>> = arrayOf(
WidgetPickerModule::class.java
)
val reactModuleInfoMap: MutableMap<String, ReactModuleInfo> = HashMap()
/**
* And here just iterate on that array and produce the info provider instance
*/
for (moduleClass in moduleList) {
val reactModule = moduleClass.getAnnotation(ReactModule::class.java) ?: continue
reactModuleInfoMap[reactModule.name] =
ReactModuleInfo(
reactModule.name,
moduleClass.name,
true,
reactModule.needsEagerInit,
reactModule.hasConstants,
reactModule.isCxxModule,
TurboModule::class.java.isAssignableFrom(moduleClass)
)
}
return ReactModuleInfoProvider { reactModuleInfoMap }
}
}
Loading

0 comments on commit 0da1d71

Please sign in to comment.