Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added expo support #126

Merged
merged 8 commits into from
Dec 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,33 @@ The following objects are returned on the methods described above:
}
```

## Expo Support

If you're using expo you can add this plugins

```
"plugins": [
...
// This is required, please add `expo-build-properties` to your project
[
"expo-build-properties",
{
"ios": {
"useFrameworks": "static"
}
}
],
[
"@xmartlabs/react-native-line",
{
"channelId": "YOUR_CHANNEL_ID"
}
]
],
```

⚠️ [iOS] if you're using other plugins it might cause conflict on `appDelegate.mm` file, please implement your own appDelegate mod if conflict occur

## Example

If you want to see `@xmartlabs/react-native-line` in action, just move into the [example](/example) folder and run `yarn ios`/`yarn android`. By seeing its source code, you will have a better understanding of the library usage.
Expand Down
1 change: 0 additions & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ repositories {

apply plugin: "com.android.library"
apply plugin: "kotlin-android"
apply plugin: "kotlin-android-extensions"

def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
Expand Down
1 change: 1 addition & 0 deletions app.plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./plugins/withLineSDK');
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@
"test:ts": "tsc --noEmit"
},
"peerDependencies": {
"@expo/config-plugins": "^8.0.10",
"react": "^16.0",
"react-native": ">=0.61.1"
},
"devDependencies": {
"@expo/config-plugins": "^8.0.10",
"@react-native-community/eslint-config": "0.0.5",
"@types/react-native": "0.60.25",
"@typescript-eslint/eslint-plugin": "2.12.0",
Expand Down
8 changes: 8 additions & 0 deletions plugins/android/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
let lineImplementation = "implementation 'com.linecorp.linesdk:linesdk:5.8.0'"

let compileOptions = `compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}`

module.exports = { compileOptions, lineImplementation }
37 changes: 37 additions & 0 deletions plugins/android/withApplyAndroidCompileOptions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const {
withAppBuildGradle,
WarningAggregator,
} = require('@expo/config-plugins')
const { compileOptions } = require('./constants')

// https://developers.line.biz/en/docs/android-sdk/integrate-line-login/#add-android-compilation-options
function applyCompileOptions(appBuildGradle) {
// TODO: Find a more stable solution for this
if (!appBuildGradle.includes(compileOptions)) {
return appBuildGradle.replace(
/android\s?{/,
`android {
${compileOptions}`,
)
} else {
return appBuildGradle
}
}

function withApplyAndroidCompileOptions(config) {
return withAppBuildGradle(config, config => {
if (config.modResults.language === 'groovy') {
config.modResults.contents = applyCompileOptions(
config.modResults.contents,
)
} else {
WarningAggregator.addWarningAndroid(
'react-native-line',
`Cannot automatically configure app build.gradle if it's not groovy`,
)
}
return config
})
}

module.exports = withApplyAndroidCompileOptions
38 changes: 38 additions & 0 deletions plugins/android/withApplyLineImplementation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const {
withAppBuildGradle,
WarningAggregator,
} = require('@expo/config-plugins')
const { lineImplementation } = require('./constants')

// Android mods
// https://developers.line.biz/en/docs/android-sdk/integrate-line-login/#import-library-into-your-project
function applyLineImplementation(appBuildGradle) {
// TODO: Find a more stable solution for this
if (!appBuildGradle.includes(lineImplementation)) {
return appBuildGradle.replace(
/dependencies\s?{/,
`dependencies {
${lineImplementation}`,
)
} else {
return appBuildGradle
}
}

function withApplyLineImplementation(config) {
return withAppBuildGradle(config, config => {
if (config.modResults.language === 'groovy') {
config.modResults.contents = applyLineImplementation(
config.modResults.contents,
)
} else {
WarningAggregator.addWarningAndroid(
'react-native-line',
`Cannot automatically configure app build.gradle if it's not groovy`,
)
}
return config
})
}

module.exports = withApplyLineImplementation
34 changes: 34 additions & 0 deletions plugins/android/withApplyMaven.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const {
withAppBuildGradle,
WarningAggregator,
} = require('@expo/config-plugins')

function applyMavenRepositories(appBuildGradle) {
// TODO: Find a more stable solution for this
if (
appBuildGradle.includes(`repositories {
mavenCentral()`)
) {
return appBuildGradle
} else {
return appBuildGradle + `\nrepositories {\nmavenCentral()\n}`
}
}

function withApplyMaven(config) {
return withAppBuildGradle(config, config => {
if (config.modResults.language === 'groovy') {
config.modResults.contents = applyMavenRepositories(
config.modResults.contents,
)
} else {
WarningAggregator.addWarningAndroid(
'react-native-line',
`Cannot automatically configure app build.gradle if it's not groovy`,
)
}
return config
})
}

module.exports = withApplyMaven
28 changes: 28 additions & 0 deletions plugins/android/withLineChannelId.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const { withStringsXml } = require('@expo/config-plugins')

function withLineChannelId(config, { channelId }) {
return withStringsXml(config, config => {
let strings = config.modResults.resources.string

let line_channel_id = strings.findIndex(
value => value.$.name === 'line_channel_id',
)

if (line_channel_id !== -1) {
// Dp nothing
return config
} else {
strings.push({
$: {
name: 'line_channel_id',
translatable: 'false',
},
_: channelId.toString(),
})
}

return config
})
}

module.exports = withLineChannelId
15 changes: 15 additions & 0 deletions plugins/android/withLineManifest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const { withAndroidManifest } = require('@expo/config-plugins')

function withLineManifest(config) {
return withAndroidManifest(config, config => {
let manifest = config.modResults.manifest
let application = manifest.application

manifest.$['xmlns:tools'] = 'http://schemas.android.com/tools'
application[0].$['tools:replace'] = 'android:allowBackup'

return config
})
}

module.exports = withLineManifest
73 changes: 73 additions & 0 deletions plugins/ios/appDelegate_template.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#import "AppDelegate.h"
// Line-SDK-RN
#import "RNLine-Swift.h"
#import <Firebase/Firebase.h>

#import <React/RCTBundleURLProvider.h>
#import <React/RCTLinkingManager.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// @generated begin @react-native-firebase/app-didFinishLaunchingWithOptions - expo prebuild (DO NOT MODIFY) sync-ecd111c37e49fdd1ed6354203cd6b1e2a38cccda
[FIRApp configure];
// @generated end @react-native-firebase/app-didFinishLaunchingWithOptions
self.moduleName = @"main";

// You can add your custom initial props in the dictionary below.
// They will be passed down to the ViewController used by React Native.
self.initialProps = @{};

// Line-SDK-RN YOUR_CHANNEL_ID changed
[LineLogin setupWithChannelID:@"CHANNEL_ID_REPLACE" universalLinkURL:nil];

return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
return [self bundleURL];
}

