-
-
Notifications
You must be signed in to change notification settings - Fork 655
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
android: Receive and handle shares from other apps.
Enables handling and receiving shares from other apps, for any type of content (text, images, and other files). Creates a native RN module `SharingModule` to handle the Android part of this feature. 'ReceiveShareActivity.kt' parses the received shared data and handles activating the JS part of the codebase - either by sending an event to an already-running app, or launching `MainActivity` with some initial shared data via the native module. Registers a new root component, `SharingRoot`. It is a dummy component that does nothing, and `ReceiveShareActivity.kt`. It exists because 'Sharing' is linked with launching an activity in the Android ecosystem. But we don't always want to launch `MainActivity` when receiving a share because it may cause two instances of it to be opened simultaneously. We can't check whether the app is running before an activity launches. So, we launch this dummy component, then process the events, identify whether the app is running or not, and handle that as mentioned in the paragraph above, and then quickly kill this dummy Activity. All of this happens fast enough that the component does not even get time to render, so it's seamless. Closes #M117.
- Loading branch information
Showing
16 changed files
with
817 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
108 changes: 108 additions & 0 deletions
108
android/app/src/main/java/com/zulipmobile/sharing/ReceiveShareActivity.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package com.zulipmobile.sharing | ||
|
||
import android.content.Context | ||
import android.content.Intent | ||
import android.content.res.Configuration | ||
import android.net.Uri | ||
import android.os.Bundle | ||
import android.util.Log | ||
import android.webkit.WebView | ||
import androidx.annotation.Nullable | ||
import com.facebook.react.ReactActivity | ||
import com.facebook.react.ReactApplication | ||
import com.facebook.react.bridge.ReactContext | ||
import com.facebook.react.bridge.WritableMap | ||
import com.facebook.react.bridge.Arguments | ||
import com.zulipmobile.notifications.* | ||
|
||
const val TAG = "ZulipReceiveShare" | ||
|
||
class ReceiveShareActivity : ReactActivity() { | ||
|
||
/** | ||
* Returns the name of the main component registered from JavaScript. | ||
* This is used to schedule rendering of the component. | ||
*/ | ||
override fun getMainComponentName(): String? { | ||
return "SharingRoot" | ||
} | ||
|
||
private fun sendEvent(reactContext: ReactContext, | ||
eventName: String, | ||
@Nullable params: WritableMap) { | ||
Log.d(TAG, "Sending event with shared data") | ||
emit(reactContext, eventName, params) | ||
} | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
WebView.setWebContentsDebuggingEnabled(true) | ||
if (intent?.action == Intent.ACTION_SEND) { | ||
handleSend(intent) | ||
} | ||
finish() | ||
} | ||
|
||
private fun handleSend(intent: Intent) { | ||
val application = application as ReactApplication | ||
val host = application.reactNativeHost | ||
val reactContext = host.tryGetReactInstanceManager()?.currentReactContext | ||
val params: WritableMap | ||
try { | ||
params = getParamsFromIntent(intent) | ||
} catch (e: ShareParamsParseException) { | ||
Log.w(TAG, "Ignoring malformed share Intent: ${e.message}") | ||
return | ||
} | ||
|
||
val appStatus = reactContext?.appStatus | ||
when (appStatus) { | ||
null, ReactAppStatus.NOT_RUNNING -> | ||
// Either there's no JS environment running, or we haven't yet reached | ||
// foreground. Expect the app to check initialSharedData on launch. | ||
SharingModule.initialSharedData = params | ||
ReactAppStatus.BACKGROUND, ReactAppStatus.FOREGROUND -> | ||
// JS is running and has already reached foreground.It won't check | ||
// initialSharedData again, but it will see a shareReceived event. | ||
sendEvent(reactContext, "shareReceived", params) | ||
} | ||
when (appStatus) { | ||
null, ReactAppStatus.NOT_RUNNING, ReactAppStatus.BACKGROUND -> | ||
launchMainActivity(application as Context) | ||
ReactAppStatus.FOREGROUND -> Unit | ||
} | ||
} | ||
|
||
private fun getParamsFromIntent(intent: Intent): WritableMap { | ||
val params = Arguments.createMap() | ||
when { | ||
"text/plain" == intent.type -> { | ||
val sharedText = intent.getStringExtra(Intent.EXTRA_TEXT) | ||
params.putString("type", "text") | ||
params.putString("sharedText", sharedText) | ||
} | ||
intent.type?.startsWith("image/") == true -> { | ||
val url = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM) | ||
?: throw ShareParamsParseException("Could not extract URL from Image Intent") | ||
params.putString("type", "image") | ||
params.putString("sharedImageUrl", url.toString()) | ||
} | ||
else -> { | ||
val url = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM) | ||
?: throw ShareParamsParseException("Could not extract URL from File Intent") | ||
params.putString("type", "file") | ||
params.putString("sharedFileUrl", url.toString()) | ||
} | ||
} | ||
return params | ||
} | ||
|
||
override fun onConfigurationChanged(newConfig: Configuration) { | ||
super.onConfigurationChanged(newConfig) | ||
val intent = Intent("onConfigurationChanged") | ||
intent.putExtra("newConfig", newConfig) | ||
this.sendBroadcast(intent) | ||
} | ||
} | ||
|
||
class ShareParamsParseException(errorMessage: String) : RuntimeException(errorMessage) |
24 changes: 24 additions & 0 deletions
24
android/app/src/main/java/com/zulipmobile/sharing/SharingModule.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package com.zulipmobile.sharing | ||
|
||
import com.facebook.react.bridge.* | ||
|
||
internal class SharingModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { | ||
|
||
override fun getName(): String { | ||
return "Sharing" | ||
} | ||
|
||
@ReactMethod | ||
fun getInitialSharedContent(promise: Promise) { | ||
if (null == initialSharedData) { | ||
promise.resolve(null) | ||
} else { | ||
promise.resolve(initialSharedData) | ||
} | ||
|
||
} | ||
|
||
companion object { | ||
var initialSharedData: WritableMap? = null | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
android/app/src/main/java/com/zulipmobile/sharing/SharingPackage.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package com.zulipmobile.sharing | ||
|
||
|
||
import com.facebook.react.ReactPackage | ||
import com.facebook.react.bridge.NativeModule | ||
import com.facebook.react.bridge.ReactApplicationContext | ||
import com.facebook.react.uimanager.ViewManager | ||
|
||
import java.util.ArrayList | ||
|
||
class SharingPackage : ReactPackage { | ||
|
||
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> { | ||
return emptyList() | ||
} | ||
|
||
override fun createNativeModules( | ||
reactContext: ReactApplicationContext): List<NativeModule> { | ||
val modules = ArrayList<NativeModule>() | ||
|
||
modules.add(SharingModule(reactContext)) | ||
|
||
return modules | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
/* @flow strict-local */ | ||
import { AppRegistry } from 'react-native'; | ||
import ZulipMobile from './src/ZulipMobile'; | ||
import SharingRoot from './src/sharing/SharingRoot'; | ||
|
||
AppRegistry.registerComponent('ZulipMobile', () => ZulipMobile); | ||
AppRegistry.registerComponent('SharingRoot', () => SharingRoot); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/* @flow strict-local */ | ||
import React, { PureComponent } from 'react'; | ||
import type { User, Dispatch } from '../types'; | ||
import { connect } from '../react-redux'; | ||
import { Screen } from '../common'; | ||
import UserPickerCard from '../user-picker/UserPickerCard'; | ||
|
||
type Props = $ReadOnly<{| | ||
dispatch: Dispatch, | ||
onComplete: (User[]) => void, | ||
|}>; | ||
|
||
type State = {| | ||
filter: string, | ||
|}; | ||
|
||
class ChooseRecipientsScreen extends PureComponent<Props, State> { | ||
state = { | ||
filter: '', | ||
}; | ||
|
||
handleFilterChange = (filter: string) => this.setState({ filter }); | ||
|
||
handleComplete = (selected: Array<User>) => { | ||
const { onComplete } = this.props; | ||
onComplete(selected); | ||
}; | ||
|
||
render() { | ||
const { filter } = this.state; | ||
return ( | ||
<Screen | ||
search | ||
scrollEnabled={false} | ||
searchBarOnChange={this.handleFilterChange} | ||
canGoBack={false} | ||
> | ||
<UserPickerCard filter={filter} onComplete={this.handleComplete} /> | ||
</Screen> | ||
); | ||
} | ||
} | ||
|
||
export default connect<{||}, _, _>()(ChooseRecipientsScreen); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
/* @flow strict-local */ | ||
import React from 'react'; | ||
import { View } from 'react-native'; | ||
|
||
/** | ||
* This is a dummy component to by-pass some weird quirks of Android Activity | ||
* launches in a React Native context. The native code in | ||
* `ReceiveShareActivity.kt` finishes this activity quickly, after either | ||
* i) Sending events to an already open app in the background | ||
* ii) Launching `MainActivity` with some initial share data. | ||
*/ | ||
class SharingRoot extends React.Component<{||}> { | ||
render() { | ||
return <View />; | ||
} | ||
} | ||
|
||
export default SharingRoot; |
Oops, something went wrong.