react-native-custom-splash 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +278 -0
- package/android/build.gradle +52 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/com/rncustomsplash/SplashScreenModule.kt +111 -0
- package/android/src/main/java/com/rncustomsplash/SplashScreenPackage.kt +16 -0
- package/android/src/main/res/drawable/splash.xml +10 -0
- package/android/src/main/res/layout/splash_screen.xml +16 -0
- package/android/src/main/res/values/colors.xml +4 -0
- package/ios/SplashScreenModule.m +23 -0
- package/ios/SplashScreenModule.swift +114 -0
- package/package.json +45 -0
- package/plugin/src/index.js +89 -0
- package/react-native-custom-splash.podspec +18 -0
- package/src/index.js +3 -0
- package/src/index.tsx +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# react-native-custom-splash
|
|
2
|
+
|
|
3
|
+
A custom splash screen module for React Native with native iOS and Android support, fully compatible with Expo.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ **Native iOS & Android** splash screens
|
|
8
|
+
- ✅ **Expo compatible** with config plugin
|
|
9
|
+
- ✅ **Customizable** splash images
|
|
10
|
+
- ✅ **Animated transitions** for smooth hiding
|
|
11
|
+
- ✅ **TypeScript** support
|
|
12
|
+
- ✅ **Auto-show** on app launch
|
|
13
|
+
- ✅ **Manual control** with show/hide methods
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
### For Expo Projects
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install react-native-custom-splash
|
|
21
|
+
# or
|
|
22
|
+
yarn add react-native-custom-splash
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Add the plugin to your `app.json` or `app.config.js`:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"expo": {
|
|
30
|
+
"plugins": [
|
|
31
|
+
"react-native-custom-splash"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Then run prebuild:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx expo prebuild
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### For Bare React Native Projects
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install react-native-custom-splash
|
|
47
|
+
# or
|
|
48
|
+
yarn add react-native-custom-splash
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
#### iOS Setup
|
|
52
|
+
|
|
53
|
+
1. Install pods:
|
|
54
|
+
```bash
|
|
55
|
+
cd ios && pod install && cd ..
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
2. The module will be automatically linked.
|
|
59
|
+
|
|
60
|
+
#### Android Setup
|
|
61
|
+
|
|
62
|
+
1. Add the package to your `MainApplication.kt`:
|
|
63
|
+
|
|
64
|
+
```kotlin
|
|
65
|
+
import com.rncustomsplash.SplashScreenPackage
|
|
66
|
+
|
|
67
|
+
// In getPackages() method:
|
|
68
|
+
packages.add(SplashScreenPackage())
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
2. Show splash in `MainActivity.kt`:
|
|
72
|
+
|
|
73
|
+
```kotlin
|
|
74
|
+
import com.rncustomsplash.SplashScreenModule
|
|
75
|
+
|
|
76
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
77
|
+
SplashScreenModule.show(this)
|
|
78
|
+
super.onCreate(savedInstanceState)
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Adding Custom Splash Images
|
|
83
|
+
|
|
84
|
+
### iOS
|
|
85
|
+
|
|
86
|
+
Add your splash image to your Xcode project:
|
|
87
|
+
1. Open your project in Xcode
|
|
88
|
+
2. Add an image asset named `splash` to your Assets catalog
|
|
89
|
+
3. Or add a `splash.png` file to your project
|
|
90
|
+
|
|
91
|
+
### Android
|
|
92
|
+
|
|
93
|
+
Add your splash image to Android resources:
|
|
94
|
+
1. Add `splash.png` (or `splash.jpg`) to `android/app/src/main/res/drawable/`
|
|
95
|
+
2. Or create a drawable resource named `splash`
|
|
96
|
+
|
|
97
|
+
You can also customize the background color in `android/app/src/main/res/values/colors.xml`:
|
|
98
|
+
|
|
99
|
+
```xml
|
|
100
|
+
<color name="splash_background">#FFFFFF</color>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Usage
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import SplashScreen from 'react-native-custom-splash';
|
|
107
|
+
import { useEffect } from 'react';
|
|
108
|
+
|
|
109
|
+
function App() {
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
// Hide splash screen after app is ready
|
|
112
|
+
// The splash screen shows automatically on launch
|
|
113
|
+
|
|
114
|
+
// Simple hide (instant)
|
|
115
|
+
SplashScreen.hide(false);
|
|
116
|
+
|
|
117
|
+
// Or with animation
|
|
118
|
+
SplashScreen.hide(true);
|
|
119
|
+
|
|
120
|
+
// You can also show it again
|
|
121
|
+
// SplashScreen.show();
|
|
122
|
+
}, []);
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
// Your app content
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Example Project
|
|
131
|
+
|
|
132
|
+
A complete working example is included in the `example/` directory. It demonstrates:
|
|
133
|
+
|
|
134
|
+
- ✅ Automatic splash screen on launch
|
|
135
|
+
- ✅ Loading progress simulation
|
|
136
|
+
- ✅ Animated hide transitions
|
|
137
|
+
- ✅ Manual show/hide controls
|
|
138
|
+
- ✅ Full TypeScript integration
|
|
139
|
+
|
|
140
|
+
### Run the Example
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
cd example
|
|
144
|
+
npm install
|
|
145
|
+
npx expo prebuild
|
|
146
|
+
npm run ios # or npm run android
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
See [example/README.md](./example/README.md) for detailed instructions.
|
|
150
|
+
|
|
151
|
+
## API Reference
|
|
152
|
+
|
|
153
|
+
### `SplashScreen.show()`
|
|
154
|
+
|
|
155
|
+
Shows the splash screen.
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
SplashScreen.show();
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### `SplashScreen.hide(animated?)`
|
|
162
|
+
|
|
163
|
+
Hides the splash screen.
|
|
164
|
+
|
|
165
|
+
**Parameters:**
|
|
166
|
+
- `animated` (boolean, optional): Whether to animate the hide transition. Default: `false`
|
|
167
|
+
|
|
168
|
+
**Returns:** `Promise<boolean>` - Resolves to `true` if successful
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
// Hide instantly
|
|
172
|
+
await SplashScreen.hide();
|
|
173
|
+
|
|
174
|
+
// Hide with fade animation
|
|
175
|
+
await SplashScreen.hide(true);
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Example
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import React, { useEffect, useState } from 'react';
|
|
182
|
+
import { View, Text, ActivityIndicator } from 'react-native';
|
|
183
|
+
import SplashScreen from 'react-native-custom-splash';
|
|
184
|
+
|
|
185
|
+
export default function App() {
|
|
186
|
+
const [appIsReady, setAppIsReady] = useState(false);
|
|
187
|
+
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
async function prepare() {
|
|
190
|
+
try {
|
|
191
|
+
// Load your resources here
|
|
192
|
+
await loadFonts();
|
|
193
|
+
await loadData();
|
|
194
|
+
|
|
195
|
+
// Artificially delay for demo
|
|
196
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
197
|
+
} catch (e) {
|
|
198
|
+
console.warn(e);
|
|
199
|
+
} finally {
|
|
200
|
+
setAppIsReady(true);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
prepare();
|
|
205
|
+
}, []);
|
|
206
|
+
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
if (appIsReady) {
|
|
209
|
+
// Hide splash screen with animation when app is ready
|
|
210
|
+
SplashScreen.hide(true);
|
|
211
|
+
}
|
|
212
|
+
}, [appIsReady]);
|
|
213
|
+
|
|
214
|
+
if (!appIsReady) {
|
|
215
|
+
return null; // Splash screen is visible
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return (
|
|
219
|
+
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
|
220
|
+
<Text>App is ready!</Text>
|
|
221
|
+
</View>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Troubleshooting
|
|
227
|
+
|
|
228
|
+
### iOS
|
|
229
|
+
|
|
230
|
+
**Module not found:**
|
|
231
|
+
- Make sure you ran `pod install` in the `ios/` directory
|
|
232
|
+
- Clean build folder: `cd ios && rm -rf build && cd ..`
|
|
233
|
+
- Rebuild the app
|
|
234
|
+
|
|
235
|
+
**Splash image not showing:**
|
|
236
|
+
- Verify the image is named `splash` in your Assets catalog
|
|
237
|
+
- Check that the image is added to the target
|
|
238
|
+
|
|
239
|
+
### Android
|
|
240
|
+
|
|
241
|
+
**Module not found:**
|
|
242
|
+
- Verify `SplashScreenPackage()` is added to `MainApplication.kt`
|
|
243
|
+
- Clean build: `cd android && ./gradlew clean && cd ..`
|
|
244
|
+
- Rebuild the app
|
|
245
|
+
|
|
246
|
+
**Splash image not showing:**
|
|
247
|
+
- Check that `splash.png` exists in `res/drawable/`
|
|
248
|
+
- Verify the resource name matches in `splash_screen.xml`
|
|
249
|
+
|
|
250
|
+
### Expo
|
|
251
|
+
|
|
252
|
+
**Plugin not working:**
|
|
253
|
+
- Make sure you added the plugin to `app.json`
|
|
254
|
+
- Run `npx expo prebuild --clean`
|
|
255
|
+
- Rebuild the app
|
|
256
|
+
|
|
257
|
+
## TypeScript
|
|
258
|
+
|
|
259
|
+
This package includes TypeScript definitions. The module exports the following interface:
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
interface SplashScreenInterface {
|
|
263
|
+
show(): void;
|
|
264
|
+
hide(animated?: boolean): Promise<boolean>;
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## License
|
|
269
|
+
|
|
270
|
+
MIT
|
|
271
|
+
|
|
272
|
+
## Contributing
|
|
273
|
+
|
|
274
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
275
|
+
|
|
276
|
+
## Issues
|
|
277
|
+
|
|
278
|
+
If you encounter any issues, please file them on the GitHub repository.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.kotlin_version = '1.8.0'
|
|
3
|
+
|
|
4
|
+
repositories {
|
|
5
|
+
google()
|
|
6
|
+
mavenCentral()
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
dependencies {
|
|
10
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
apply plugin: 'com.android.library'
|
|
15
|
+
apply plugin: 'kotlin-android'
|
|
16
|
+
|
|
17
|
+
android {
|
|
18
|
+
compileSdkVersion 33
|
|
19
|
+
|
|
20
|
+
defaultConfig {
|
|
21
|
+
minSdkVersion 21
|
|
22
|
+
targetSdkVersion 33
|
|
23
|
+
versionCode 1
|
|
24
|
+
versionName "1.0.0"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
buildTypes {
|
|
28
|
+
release {
|
|
29
|
+
minifyEnabled false
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
compileOptions {
|
|
34
|
+
sourceCompatibility JavaVersion.VERSION_11
|
|
35
|
+
targetCompatibility JavaVersion.VERSION_11
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
kotlinOptions {
|
|
39
|
+
jvmTarget = '11'
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
repositories {
|
|
44
|
+
google()
|
|
45
|
+
mavenCentral()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
dependencies {
|
|
49
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
50
|
+
implementation 'com.facebook.react:react-native:+'
|
|
51
|
+
implementation 'androidx.appcompat:appcompat:1.6.1'
|
|
52
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
package com.rncustomsplash
|
|
2
|
+
|
|
3
|
+
import android.app.Dialog
|
|
4
|
+
import android.graphics.Color
|
|
5
|
+
import android.graphics.drawable.ColorDrawable
|
|
6
|
+
import android.os.Build
|
|
7
|
+
import android.view.View
|
|
8
|
+
import android.view.WindowManager
|
|
9
|
+
import androidx.appcompat.app.AppCompatActivity
|
|
10
|
+
import com.facebook.react.bridge.Promise
|
|
11
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
12
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
13
|
+
import com.facebook.react.bridge.ReactMethod
|
|
14
|
+
import com.facebook.react.bridge.UiThreadUtil
|
|
15
|
+
|
|
16
|
+
class SplashScreenModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
|
|
17
|
+
|
|
18
|
+
companion object {
|
|
19
|
+
private var splashDialog: Dialog? = null
|
|
20
|
+
private var isVisible = false
|
|
21
|
+
|
|
22
|
+
@JvmStatic
|
|
23
|
+
fun show(activity: AppCompatActivity) {
|
|
24
|
+
if (isVisible) return
|
|
25
|
+
|
|
26
|
+
UiThreadUtil.runOnUiThread {
|
|
27
|
+
try {
|
|
28
|
+
splashDialog = Dialog(activity, android.R.style.Theme_NoTitleBar_Fullscreen).apply {
|
|
29
|
+
setContentView(R.layout.splash_screen)
|
|
30
|
+
setCancelable(false)
|
|
31
|
+
window?.apply {
|
|
32
|
+
setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
|
|
33
|
+
|
|
34
|
+
// Make splash screen full screen
|
|
35
|
+
setFlags(
|
|
36
|
+
WindowManager.LayoutParams.FLAG_FULLSCREEN,
|
|
37
|
+
WindowManager.LayoutParams.FLAG_FULLSCREEN
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
// For API 30+, use WindowInsetsController
|
|
41
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
42
|
+
setDecorFitsSystemWindows(false)
|
|
43
|
+
} else {
|
|
44
|
+
@Suppress("DEPRECATION")
|
|
45
|
+
decorView.systemUiVisibility = (
|
|
46
|
+
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
47
|
+
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
48
|
+
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
49
|
+
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
|
50
|
+
or View.SYSTEM_UI_FLAG_FULLSCREEN
|
|
51
|
+
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
show()
|
|
56
|
+
}
|
|
57
|
+
isVisible = true
|
|
58
|
+
} catch (e: Exception) {
|
|
59
|
+
e.printStackTrace()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
override fun getName(): String {
|
|
66
|
+
return "SplashScreen"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@ReactMethod
|
|
70
|
+
fun show() {
|
|
71
|
+
reactApplicationContext.currentActivity?.let { activity ->
|
|
72
|
+
if (activity is AppCompatActivity) {
|
|
73
|
+
show(activity)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@ReactMethod
|
|
79
|
+
fun hide(animated: Boolean, promise: Promise) {
|
|
80
|
+
if (!isVisible) {
|
|
81
|
+
promise.resolve(false)
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
UiThreadUtil.runOnUiThread {
|
|
86
|
+
try {
|
|
87
|
+
splashDialog?.let { dialog ->
|
|
88
|
+
if (animated) {
|
|
89
|
+
// Add fade out animation
|
|
90
|
+
dialog.window?.decorView?.animate()
|
|
91
|
+
?.alpha(0f)
|
|
92
|
+
?.setDuration(300)
|
|
93
|
+
?.withEndAction {
|
|
94
|
+
dialog.dismiss()
|
|
95
|
+
splashDialog = null
|
|
96
|
+
isVisible = false
|
|
97
|
+
}
|
|
98
|
+
?.start()
|
|
99
|
+
} else {
|
|
100
|
+
dialog.dismiss()
|
|
101
|
+
splashDialog = null
|
|
102
|
+
isVisible = false
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
promise.resolve(true)
|
|
106
|
+
} catch (e: Exception) {
|
|
107
|
+
promise.reject("ERROR", e.message)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
package com.rncustomsplash
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
|
|
8
|
+
class SplashScreenPackage : ReactPackage {
|
|
9
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
10
|
+
return listOf(SplashScreenModule(reactContext))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
14
|
+
return emptyList()
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
|
3
|
+
<item android:drawable="@color/splash_background"/>
|
|
4
|
+
<item>
|
|
5
|
+
<!-- This is a placeholder. Users will replace with their own splash image -->
|
|
6
|
+
<shape android:shape="rectangle">
|
|
7
|
+
<solid android:color="@color/splash_background"/>
|
|
8
|
+
</shape>
|
|
9
|
+
</item>
|
|
10
|
+
</layer-list>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
3
|
+
android:layout_width="match_parent"
|
|
4
|
+
android:layout_height="match_parent"
|
|
5
|
+
android:background="@color/splash_background"
|
|
6
|
+
android:gravity="center"
|
|
7
|
+
android:orientation="vertical">
|
|
8
|
+
|
|
9
|
+
<ImageView
|
|
10
|
+
android:id="@+id/splash_image"
|
|
11
|
+
android:layout_width="match_parent"
|
|
12
|
+
android:layout_height="match_parent"
|
|
13
|
+
android:scaleType="centerCrop"
|
|
14
|
+
android:src="@drawable/splash" />
|
|
15
|
+
|
|
16
|
+
</LinearLayout>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//
|
|
2
|
+
// SplashScreenModule.m
|
|
3
|
+
// RNCustomSplash
|
|
4
|
+
//
|
|
5
|
+
// Bridge for SplashScreenModule
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#import <React/RCTBridgeModule.h>
|
|
9
|
+
|
|
10
|
+
@interface RCT_EXTERN_MODULE(SplashScreen, NSObject)
|
|
11
|
+
|
|
12
|
+
RCT_EXTERN_METHOD(show)
|
|
13
|
+
|
|
14
|
+
RCT_EXTERN_METHOD(hide:(BOOL)animated
|
|
15
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
16
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
17
|
+
|
|
18
|
+
+ (BOOL)requiresMainQueueSetup
|
|
19
|
+
{
|
|
20
|
+
return YES;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
//
|
|
2
|
+
// SplashScreenModule.swift
|
|
3
|
+
// RNCustomSplash
|
|
4
|
+
//
|
|
5
|
+
// Custom Native Splash Screen Module for iOS
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import UIKit
|
|
10
|
+
import React
|
|
11
|
+
|
|
12
|
+
@objc(SplashScreen)
|
|
13
|
+
class SplashScreenModule: NSObject {
|
|
14
|
+
|
|
15
|
+
private static var splashWindow: UIWindow?
|
|
16
|
+
private static var isVisible = false
|
|
17
|
+
|
|
18
|
+
@objc
|
|
19
|
+
static func requiresMainQueueSetup() -> Bool {
|
|
20
|
+
return true
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
override init() {
|
|
24
|
+
super.init()
|
|
25
|
+
// Show splash automatically when module initializes
|
|
26
|
+
DispatchQueue.main.async {
|
|
27
|
+
SplashScreenModule.showSplashScreen()
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private static func showSplashScreen() {
|
|
32
|
+
guard !isVisible else { return }
|
|
33
|
+
|
|
34
|
+
if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
|
|
35
|
+
let window = UIWindow(windowScene: scene)
|
|
36
|
+
window.windowLevel = .alert + 1
|
|
37
|
+
window.backgroundColor = .white
|
|
38
|
+
|
|
39
|
+
// Create splash view controller
|
|
40
|
+
let splashVC = UIViewController()
|
|
41
|
+
splashVC.view.backgroundColor = .white
|
|
42
|
+
|
|
43
|
+
// Try to load splash image from the main bundle
|
|
44
|
+
// Users should add their splash image to their app's assets
|
|
45
|
+
if let splashImage = UIImage(named: "splash") {
|
|
46
|
+
let imageView = UIImageView(image: splashImage)
|
|
47
|
+
imageView.contentMode = .scaleAspectFill
|
|
48
|
+
imageView.frame = splashVC.view.bounds
|
|
49
|
+
imageView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
50
|
+
splashVC.view.addSubview(imageView)
|
|
51
|
+
} else {
|
|
52
|
+
// Fallback: show white screen if no splash image is provided
|
|
53
|
+
if #available(iOS 13.0, *) {
|
|
54
|
+
splashVC.view.backgroundColor = .systemBackground
|
|
55
|
+
} else {
|
|
56
|
+
splashVC.view.backgroundColor = .white
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
window.rootViewController = splashVC
|
|
61
|
+
window.makeKeyAndVisible()
|
|
62
|
+
|
|
63
|
+
splashWindow = window
|
|
64
|
+
isVisible = true
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@objc
|
|
69
|
+
func show() {
|
|
70
|
+
DispatchQueue.main.async {
|
|
71
|
+
SplashScreenModule.showSplashScreen()
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@objc
|
|
76
|
+
func hide(_ animated: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
77
|
+
DispatchQueue.main.async {
|
|
78
|
+
guard SplashScreenModule.isVisible, let window = SplashScreenModule.splashWindow else {
|
|
79
|
+
resolve(false)
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if animated {
|
|
84
|
+
UIView.animate(withDuration: 0.3, animations: {
|
|
85
|
+
window.alpha = 0
|
|
86
|
+
}) { _ in
|
|
87
|
+
window.isHidden = true
|
|
88
|
+
window.rootViewController = nil
|
|
89
|
+
SplashScreenModule.splashWindow = nil
|
|
90
|
+
SplashScreenModule.isVisible = false
|
|
91
|
+
|
|
92
|
+
// Ensure the main window becomes key again
|
|
93
|
+
if let mainWindow = UIApplication.shared.windows.first(where: { $0.rootViewController != nil && $0 != window }) {
|
|
94
|
+
mainWindow.makeKeyAndVisible()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
resolve(true)
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
window.isHidden = true
|
|
101
|
+
window.rootViewController = nil
|
|
102
|
+
SplashScreenModule.splashWindow = nil
|
|
103
|
+
SplashScreenModule.isVisible = false
|
|
104
|
+
|
|
105
|
+
// Ensure the main window becomes key again
|
|
106
|
+
if let mainWindow = UIApplication.shared.windows.first(where: { $0.rootViewController != nil && $0 != window }) {
|
|
107
|
+
mainWindow.makeKeyAndVisible()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
resolve(true)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-native-custom-splash",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A custom splash screen module for React Native with native iOS and Android support, fully compatible with Expo",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"types": "src/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "echo \"No tests yet\"",
|
|
9
|
+
"prepare": "echo \"Package prepared\""
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"react-native",
|
|
13
|
+
"expo",
|
|
14
|
+
"splash-screen",
|
|
15
|
+
"splash",
|
|
16
|
+
"ios",
|
|
17
|
+
"android",
|
|
18
|
+
"native-module"
|
|
19
|
+
],
|
|
20
|
+
"author": "Your Name",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/vijaykishan312/react-native-custom-splash.git"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"react": "*",
|
|
28
|
+
"react-native": "*",
|
|
29
|
+
"expo": "*"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/react": "^18.0.0",
|
|
33
|
+
"@types/react-native": "^0.72.0",
|
|
34
|
+
"typescript": "^5.0.0"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"src/",
|
|
38
|
+
"ios/",
|
|
39
|
+
"android/",
|
|
40
|
+
"plugin/",
|
|
41
|
+
"react-native-custom-splash.podspec",
|
|
42
|
+
"README.md",
|
|
43
|
+
"LICENSE"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const {
|
|
2
|
+
withDangerousMod,
|
|
3
|
+
withPlugins,
|
|
4
|
+
withMainActivity,
|
|
5
|
+
withMainApplication,
|
|
6
|
+
} = require('@expo/config-plugins');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Plugin to add SplashScreenModule to Android
|
|
12
|
+
*/
|
|
13
|
+
const withSplashScreenAndroid = (config) => {
|
|
14
|
+
// Add package to MainApplication
|
|
15
|
+
config = withMainApplication(config, async (config) => {
|
|
16
|
+
const { modResults } = config;
|
|
17
|
+
let contents = modResults.contents;
|
|
18
|
+
|
|
19
|
+
// Add import if not already present
|
|
20
|
+
if (!contents.includes('import com.rncustomsplash.SplashScreenPackage')) {
|
|
21
|
+
contents = contents.replace(
|
|
22
|
+
/(package\s+[\w.]+)/,
|
|
23
|
+
'$1\nimport com.rncustomsplash.SplashScreenPackage'
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Add package to packages list
|
|
28
|
+
if (!contents.includes('SplashScreenPackage()')) {
|
|
29
|
+
contents = contents.replace(
|
|
30
|
+
/(packages\.apply\s*{[^}]*)/,
|
|
31
|
+
'$1\n add(SplashScreenPackage())'
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
modResults.contents = contents;
|
|
36
|
+
return config;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Add splash initialization to MainActivity
|
|
40
|
+
config = withMainActivity(config, async (config) => {
|
|
41
|
+
const { modResults } = config;
|
|
42
|
+
let contents = modResults.contents;
|
|
43
|
+
|
|
44
|
+
// Add import if not already present
|
|
45
|
+
if (!contents.includes('import com.rncustomsplash.SplashScreenModule')) {
|
|
46
|
+
contents = contents.replace(
|
|
47
|
+
/(package\s+[\w.]+)/,
|
|
48
|
+
'$1\nimport com.rncustomsplash.SplashScreenModule'
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Add splash show in onCreate
|
|
53
|
+
if (!contents.includes('SplashScreenModule.show(this)')) {
|
|
54
|
+
contents = contents.replace(
|
|
55
|
+
/(override\s+fun\s+onCreate\([^)]*\)\s*{)/,
|
|
56
|
+
'$1\n // Show splash screen\n SplashScreenModule.show(this)\n'
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
modResults.contents = contents;
|
|
61
|
+
return config;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return config;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Plugin to add SplashScreenModule to iOS
|
|
69
|
+
*/
|
|
70
|
+
const withSplashScreenIOS = (config) => {
|
|
71
|
+
return withDangerousMod(config, [
|
|
72
|
+
'ios',
|
|
73
|
+
async (config) => {
|
|
74
|
+
// The native files will be linked via CocoaPods
|
|
75
|
+
// No additional configuration needed here
|
|
76
|
+
return config;
|
|
77
|
+
},
|
|
78
|
+
]);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Main plugin export
|
|
83
|
+
*/
|
|
84
|
+
module.exports = (config) => {
|
|
85
|
+
return withPlugins(config, [
|
|
86
|
+
withSplashScreenAndroid,
|
|
87
|
+
withSplashScreenIOS,
|
|
88
|
+
]);
|
|
89
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "../package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "RNCustomSplash"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["repository"]["url"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = { package["author"] => "" }
|
|
12
|
+
s.platforms = { :ios => "13.0" }
|
|
13
|
+
s.source = { :git => package["repository"]["url"], :tag => "#{s.version}" }
|
|
14
|
+
|
|
15
|
+
s.source_files = "*.{h,m,mm,swift}"
|
|
16
|
+
|
|
17
|
+
s.dependency "React-Core"
|
|
18
|
+
end
|
package/src/index.js
ADDED
package/src/index.tsx
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { NativeModules, Platform } from 'react-native';
|
|
2
|
+
|
|
3
|
+
const LINKING_ERROR =
|
|
4
|
+
`The package 'react-native-custom-splash' doesn't seem to be linked. Make sure: \n\n` +
|
|
5
|
+
Platform.select({ ios: "- Run 'pod install' in the ios/ directory\n", default: '' }) +
|
|
6
|
+
'- Rebuild the app after installing the package\n' +
|
|
7
|
+
'- If you are using Expo, run npx expo prebuild\n';
|
|
8
|
+
|
|
9
|
+
// Try both module names for compatibility
|
|
10
|
+
const SplashScreenModule =
|
|
11
|
+
NativeModules.SplashScreen || NativeModules.SplashScreenModule;
|
|
12
|
+
|
|
13
|
+
if (!SplashScreenModule) {
|
|
14
|
+
throw new Error(LINKING_ERROR);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (__DEV__ && !SplashScreenModule) {
|
|
18
|
+
console.error(
|
|
19
|
+
'❌ SplashScreen native module not found!',
|
|
20
|
+
'\nAvailable modules:',
|
|
21
|
+
Object.keys(NativeModules).filter((m) => m.includes('Splash'))
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface SplashScreenInterface {
|
|
26
|
+
/**
|
|
27
|
+
* Show the splash screen
|
|
28
|
+
*/
|
|
29
|
+
show(): void;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Hide the splash screen
|
|
33
|
+
* @param animated - Whether to animate the hide transition (default: false)
|
|
34
|
+
* @returns Promise that resolves to true if successful
|
|
35
|
+
*/
|
|
36
|
+
hide(animated?: boolean): Promise<boolean>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const SplashScreen: SplashScreenInterface = {
|
|
40
|
+
show: () => {
|
|
41
|
+
if (SplashScreenModule?.show) {
|
|
42
|
+
if (__DEV__) {
|
|
43
|
+
console.log('📱 Calling SplashScreen.show()');
|
|
44
|
+
}
|
|
45
|
+
SplashScreenModule.show();
|
|
46
|
+
} else {
|
|
47
|
+
console.warn('⚠️ SplashScreen.show() not available');
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
hide: async (animated: boolean = false): Promise<boolean> => {
|
|
52
|
+
if (SplashScreenModule?.hide) {
|
|
53
|
+
if (__DEV__) {
|
|
54
|
+
console.log('📱 Calling SplashScreen.hide(animated:', animated, ')');
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
return await SplashScreenModule.hide(animated);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error('❌ Error hiding splash screen:', error);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
console.warn('⚠️ SplashScreen.hide() not available');
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export default SplashScreen;
|