Skip to content

Commit

Permalink
feat: Allow to show/hide SplashScreens by name
Browse files Browse the repository at this point in the history
With previous commit we can show and hide the SplashScreen when the app
enters and leaves background state

The problem with that naïve implementation is that we don't have
precise control of when we want to hide the SplashScreen because the
app successfully started or because the app came back from background

One example that is problematic is:
- The app starts and display the SplashScreen
- The Home is loading
- The user briefly open the iOS multi-task screen and directly come
back to the app
- The app detects that it became active and tries to hide the
SplashScreen
- But the Home has not finished loading so it should not hide the
SplashScreen

To prevent this a solution is to give a name to SplashScreens so we can
ask to only hide the "background state" related SplashScreen and keep
the one for Home displayed

The impact is that to hide the SplashScreen, all SplashScreen names
should be hide

For now we need two SplashScreen:
- `global` corresponds to the main SplashScreen that is used to hide
the app when something is loading
- `secure_background` corresponds to the SplashScreen used to hide the
app's content when sent to background
  • Loading branch information
Ldoppea committed Aug 3, 2023
1 parent 0df7d84 commit c17337c
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ private enum Status {

private static int mDrawableResId = -1;
private static final ArrayList<RNBootSplashTask> mTaskQueue = new ArrayList<>();
private static final ArrayList<String> mBootsplashNames = new ArrayList<>();
private static Status mStatus = Status.HIDDEN;
private static boolean mIsAppInBackground = false;

Expand Down Expand Up @@ -95,7 +96,7 @@ public void onHostDestroy() {
@Override
public void onHostPause() {
Promise promise = new PromiseImpl(null, null);
mTaskQueue.add(new RNBootSplashTask(RNBootSplashTask.Type.SHOW, false, promise));
mTaskQueue.add(new RNBootSplashTask(RNBootSplashTask.Type.SHOW, false, "secure_background", promise));
shiftNextTask();
}

Expand Down Expand Up @@ -142,6 +143,7 @@ private void show(final RNBootSplashTask task) {
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
addBootsplashName(task.getBootsplashName());
final Activity activity = getReactApplicationContext().getCurrentActivity();
final Promise promise = task.getPromise();

Expand Down Expand Up @@ -195,6 +197,13 @@ private void hide(final RNBootSplashTask task) {
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
removeBootsplashName(task.getBootsplashName());

if (hasBootsplashToDisplay()) {
waitAndShiftNextTask(); // splash screen should still be displayed for some BootsplashNames
return;
}

final Activity activity = getReactApplicationContext().getCurrentActivity();
final Promise promise = task.getPromise();

Expand Down Expand Up @@ -245,21 +254,21 @@ public void onAnimationEnd(Animator animation) {
}

@ReactMethod
public void show(final boolean fade, final Promise promise) {
public void show(final boolean fade, final String bootsplashName, final Promise promise) {
if (mDrawableResId == -1) {
promise.reject("uninitialized_module", "react-native-bootsplash has not been initialized");
} else {
mTaskQueue.add(new RNBootSplashTask(RNBootSplashTask.Type.SHOW, fade, promise));
mTaskQueue.add(new RNBootSplashTask(RNBootSplashTask.Type.SHOW, fade, bootsplashName, promise));
shiftNextTask();
}
}

@ReactMethod
public void hide(final boolean fade, final Promise promise) {
public void hide(final boolean fade, final String bootsplashName, final Promise promise) {
if (mDrawableResId == -1) {
promise.reject("uninitialized_module", "react-native-bootsplash has not been initialized");
} else {
mTaskQueue.add(new RNBootSplashTask(RNBootSplashTask.Type.HIDE, fade, promise));
mTaskQueue.add(new RNBootSplashTask(RNBootSplashTask.Type.HIDE, fade, bootsplashName, promise));
shiftNextTask();
}
}
Expand All @@ -279,4 +288,20 @@ public void getVisibilityStatus(final Promise promise) {
break;
}
}

protected void addBootsplashName(String name) {
if (!mBootsplashNames.contains(name)) {
mBootsplashNames.add(name);
}
}

protected void removeBootsplashName(String name) {
if (mBootsplashNames.contains(name)) {
mBootsplashNames.remove(name);
}
}

protected boolean hasBootsplashToDisplay() {
return mBootsplashNames.size() != 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,28 @@ public enum Type {
}

private final boolean mFade;
private final String mBootsplashName;
@NonNull private final Promise mPromise;
@NonNull private final Type mType;

public RNBootSplashTask(@NonNull Type type,
boolean fade,
String bootsplashName,
@NonNull Promise promise) {
mType = type;
mFade = fade;
mBootsplashName = bootsplashName;
mPromise = promise;
}

public boolean getFade() {
return mFade;
}

public String getBootsplashName() {
return mBootsplashName;
}

@NonNull
public Type getType() {
return mType;
Expand Down
5 changes: 5 additions & 0 deletions ios/RNBootSplash.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ typedef enum {

@property (nonatomic, readonly) RNBootSplashTaskType type;
@property (nonatomic, readonly) BOOL fade;
@property (nonatomic, readonly) NSString* bootsplashName;
@property (nonatomic, readonly, strong) RCTPromiseResolveBlock _Nonnull resolve;
@property (nonatomic, readonly, strong) RCTPromiseRejectBlock _Nonnull reject;

- (instancetype _Nonnull)initWithType:(RNBootSplashTaskType)type
fade:(BOOL)fade
bootsplashName:(NSString *)bootsplashName
resolver:(RCTPromiseResolveBlock _Nonnull)resolve
rejecter:(RCTPromiseRejectBlock _Nonnull)reject;

Expand All @@ -31,5 +33,8 @@ typedef enum {

+ (void)initWithStoryboard:(NSString * _Nonnull)storyboardName
rootView:(RCTRootView * _Nonnull)rootView;
+ (void)addBootsplashName:(NSString * _Nonnull)name;
+ (void)removeBootsplashName:(NSString * _Nonnull)name;
+ (BOOL)hasBootsplashToDisplay;

@end
32 changes: 31 additions & 1 deletion ios/RNBootSplash.m
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#import <React/RCTUtils.h>

static NSMutableArray<RNBootSplashTask *> *_taskQueue = nil;
static NSMutableArray<NSString *> *_bootsplashNames = nil;
static NSString *_storyboardName = @"BootSplash";
static RCTRootView *_rootView = nil;
static RNBootSplashStatus _status = RNBootSplashStatusHidden;
Expand All @@ -13,11 +14,13 @@ @implementation RNBootSplashTask

- (instancetype)initWithType:(RNBootSplashTaskType)type
fade:(BOOL)fade
bootsplashName:(NSString *)bootsplashName
resolver:(RCTPromiseResolveBlock _Nonnull)resolve
rejecter:(RCTPromiseRejectBlock _Nonnull)reject {
if (self = [super init]) {
_type = type;
_fade = fade;
_bootsplashName = bootsplashName;
_resolve = resolve;
_reject = reject;
}
Expand All @@ -39,12 +42,29 @@ - (dispatch_queue_t)methodQueue {
return dispatch_get_main_queue();
}

+ (void)addBootsplashName:(NSString * _Nonnull)name {
if ([_bootsplashNames indexOfObject:name] == NSNotFound) {
[_bootsplashNames addObject:name];
}
}

+ (void)removeBootsplashName:(NSString * _Nonnull)name {
if ([_bootsplashNames indexOfObject:name] != NSNotFound) {
[_bootsplashNames removeObject:name];
}
}

+ (BOOL)hasBootsplashToDisplay {
return [_bootsplashNames count] != 0;
}

+ (void)initWithStoryboard:(NSString * _Nonnull)storyboardName
rootView:(RCTRootView * _Nonnull)rootView {
_rootView = rootView;
_status = RNBootSplashStatusVisible;
_storyboardName = storyboardName;
_taskQueue = [[NSMutableArray alloc] init];
_bootsplashNames = [[NSMutableArray alloc] init];

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:_storyboardName bundle:nil];
[_rootView setLoadingView:[[storyboard instantiateInitialViewController] view]];
Expand Down Expand Up @@ -128,6 +148,7 @@ + (void)shiftNextTask {
}

+ (void)showWithTask:(RNBootSplashTask *)task {
[RNBootSplash addBootsplashName:task.bootsplashName];
if (_splashVC != nil) {
task.resolve(@(true)); // splash screen is already visible
[self shiftNextTask];
Expand All @@ -150,7 +171,11 @@ + (void)showWithTask:(RNBootSplashTask *)task {
}

+ (void)hideWithTask:(RNBootSplashTask *)task {
if (_splashVC == nil) {
[RNBootSplash removeBootsplashName:task.bootsplashName];
if ([self hasBootsplashToDisplay]) {
task.resolve(@(true)); // splash screen should still be displayed for some BootsplashNames
[self shiftNextTask];
} else if (_splashVC == nil) {
task.resolve(@(true)); // splash screen is already hidden
[self shiftNextTask];
} else {
Expand All @@ -171,6 +196,7 @@ + (void)applicationWillResignActive
{
RNBootSplashTask *task = [[RNBootSplashTask alloc] initWithType:RNBootSplashTaskTypeShow
fade:false
bootsplashName:@"secure_background"
resolver:^(NSString *one) {}
rejecter:^(NSString *one, NSString *two, NSError *three) {}];

Expand All @@ -180,13 +206,15 @@ + (void)applicationWillResignActive

RCT_REMAP_METHOD(show,
showWithFade:(BOOL)fade
showWithName:(NSString *)bootsplashName
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
if (_rootView == nil)
return reject(@"uninitialized_module", @"react-native-bootsplash has not been initialized", nil);

RNBootSplashTask *task = [[RNBootSplashTask alloc] initWithType:RNBootSplashTaskTypeShow
fade:fade
bootsplashName:bootsplashName
resolver:resolve
rejecter:reject];

Expand All @@ -196,13 +224,15 @@ + (void)applicationWillResignActive

RCT_REMAP_METHOD(hide,
hideWithFade:(BOOL)fade
hideWithName:(NSString *)bootsplashName
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
if (_rootView == nil)
return reject(@"uninitialized_module", @"react-native-bootsplash has not been initialized", nil);

RNBootSplashTask *task = [[RNBootSplashTask alloc] initWithType:RNBootSplashTaskTypeHide
fade:fade
bootsplashName:bootsplashName
resolver:resolve
rejecter:reject];

Expand Down
16 changes: 11 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import { NativeModules } from "react-native";

export type VisibilityStatus = "visible" | "hidden" | "transitioning";
export type Config = { fade?: boolean };
export type Config = { fade?: boolean; bootsplashName?: string };

const NativeModule: {
show: (fade: boolean) => Promise<true>;
hide: (fade: boolean) => Promise<true>;
show: (fade: boolean, bootsplashName: string) => Promise<true>;
hide: (fade: boolean, bootsplashName: string) => Promise<true>;
getVisibilityStatus: () => Promise<VisibilityStatus>;
} = NativeModules.RNBootSplash;

export function show(config: Config = {}): Promise<void> {
return NativeModule.show({ fade: false, ...config }.fade).then(() => {});
return NativeModule.show(
{ fade: false, ...config }.fade,
config.bootsplashName ?? "global",
).then(() => {});
}

export function hide(config: Config = {}): Promise<void> {
return NativeModule.hide({ fade: false, ...config }.fade).then(() => {});
return NativeModule.hide(
{ fade: false, ...config }.fade,
config.bootsplashName ?? "global",
).then(() => {});
}

export function getVisibilityStatus(): Promise<VisibilityStatus> {
Expand Down

0 comments on commit c17337c

Please sign in to comment.