Files
react-native/Libraries/Share/Share.js
T
McCoy Zhu aeab38357f feat(ios): Share with anchor (#35008)
Summary:
[`Share`](https://reactnative.dev/docs/share) currently does not support the `anchor` option in iOS, so share sheets will always be displayed in the middle of the screen on iPads and on the top left corner of the window on Mac Catalyst.

This PR utilizes the `anchor` functionality already implemented in [`ActionSheetIOS`](https://reactnative.dev/docs/actionsheetios) to bring this support to `Share` on iOS.

## Changelog

[iOS] [Changed] - type definition for the `options` parameter of `Share.share` (added optional `anchor` property)

[iOS] [Added] - `anchor` option support for `Share`

Pull Request resolved: https://github.com/facebook/react-native/pull/35008

Test Plan:
Tested with modified `rn-tester` that utilizes the `anchor` option on iPad simulator. Marked all 3 changes in code.

![Simulator Screen Shot - iPad Pro (11-inch) (3rd generation) - 2022-10-17 at 15 44 23](https://user-images.githubusercontent.com/31050761/196271991-469cac23-ef2b-4be5-aee2-b4197936007e.png)

```js
const SharedAction = () => {
  const [shared, setShared] = React.useState();
  const ref = React.useRef(); /* create ref (1/3) */

  const sharedAction = async () => {
    try {
      const result = await Share.share(
        {
          title: 'Create native apps',
          message:
            ('React Native combines the best parts of native development with React, a best-in-class JavaScript library for building user interfaces.': string),
          url: 'https://reactnative.dev/',
        },
        {
          subject: 'MUST READ: Create native apps with React Native',
          dialogTitle: 'Share React Native Home Page',
          tintColor: 'blue',
          anchor: ref.current?._nativeTag, /* add anchor in options (2/3) */
        },
      );
      if (result.action === Share.sharedAction) {
        setShared(result.action);
      } else if (result.action === Share.dismissedAction) {
        //iOS only, if dialog was dismissed
        setShared(null);
      }
    } catch (e) {
      console.error(e);
    }
  };
  return (
    <View style={styles.container}>
      <Text>action: {shared ? shared : 'null'}</Text>
      <Text style={styles.title}>Create native apps</Text>
      <Text>
        React Native combines the best parts of native development with React, a
        best-in-class JavaScript library for building user interfaces.
      </Text>
      {/* supply ref to Node (3/3) */}
      <Text ref={ref} style={styles.button} onPress={sharedAction}>
        SHARE
      </Text>
    </View>
  );
};
```

Reviewed By: cipolleschi

Differential Revision: D40459336

Pulled By: skinsshark

fbshipit-source-id: 72fbb3905ea0b982bb7f4b99967d121cd482181a
2022-10-18 18:56:19 -07:00

173 lines
4.5 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
*/
import NativeActionSheetManager from '../ActionSheetIOS/NativeActionSheetManager';
import NativeShareModule from './NativeShareModule';
const processColor = require('../StyleSheet/processColor');
const Platform = require('../Utilities/Platform');
const invariant = require('invariant');
type Content =
| {
title?: string,
message: string,
...
}
| {
title?: string,
url: string,
...
};
type Options = {
dialogTitle?: string,
excludedActivityTypes?: Array<string>,
tintColor?: string,
subject?: string,
anchor?: number,
...
};
class Share {
/**
* Open a dialog to share text content.
*
* In iOS, Returns a Promise which will be invoked an object containing `action`, `activityType`.
* If the user dismissed the dialog, the Promise will still be resolved with action being `Share.dismissedAction`
* and all the other keys being undefined.
*
* In Android, Returns a Promise which always be resolved with action being `Share.sharedAction`.
*
* ### Content
*
* - `message` - a message to share
*
* #### iOS
*
* - `url` - a URL to share
*
* At least one of URL and message is required.
*
* #### Android
*
* - `title` - title of the message
*
* ### Options
*
* #### iOS
*
* - `subject` - a subject to share via email
* - `excludedActivityTypes`
* - `tintColor`
*
* #### Android
*
* - `dialogTitle`
*
*/
static share(
content: Content,
options: Options = {},
): Promise<{action: string, activityType: ?string}> {
invariant(
typeof content === 'object' && content !== null,
'Content to share must be a valid object',
);
invariant(
typeof content.url === 'string' || typeof content.message === 'string',
'At least one of URL and message is required',
);
invariant(
typeof options === 'object' && options !== null,
'Options must be a valid object',
);
if (Platform.OS === 'android') {
invariant(
NativeShareModule,
'ShareModule should be registered on Android.',
);
invariant(
content.title == null || typeof content.title === 'string',
'Invalid title: title should be a string.',
);
const newContent = {
title: content.title,
message:
typeof content.message === 'string' ? content.message : undefined,
};
return NativeShareModule.share(newContent, options.dialogTitle).then(
result => ({
activityType: null,
...result,
}),
);
} else if (Platform.OS === 'ios') {
return new Promise((resolve, reject) => {
const tintColor = processColor(options.tintColor);
invariant(
tintColor == null || typeof tintColor === 'number',
'Unexpected color given for options.tintColor',
);
invariant(
NativeActionSheetManager,
'NativeActionSheetManager is not registered on iOS, but it should be.',
);
NativeActionSheetManager.showShareActionSheetWithOptions(
{
message:
typeof content.message === 'string' ? content.message : undefined,
url: typeof content.url === 'string' ? content.url : undefined,
subject: options.subject,
tintColor: typeof tintColor === 'number' ? tintColor : undefined,
anchor:
typeof options.anchor === 'number' ? options.anchor : undefined,
excludedActivityTypes: options.excludedActivityTypes,
},
error => reject(error),
(success, activityType) => {
if (success) {
resolve({
action: 'sharedAction',
activityType: activityType,
});
} else {
resolve({
action: 'dismissedAction',
activityType: null,
});
}
},
);
});
} else {
return Promise.reject(new Error('Unsupported platform'));
}
}
/**
* The content was successfully shared.
*/
static sharedAction: 'sharedAction' = 'sharedAction';
/**
* The dialog has been dismissed.
* @platform ios
*/
static dismissedAction: 'dismissedAction' = 'dismissedAction';
}
module.exports = Share;