Skip to content

Commit

Permalink
Merge pull request #40 from bananaappsuk/master
Browse files Browse the repository at this point in the history
Resolves many open issues and adds additional support
  • Loading branch information
VNAPNIC authored Dec 15, 2023
2 parents e1e97b6 + ea41be1 commit 0437b4b
Show file tree
Hide file tree
Showing 29 changed files with 684 additions and 409 deletions.
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,26 @@ Widget build (BuildContext context) {
);
}
```
- wesetup new entry point for secondary display so we can decided what we need to call for initialization. Works only for android for now
```dart
@pragma('vm:entry-point')
void secondaryDisplayMain() {
/// do something that don't break plugin registration here.
}
```
- Able to package android release build. Works fine in example app.

- Tested example app in android tab and ios tab and things work as expected. Ensure the devices have USB C 3.0 and above else HDMI out is not supported.

- In case of iOS, please refer to example app app delegate. There are few lines of code which needs to be added to your app's app delegate as well for this to work fine in iOS.

- Updated optional issues and null checks

- Added option to hide second display from the first

- WIP support second main in iOS for extended display

- WIP Send data back from 2nd to 1st display

You can take a look at our example to learn more about how the plugin works

Expand Down
14 changes: 8 additions & 6 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,36 @@ group 'com.namit.presentation_displays'
version '1.0-SNAPSHOT'

buildscript {
ext.kotlin_version = '1.5.20'
ext.kotlin_version = '1.7.20'
repositories {
google()
jcenter()
mavenCentral()
}

dependencies {
classpath 'com.android.tools.build:gradle:4.1.3'
classpath 'com.android.tools.build:gradle:7.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

rootProject.allprojects {
repositories {
google()
mavenCentral()
}
}

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

android {
compileSdkVersion 31
compileSdkVersion 33

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
minSdkVersion 19
minSdkVersion 21
}
lintOptions {
disable 'InvalidPackage'
Expand All @@ -39,5 +40,6 @@ android {

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.android.support:support-annotations:28.0.0'
}
1 change: 0 additions & 1 deletion android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true
2 changes: 1 addition & 1 deletion android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ data class DisplayJson(
val rotation: Int,
@SerializedName("name")
val name: String
)
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@ package com.namit.presentation_displays
import android.content.ContentValues.TAG
import android.content.Context
import android.hardware.display.DisplayManager
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.Display
import androidx.annotation.NonNull
import androidx.core.content.ContextCompat.getSystemService
import com.google.gson.Gson
import io.flutter.FlutterInjector
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.embedding.engine.dart.DartExecutor
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.PluginRegistry
Expand All @@ -22,121 +25,176 @@ import org.json.JSONObject
/** PresentationDisplaysPlugin */
class PresentationDisplaysPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCallHandler {

private lateinit var channel: MethodChannel
private var flutterEngineChannel: MethodChannel? = null
private var displayManager: DisplayManager? = null
private var context: Context? = null
private lateinit var channel: MethodChannel
private lateinit var eventChannel: EventChannel
private var flutterEngineChannel: MethodChannel? = null
private var context: Context? = null
private var presentation: PresentationDisplay? = null

override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.flutterEngine.dartExecutor, viewTypeId)
channel.setMethodCallHandler(this)
}
override fun onAttachedToEngine(
@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, viewTypeId)
channel.setMethodCallHandler(this)

companion object {
private const val viewTypeId = "presentation_displays_plugin"
eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, viewTypeEventsId)
displayManager =
flutterPluginBinding.applicationContext.getSystemService(Context.DISPLAY_SERVICE) as
DisplayManager
val displayConnectedStreamHandler = DisplayConnectedStreamHandler(displayManager)
eventChannel.setStreamHandler(displayConnectedStreamHandler)
}

@JvmStatic
fun registerWith(registrar: PluginRegistry.Registrar) {
val channel = MethodChannel(registrar.messenger(), viewTypeId)
channel.setMethodCallHandler(PresentationDisplaysPlugin())
}
}
companion object {
private const val viewTypeId = "presentation_displays_plugin"
private const val viewTypeEventsId = "presentation_displays_plugin_events"
private var displayManager: DisplayManager? = null

/** @hide */
@Suppress("unused", "DEPRECATION")
@JvmStatic
fun registerWith(registrar: PluginRegistry.Registrar) {
val channel = MethodChannel(registrar.messenger(), viewTypeId)
channel.setMethodCallHandler(PresentationDisplaysPlugin())

override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
val eventChannel = EventChannel(registrar.messenger(), viewTypeEventsId)
displayManager =
registrar.activity()!!.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val displayConnectedStreamHandler = DisplayConnectedStreamHandler(displayManager)
eventChannel.setStreamHandler(displayConnectedStreamHandler)
}
}

override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
Log.i(TAG, "Channel: method: ${call.method} | arguments: ${call.arguments}")
when (call.method) {
"showPresentation" -> {
try {
val obj = JSONObject(call.arguments as String)
Log.i(
TAG,
"Channel: method: ${call.method} | displayId: ${obj.getInt("displayId")} | routerName: ${
obj.getString("routerName")
}"
)
val displayId: Int = obj.getInt("displayId")
val tag: String = obj.getString("routerName")
val display = displayManager?.getDisplay(displayId)
if (display != null) {
val flutterEngine = createFlutterEngine(tag)
flutterEngine?.let {
flutterEngineChannel = MethodChannel(
it.dartExecutor.binaryMessenger,
"${viewTypeId}_engine"
)
val presentation =
context?.let { it1 -> PresentationDisplay(it1, tag, display) }
Log.i(TAG, "presentation: $presentation")
presentation?.show()
result.success(true)
} ?: result.error("404", "Can't find FlutterEngine", null)
} else {
result.error("404", "Can't find display with displayId is $displayId", null)
}
} catch (e: Exception) {
result.error(call.method, e.message, null)
}
}
"listDisplay" -> {
val listJson = ArrayList<DisplayJson>()
val category = call.arguments
val displays = displayManager?.getDisplays(category as String?)
if (displays != null) {
for (display: Display in displays) {
Log.i(TAG, "display: $display")
val d = DisplayJson(
display.displayId,
display.flags,
display.rotation,
display.name
)
listJson.add(d)
}
}
result.success(Gson().toJson(listJson))
}
"transferDataToPresentation" -> {
try {
flutterEngineChannel?.invokeMethod("DataTransfer", call.arguments)
result.success(true)
} catch (e: Exception) {
result.success(false)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
eventChannel.setStreamHandler(null)
}

override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
Log.i(TAG, "Channel: method: ${call.method} | arguments: ${call.arguments}")
when (call.method) {
"showPresentation" -> {
try {
val obj = JSONObject(call.arguments as String)
Log.i(
TAG,
"Channel: method: ${call.method} | displayId: ${obj.getInt("displayId")} | routerName: ${
obj.getString("routerName")
}"
)
val displayId: Int = obj.getInt("displayId")
val tag: String = obj.getString("routerName")
val display = displayManager?.getDisplay(displayId)
if (display != null) {
val flutterEngine = createFlutterEngine(tag)
flutterEngine?.let {
flutterEngineChannel =
MethodChannel(it.dartExecutor.binaryMessenger, "${viewTypeId}_engine")
presentation = context?.let { it1 -> PresentationDisplay(it1, tag, display) }
Log.i(TAG, "presentation: $presentation")
presentation?.show()

result.success(true)
}
?: result.error("404", "Can't find FlutterEngine", null)
} else {
result.error("404", "Can't find display with displayId is $displayId", null)
}
} catch (e: Exception) {
result.error(call.method, e.message, null)
}
}
}
"hidePresentation" -> {
try {
val obj = JSONObject(call.arguments as String)
Log.i(TAG, "Channel: method: ${call.method} | displayId: ${obj.getInt("displayId")}")

private fun createFlutterEngine(tag: String): FlutterEngine? {
if (context == null)
return null
if (FlutterEngineCache.getInstance().get(tag) == null) {
val flutterEngine = FlutterEngine(context!!)
flutterEngine.navigationChannel.setInitialRoute(tag)
flutterEngine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)
flutterEngine.lifecycleChannel.appIsResumed()
// Cache the FlutterEngine to be used by FlutterActivity.
FlutterEngineCache.getInstance().put(tag, flutterEngine)
presentation?.dismiss()
presentation = null
result.success(true)
} catch (e: Exception) {
result.error(call.method, e.message, null)
}
}
"listDisplay" -> {
val listJson = ArrayList<DisplayJson>()
val category = call.arguments
val displays = displayManager?.getDisplays(category as String?)
if (displays != null) {
for (display: Display in displays) {
Log.i(TAG, "display: $display")
val d = DisplayJson(display.displayId, display.flags, display.rotation, display.name)
listJson.add(d)
}
}
result.success(Gson().toJson(listJson))
}
"transferDataToPresentation" -> {
try {
flutterEngineChannel?.invokeMethod("DataTransfer", call.arguments)
result.success(true)
} catch (e: Exception) {
result.success(false)
}
return FlutterEngineCache.getInstance().get(tag)
}
}
}

override fun onDetachedFromActivity() {
private fun createFlutterEngine(tag: String): FlutterEngine? {
if (context == null) return null
if (FlutterEngineCache.getInstance().get(tag) == null) {
val flutterEngine = FlutterEngine(context!!)
flutterEngine.navigationChannel.setInitialRoute(tag)
FlutterInjector.instance().flutterLoader().startInitialization(context!!)
val path = FlutterInjector.instance().flutterLoader().findAppBundlePath()
val entrypoint = DartExecutor.DartEntrypoint(path, "secondaryDisplayMain")
flutterEngine.dartExecutor.executeDartEntrypoint(entrypoint)
flutterEngine.lifecycleChannel.appIsResumed()
// Cache the FlutterEngine to be used by FlutterActivity.
FlutterEngineCache.getInstance().put(tag, flutterEngine)
}
return FlutterEngineCache.getInstance().get(tag)
}

override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
}
override fun onDetachedFromActivity() {}

override fun onAttachedToActivity(binding: ActivityPluginBinding) {
this.context = binding.activity
this.displayManager = context?.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {}

override fun onDetachedFromActivityForConfigChanges() {
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
this.context = binding.activity
displayManager = context?.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
}

override fun onDetachedFromActivityForConfigChanges() {}
}

class DisplayConnectedStreamHandler(private var displayManager: DisplayManager?) :
EventChannel.StreamHandler {
private var sink: EventChannel.EventSink? = null
private var handler: Handler? = null

private val displayListener =
object : DisplayManager.DisplayListener {
override fun onDisplayAdded(displayId: Int) {
sink?.success(1)
}

override fun onDisplayRemoved(displayId: Int) {
sink?.success(0)
}

override fun onDisplayChanged(p0: Int) {}
}

override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
sink = events
handler = Handler(Looper.getMainLooper())
displayManager?.registerDisplayListener(displayListener, handler)
}

override fun onCancel(arguments: Any?) {
sink = null
handler = null
displayManager?.unregisterDisplayListener(displayListener)
}
}
Loading

0 comments on commit 0437b4b

Please sign in to comment.