- (NSURL *)bundleURL
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@".expo/.virtual-metro-entry"];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}


// Linking API
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
return [LineLogin application:application open:url options:options] || [super application:application openURL:url options:options] || [RCTLinkingManager application:application openURL:url options:options];
}

// Universal Links
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
BOOL result = [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
BOOL handledLine = [LineLogin application:application continue:userActivity restorationHandler:restorationHandler];
return [super application:application continueUserActivity:userActivity restorationHandler:restorationHandler] || handledLine || result;
}

// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
return [super application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}

// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
return [super application:application didFailToRegisterForRemoteNotificationsWithError:error];
}

// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
return [super application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
}

@end
80 changes: 80 additions & 0 deletions plugins/ios/withAppDelegateMod.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
const { withAppDelegate } = require('@expo/config-plugins');
const { mergeContents } = require('@expo/config-plugins/build/utils/generateCode')

/*
* mergeContents doesn't work with offset <1 to put value before anchor,
* hence I made this utility function, probably unstable, use at your own risk
* */
function injectIntoContents(contents, searchAnchor, injection) {
const openUrlInjectionIndex = contents.indexOf(searchAnchor);

// Check if the anchor was found; if not, return contents as is
if (openUrlInjectionIndex === -1) {
console.warn('Search anchor not found in contents.');
return contents;
}

// Calculate the insertion point
const anchorIndex = openUrlInjectionIndex + searchAnchor.length;

// Inject the specified code at the appropriate location
return (
contents.substring(0, anchorIndex) +
injection +
contents.substring(anchorIndex)
);
}

const withAppDelegateMod = (config, { channelId }) => {
return withAppDelegate(
config,
async (config) => {
const appDelegate = config.modResults;

const withHeader = mergeContents({
anchor: /#import "AppDelegate\.h"/,
comment: "//",
newSrc: '#import "RNLine-Swift.h"',
offset: 1,
src: appDelegate.contents,
tag: "line-header",
});

const withChannelIdInjection = mergeContents({
anchor: "self.initialProps = @{};",
comment: "//",
newSrc: `[LineLogin setupWithChannelID:@"${channelId}" universalLinkURL:nil];`,
offset: 1,
src: withHeader.contents,
tag: "line-channel-id",
});

//
// Handle redirection back to the app from Line
//
withChannelIdInjection.contents = injectIntoContents(withChannelIdInjection.contents, `- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
return`,` [LineLogin application:application open:url options:options] ||`)

const withContinueUserActivityVariableInjection = mergeContents({
anchor: "restorationHandler:restorationHandler];",
comment: "//",
newSrc: `BOOL handledLine = [LineLogin application:application continue:userActivity restorationHandler:restorationHandler];`,
offset: 1,
src: withChannelIdInjection.contents,
tag: "line-continue-user-activity-variable-injection",
});

withChannelIdInjection.contents = injectIntoContents(withContinueUserActivityVariableInjection.contents,`] || result`, ` || handledLine` )

return {
...config,
modResults: {
...appDelegate,
contents: withChannelIdInjection.contents
}
};
}
);
};

module.exports = withAppDelegateMod
Loading