From eca8a9220e2f840ec89e59903bb191557774f6ba Mon Sep 17 00:00:00 2001 From: elrizwiraswara Date: Thu, 12 Sep 2024 19:52:15 +0700 Subject: [PATCH] Initial release --- .gitignore | 9 + CHANGELOG.md | 19 ++ LICENSE | 13 + README.md | 107 ++++++ analysis_options.yaml | 29 ++ android/.gitignore | 13 + android/build.gradle | 48 +++ android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + android/settings.gradle | 1 + android/src/main/AndroidManifest.xml | 3 + .../DisplayModel.kt | 16 + .../FlutterPresentationDisplayPlugin.kt | 194 +++++++++++ .../PresentationDisplay.kt | 60 ++++ example/.gitignore | 43 +++ example/README.md | 16 + example/analysis_options.yaml | 29 ++ example/android/.gitignore | 7 + example/android/app/build.gradle | 67 ++++ example/android/app/proguard-rules.pro | 1 + .../android/app/src/debug/AndroidManifest.xml | 6 + .../android/app/src/main/AndroidManifest.xml | 47 +++ .../com/example/example/MainActivity.kt | 6 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 ++ .../app/src/main/res/values/styles.xml | 18 ++ .../app/src/profile/AndroidManifest.xml | 6 + example/android/build.gradle | 31 ++ example/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 6 + example/android/settings.gradle | 15 + example/lib/main.dart | 35 ++ example/lib/main_screen.dart | 304 ++++++++++++++++++ example/lib/presentation_screen.dart | 94 ++++++ example/lib/route.dart | 18 ++ example/pubspec.lock | 220 +++++++++++++ example/pubspec.yaml | 73 +++++ lib/display.dart | 34 ++ lib/flutter_presentation_display.dart | 130 ++++++++ pubspec.lock | 205 ++++++++++++ pubspec.yaml | 34 ++ test/flutter_presentation_display_test.dart | 1 + 48 files changed, 2011 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 analysis_options.yaml create mode 100644 android/.gitignore create mode 100644 android/build.gradle create mode 100644 android/gradle.properties create mode 100644 android/gradle/wrapper/gradle-wrapper.properties create mode 100644 android/settings.gradle create mode 100644 android/src/main/AndroidManifest.xml create mode 100644 android/src/main/kotlin/com/elriztechnology/flutter_presentation_display/DisplayModel.kt create mode 100644 android/src/main/kotlin/com/elriztechnology/flutter_presentation_display/FlutterPresentationDisplayPlugin.kt create mode 100644 android/src/main/kotlin/com/elriztechnology/flutter_presentation_display/PresentationDisplay.kt create mode 100644 example/.gitignore create mode 100644 example/README.md create mode 100644 example/analysis_options.yaml create mode 100644 example/android/.gitignore create mode 100644 example/android/app/build.gradle create mode 100644 example/android/app/proguard-rules.pro create mode 100644 example/android/app/src/debug/AndroidManifest.xml create mode 100644 example/android/app/src/main/AndroidManifest.xml create mode 100644 example/android/app/src/main/kotlin/com/example/example/MainActivity.kt create mode 100644 example/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 example/android/app/src/main/res/drawable/launch_background.xml create mode 100644 example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 example/android/app/src/main/res/values-night/styles.xml create mode 100644 example/android/app/src/main/res/values/styles.xml create mode 100644 example/android/app/src/profile/AndroidManifest.xml create mode 100644 example/android/build.gradle create mode 100644 example/android/gradle.properties create mode 100644 example/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 example/android/settings.gradle create mode 100644 example/lib/main.dart create mode 100644 example/lib/main_screen.dart create mode 100644 example/lib/presentation_screen.dart create mode 100644 example/lib/route.dart create mode 100644 example/pubspec.lock create mode 100644 example/pubspec.yaml create mode 100644 lib/display.dart create mode 100644 lib/flutter_presentation_display.dart create mode 100644 pubspec.lock create mode 100644 pubspec.yaml create mode 100644 test/flutter_presentation_display_test.dart diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0cb8159 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +build/ + +.idea/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5ef698c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,19 @@ +## 2.0.4 + +* Improved pub scores. + +## 2.0.3 + +* Updated README.md. + +## 2.0.2 + +* Updated README.md. + +## 2.0.1 + +* Improved pub scores. + +## 2.0.0 + +* Initial release. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a6fb5e7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2020, Nankai +All rights reserved. + +Copyright (c) 2024, Elriz Technology +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions, and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4185ede --- /dev/null +++ b/README.md @@ -0,0 +1,107 @@ +# Flutter Presentation Display + +`Flutter Presentation Display` is a Flutter plugin designed to run on multiple displays, including handling secondary (presentation) display. It provides methods to interact with connected displays, transfer data, and respond to display connection changes. Tested on SUNMI T2s. + +## Features + +- Retrieve a list of connected displays +- Show and hide secondary (presentation) display +- Transfer data from the main display to the secondary display or vice versa +- Listen to display connection changes + +## Installation + +Add `flutter_presentation_display` to your `pubspec.yaml` file: + +```yaml +dependencies: + flutter: + sdk: flutter + flutter_presentation_display: any # Replace with the latest version +``` + +## Usage +### Import the Package +``` +import 'package:flutter_presentation_display/flutter_presentation_display.dart'; +``` + +### Initialize FlutterPresentationDisplay +Create an instance of FlutterPresentationDisplay: +``` +final display = FlutterPresentationDisplay(); +``` + +### Retrieve Displays +Get a list of connected displays: +``` +List? displays = await display.getDisplays(); +``` +Get Display Name by ID +Retrieve the name of a display using its ID: +``` +String? displayName = await display.getNameByDisplayId(1); +``` +Get Display Name by Index +Retrieve the name of a display using its index in the list: +``` +String? displayName = await display.getNameByIndex(0); +``` + +### Show and Hide Secondary Display +Show a secondary display with a specific ID and router name: +``` +bool? result = await display.showSecondaryDisplay( + displayId: 1, + routerName: "presentation", +); +``` +Hide a secondary display using its ID: +``` +bool? result = await display.hideSecondaryDisplay(displayId: 1); +``` + +### Transfer Data to and from Displays +Transfer data to the secondary (presentation) display: +``` +bool? result = await display.transferDataToPresentation({"key": "value"}); +``` +Transfer data to the main display: +``` +bool? result = await display.transferDataToMain({"key": "value"}); +``` + +### Listen to Display Connection Changes +Listen to changes in connected displays: +``` +display.connectedDisplaysChangedStream.listen((int? displayId) { + print('Connected display ID: $displayId'); +}); +``` + +### Listen for Data from Displays +Listen for data sent from the secondary (presentation) display: +``` +display.listenDataFromPresentationDisplay((dynamic data) { + print('Data from Presentation Display: $data'); +}); +``` +Listen for data sent from the main display: +``` +display.listenDataFromMainDisplay((dynamic data) { + print('Data from Main Display: $data'); +}); +``` + + +## Example +Check out the [example](example) directory for a complete sample app demonstrating the use of the `flutter_presentation_display` package. + +## License +This package is based on [presentation_displays](https://github.com/VNAPNIC/presentation-displays) which is licensed under the BSD 2-Clause License.
+[flutter_presentation_display](https://github.com/elrizwiraswara/flutter_presentation_display) is a modified version of the original version. + + +## Support + +Trakteer Saya diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..1411db2 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +# include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..2487fb7 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,48 @@ +group 'com.elriztechnology.flutter_presentation_display' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.7.20' + repositories { + google() + mavenCentral() + } + + dependencies { + 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 34 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + minSdkVersion 21 + } + + lintOptions { + disable 'InvalidPackage' + } +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'com.google.code.gson:gson:2.10.1' + implementation 'com.android.support:support-annotations:28.0.0' + // implementation files('D:\\Softwares\\Developments\\flutter\\bin\\cache\\artifacts\\engine\\android-x64\\flutter.jar') +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c472b9 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..9498ddc --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'flutter_presentation_display' diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..caeda9b --- /dev/null +++ b/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/android/src/main/kotlin/com/elriztechnology/flutter_presentation_display/DisplayModel.kt b/android/src/main/kotlin/com/elriztechnology/flutter_presentation_display/DisplayModel.kt new file mode 100644 index 0000000..4615d8a --- /dev/null +++ b/android/src/main/kotlin/com/elriztechnology/flutter_presentation_display/DisplayModel.kt @@ -0,0 +1,16 @@ +package com.elriztechnology.flutter_presentation_display + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class DisplayModel( + @SerializedName("displayId") + val displayId: Int, + @SerializedName("flags") + val flags: Int, + @SerializedName("rotation") + val rotation: Int, + @SerializedName("name") + val name: String +) \ No newline at end of file diff --git a/android/src/main/kotlin/com/elriztechnology/flutter_presentation_display/FlutterPresentationDisplayPlugin.kt b/android/src/main/kotlin/com/elriztechnology/flutter_presentation_display/FlutterPresentationDisplayPlugin.kt new file mode 100644 index 0000000..7bf2686 --- /dev/null +++ b/android/src/main/kotlin/com/elriztechnology/flutter_presentation_display/FlutterPresentationDisplayPlugin.kt @@ -0,0 +1,194 @@ +package com.elriztechnology.flutter_presentation_display + +import android.content.Context +import android.hardware.display.DisplayManager +import android.os.Handler +import android.os.Looper +import android.util.Log +import androidx.annotation.NonNull +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 +import org.json.JSONObject + +class FlutterPresentationDisplayPlugin : FlutterPlugin, ActivityAware, MethodChannel.MethodCallHandler { + + 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 + private var flutterBinding: FlutterPlugin.FlutterPluginBinding? = null + + override fun onAttachedToEngine( + @NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding + ) { + channel = MethodChannel(flutterPluginBinding.binaryMessenger, secondaryViewTypeId) + channel.setMethodCallHandler(this) + + eventChannel = EventChannel(flutterPluginBinding.binaryMessenger, viewTypeEventsId) + displayManager = flutterPluginBinding.applicationContext.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager + val displayConnectedStreamHandler = DisplayConnectedStreamHandler(displayManager) + eventChannel.setStreamHandler(displayConnectedStreamHandler) + flutterBinding = flutterPluginBinding + } + + companion object { + private const val viewTypeEventsId = "presentation_display_channel_events" + private const val secondaryViewTypeId = "presentation_display_channel" + private const val mainViewTypeId = "main_display_channel" + + private var displayManager: DisplayManager? = null + + @JvmStatic + fun registerWith(registrar: PluginRegistry.Registrar) { + val channel = MethodChannel(registrar.messenger(), secondaryViewTypeId) + channel.setMethodCallHandler(FlutterPresentationDisplayPlugin()) + + val eventChannel = EventChannel(registrar.messenger(), viewTypeEventsId) + displayManager = registrar.activity()?.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager + val displayConnectedStreamHandler = DisplayConnectedStreamHandler(displayManager) + eventChannel.setStreamHandler(displayConnectedStreamHandler) + } + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + eventChannel.setStreamHandler(null) + } + + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + when (call.method) { + "showPresentation" -> { + try { + val obj = JSONObject(call.arguments as String) + Log.i("Plugin", "Method: ${call.method}, Arguments: ${call.arguments}") + val displayId: Int = obj.getInt("displayId") + val tag: String = obj.getString("routerName") + val display = displayManager?.getDisplay(displayId) + if (display != null) { + val dataToMainCallback: (Any?) -> Unit = { argument -> + flutterBinding?.let { + MethodChannel(it.binaryMessenger, mainViewTypeId).invokeMethod("transferDataToMain", argument) + } + } + + val flutterEngine = createFlutterEngine(tag) + flutterEngine?.let { + flutterEngineChannel = MethodChannel(it.dartExecutor.binaryMessenger, secondaryViewTypeId) + presentation = context?.let { context -> + PresentationDisplay(context, tag, display, dataToMainCallback) + } + presentation?.show() + result.success(true) + } ?: run { + result.error("404", "Can't find FlutterEngine", null) + } + } else { + result.error("404", "Can't find display with displayId $displayId", null) + } + } catch (e: Exception) { + result.error(call.method, e.message, null) + } + } + + "hidePresentation" -> { + try { + presentation?.dismiss() + presentation = null + result.success(true) + } catch (e: Exception) { + result.error(call.method, e.message, null) + } + } + + "listDisplay" -> { + val displays = displayManager?.getDisplays(call.arguments as? String) + val listJson = displays?.map { display -> + DisplayModel(display.displayId, display.flags, display.rotation, display.name) + } + result.success(Gson().toJson(listJson)) + } + + "transferDataToPresentation" -> { + try { + flutterEngineChannel?.invokeMethod("transferDataToPresentation", call.arguments) + result.success(true) + } catch (e: Exception) { + result.error("Error transferring data", e.message, null) + } + } + + else -> result.notImplemented() + } + } + + private fun createFlutterEngine(tag: String): FlutterEngine? { + return context?.let { + var flutterEngine = FlutterEngineCache.getInstance().get(tag) + if (flutterEngine == null) { + flutterEngine = FlutterEngine(it) + flutterEngine.navigationChannel.setInitialRoute(tag) + FlutterInjector.instance().flutterLoader().startInitialization(it) + val path = FlutterInjector.instance().flutterLoader().findAppBundlePath() + val entrypoint = DartExecutor.DartEntrypoint(path, "secondaryDisplayMain") + flutterEngine.dartExecutor.executeDartEntrypoint(entrypoint) + flutterEngine.lifecycleChannel.appIsResumed() + FlutterEngineCache.getInstance().put(tag, flutterEngine) + } + flutterEngine + } + } + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + context = binding.activity + displayManager = context?.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager + } + + override fun onDetachedFromActivity() { + context = null + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + context = binding.activity + } + + override fun onDetachedFromActivityForConfigChanges() { + context = null + } +} + +class DisplayConnectedStreamHandler(private val displayManager: DisplayManager?) : EventChannel.StreamHandler { + + private var sink: EventChannel.EventSink? = 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(displayId: Int) {} + } + + override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { + sink = events + displayManager?.registerDisplayListener(displayListener, Handler(Looper.getMainLooper())) + } + + override fun onCancel(arguments: Any?) { + sink = null + displayManager?.unregisterDisplayListener(displayListener) + } +} diff --git a/android/src/main/kotlin/com/elriztechnology/flutter_presentation_display/PresentationDisplay.kt b/android/src/main/kotlin/com/elriztechnology/flutter_presentation_display/PresentationDisplay.kt new file mode 100644 index 0000000..d947056 --- /dev/null +++ b/android/src/main/kotlin/com/elriztechnology/flutter_presentation_display/PresentationDisplay.kt @@ -0,0 +1,60 @@ +package com.elriztechnology.flutter_presentation_display + +import android.app.Presentation +import android.content.Context +import android.os.Bundle +import android.util.Log +import android.view.Display +import android.view.ViewGroup +import android.widget.FrameLayout +import io.flutter.embedding.android.FlutterView +import io.flutter.embedding.engine.FlutterEngineCache +import io.flutter.plugin.common.MethodChannel + + +class PresentationDisplay( + context: Context, + private val tag: String, + display: Display, + private val callBack: (Any?) -> Unit +) : Presentation(context, display) { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // Create FrameLayout container + val flContainer = FrameLayout(context) + val params = FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + flContainer.layoutParams = params + + setContentView(flContainer) + + // Initialize FlutterView and attach it to the FrameLayout + val flutterView = FlutterView(context) + flContainer.addView(flutterView, params) + + // Retrieve FlutterEngine from cache + val flutterEngine = FlutterEngineCache.getInstance().get(tag) + if (flutterEngine != null) { + flutterView.attachToFlutterEngine(flutterEngine) + + // Set up MethodChannel communication + MethodChannel( + flutterEngine.dartExecutor.binaryMessenger, + "main_display_channel" + ).setMethodCallHandler { call, result -> + Log.i("PresentationDisplay", "Method: ${call.method}, Arguments: ${call.arguments}, Callback: $callBack") + if (call.method == "transferDataToMain") { + callBack(call.arguments) // Invoke the callback + } else { + result.notImplemented() + } + } + } else { + Log.e("PresentationDisplay", "Can't find the FlutterEngine with cache name $tag") + } + } +} diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000..1ba9c33 --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,43 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Exceptions to above rules. +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..980094a --- /dev/null +++ b/example/README.md @@ -0,0 +1,16 @@ +# flutter_presentation_display_example + +An example for presentation_displays plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/example/android/.gitignore b/example/android/.gitignore new file mode 100644 index 0000000..bc2100d --- /dev/null +++ b/example/android/.gitignore @@ -0,0 +1,7 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle new file mode 100644 index 0000000..ac985fe --- /dev/null +++ b/example/android/app/build.gradle @@ -0,0 +1,67 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new FileNotFoundException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 34 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.elriztechnology.flutter_presentation_display_example" + minSdkVersion 21 + targetSdkVersion 34 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + signingConfig signingConfigs.debug + shrinkResources false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + namespace 'com.elriztechnology.flutter_presentation_display_example' + lint { + disable 'InvalidPackage' + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/example/android/app/proguard-rules.pro b/example/android/app/proguard-rules.pro new file mode 100644 index 0000000..a6b8283 --- /dev/null +++ b/example/android/app/proguard-rules.pro @@ -0,0 +1 @@ +-keep class com.namit.** { *; } \ No newline at end of file diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..f880684 --- /dev/null +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ef1471b --- /dev/null +++ b/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + diff --git a/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt new file mode 100644 index 0000000..e793a00 --- /dev/null +++ b/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/example/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/drawable/launch_background.xml b/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..3db14bb --- /dev/null +++ b/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..1f83a33 --- /dev/null +++ b/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..f880684 --- /dev/null +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + diff --git a/example/android/build.gradle b/example/android/build.gradle new file mode 100644 index 0000000..6f393b6 --- /dev/null +++ b/example/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.7.20' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.4.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/example/android/gradle.properties b/example/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..6b66533 --- /dev/null +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle new file mode 100644 index 0000000..d3b6a40 --- /dev/null +++ b/example/android/settings.gradle @@ -0,0 +1,15 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/example/lib/main.dart b/example/lib/main.dart new file mode 100644 index 0000000..8342ebe --- /dev/null +++ b/example/lib/main.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_presentation_display_example/route.dart'; + +void main() { + runApp(const MyApp()); +} + +@pragma('vm:entry-point') +void secondaryDisplayMain() { + runApp(const MySecondApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + onGenerateRoute: generateRoute, + initialRoute: '/', + ); + } +} + +class MySecondApp extends StatelessWidget { + const MySecondApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + onGenerateRoute: generateRoute, + initialRoute: 'presentation', + ); + } +} diff --git a/example/lib/main_screen.dart b/example/lib/main_screen.dart new file mode 100644 index 0000000..7e00d57 --- /dev/null +++ b/example/lib/main_screen.dart @@ -0,0 +1,304 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_presentation_display/display.dart'; +import 'package:flutter_presentation_display/flutter_presentation_display.dart'; + +/// Main Screen +class MainScreen extends StatefulWidget { + const MainScreen({Key? key}) : super(key: key); + + @override + _MainScreenState createState() => _MainScreenState(); +} + +class _MainScreenState extends State { + FlutterPresentationDisplay displayManager = FlutterPresentationDisplay(); + List displays = []; + + final TextEditingController _indexToShareController = TextEditingController(); + final TextEditingController _dataToTransferController = + TextEditingController(); + + final TextEditingController _nameOfIdController = TextEditingController(); + String _nameOfId = ""; + final TextEditingController _nameOfIndexController = TextEditingController(); + String _nameOfIndex = ""; + + dynamic dataFromPresentation; + + @override + void initState() { + displayManager.connectedDisplaysChangedStream.listen((event) { + debugPrint("connected displays changed: $event"); + }); + + displayManager.listenDataFromPresentationDisplay(onDataReceived); + super.initState(); + } + + void onDataReceived(dynamic data) { + debugPrint('received data from presentation display: $data'); + dataFromPresentation = data; + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: Center( + child: SingleChildScrollView( + padding: const EdgeInsets.all(32), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _getDisplays(), + _showPresentation(), + _hidePresentation(), + _transferData(), + _dataFromPresentation(), + _getDisplayeById(), + _getDisplayByIndex(), + ], + ), + ), + ), + ); + } + + Widget _getDisplays() { + return Padding( + padding: const EdgeInsets.all(18.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ElevatedButton( + child: const Text('Get Displays'), + onPressed: () async { + final values = await displayManager.getDisplays(); + displays.clear(); + displays.addAll(values!); + setState(() {}); + }, + ), + ListView.builder( + scrollDirection: Axis.vertical, + shrinkWrap: true, + padding: const EdgeInsets.all(8), + itemCount: displays.length, + itemBuilder: (BuildContext context, int index) { + return SizedBox( + height: 50, + child: Center( + child: Text( + ' ${displays[index]?.displayId} ${displays[index]?.name}')), + ); + }, + ), + const Divider() + ], + ), + ); + } + + Widget _showPresentation() { + return Padding( + padding: const EdgeInsets.all(18.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + controller: _indexToShareController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Index to share screen', + ), + ), + ), + ElevatedButton( + child: const Text('Show presentation'), + onPressed: () async { + int? displayId = int.tryParse(_indexToShareController.text); + if (displayId != null) { + for (final display in displays) { + if (display?.displayId == displayId) { + displayManager.showSecondaryDisplay( + displayId: displayId, routerName: "presentation"); + } + } + } + }, + ), + const Divider(), + ], + ), + ); + } + + Widget _hidePresentation() { + return Padding( + padding: const EdgeInsets.all(18.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + controller: _indexToShareController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Index to hide screen', + ), + ), + ), + ElevatedButton( + child: const Text('Hide presentation'), + onPressed: () async { + int? displayId = int.tryParse(_indexToShareController.text); + if (displayId != null) { + for (final display in displays) { + if (display?.displayId == displayId) { + displayManager.hideSecondaryDisplay(displayId: displayId); + } + } + } + }, + ), + const Divider(), + ], + ), + ); + } + + Widget _transferData() { + return Padding( + padding: const EdgeInsets.all(18.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + controller: _dataToTransferController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Data to transfer', + ), + ), + ), + ElevatedButton( + child: const Text('Transfer Data to presentation'), + onPressed: () async { + String data = _dataToTransferController.text; + await displayManager.transferDataToPresentation(data); + }, + ), + const Divider(), + ], + ), + ); + } + + Widget _dataFromPresentation() { + return Padding( + padding: const EdgeInsets.all(18.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: + Text('Data from presentation: ${dataFromPresentation ?? '-'}'), + ), + const Divider(), + ], + ), + ); + } + + Widget _getDisplayeById() { + return Padding( + padding: const EdgeInsets.all(18.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + controller: _nameOfIdController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Id', + ), + ), + ), + ElevatedButton( + child: const Text('Name By Display Id'), + onPressed: () async { + int? id = int.tryParse(_nameOfIdController.text); + if (id != null) { + final value = await displayManager + .getNameByDisplayId(displays[id]?.displayId ?? -1); + _nameOfId = value ?? ""; + setState(() {}); + } + }, + ), + SizedBox( + height: 50, + child: Center(child: Text(_nameOfId)), + ), + const Divider(), + ], + ), + ); + } + + Widget _getDisplayByIndex() { + return Padding( + padding: const EdgeInsets.all(18.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + controller: _nameOfIndexController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Index', + ), + ), + ), + ElevatedButton( + child: const Text('Name By Index'), + onPressed: () async { + int? index = int.tryParse(_nameOfIndexController.text); + if (index != null) { + final value = await displayManager.getNameByIndex(index); + _nameOfIndex = value ?? ""; + setState(() {}); + } + }, + ), + SizedBox( + height: 50, + child: Center(child: Text(_nameOfIndex)), + ), + const Divider(), + ], + ), + ); + } +} diff --git a/example/lib/presentation_screen.dart b/example/lib/presentation_screen.dart new file mode 100644 index 0000000..dd42df8 --- /dev/null +++ b/example/lib/presentation_screen.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_presentation_display/flutter_presentation_display.dart'; + +/// UI of Presentation display +class PresentationScreen extends StatefulWidget { + const PresentationScreen({Key? key}) : super(key: key); + + @override + _PresentationScreenState createState() => _PresentationScreenState(); +} + +class _PresentationScreenState extends State { + String dataFromMain = ""; + FlutterPresentationDisplay displayManager = FlutterPresentationDisplay(); + + final TextEditingController _dataToTransferController = + TextEditingController(); + + @override + void initState() { + displayManager.listenDataFromPresentationDisplay(onDataReceived); + super.initState(); + } + + void onDataReceived(dynamic data) { + debugPrint('received data from main display: $data'); + dataFromMain = data; + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Padding( + padding: const EdgeInsets.all(32.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _transferToMainButton(), + _dataFromMain(), + ], + ), + ), + ), + ); + } + + Widget _transferToMainButton() { + return Padding( + padding: const EdgeInsets.all(18.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + controller: _dataToTransferController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + labelText: 'Data', + ), + ), + ), + ElevatedButton( + child: const Text('Transfer Data To Main'), + onPressed: () async { + displayManager.transferDataToMain(_dataToTransferController.text); + }, + ), + const Divider(), + ], + ), + ); + } + + Widget _dataFromMain() { + return Padding( + padding: const EdgeInsets.all(18.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + height: 50, + child: Center(child: Text('Data from main: $dataFromMain')), + ), + const Divider(), + ], + ), + ); + } +} diff --git a/example/lib/route.dart b/example/lib/route.dart new file mode 100644 index 0000000..993dda4 --- /dev/null +++ b/example/lib/route.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_presentation_display_example/main_screen.dart'; +import 'package:flutter_presentation_display_example/presentation_screen.dart'; + +Route generateRoute(RouteSettings settings) { + switch (settings.name) { + case '/': + return MaterialPageRoute(builder: (_) => const MainScreen()); + case 'presentation': + return MaterialPageRoute(builder: (_) => const PresentationScreen()); + default: + return MaterialPageRoute( + builder: (_) => Scaffold( + body: Center(child: Text('No route defined for ${settings.name}')), + ), + ); + } +} diff --git a/example/pubspec.lock b/example/pubspec.lock new file mode 100644 index 0000000..883575f --- /dev/null +++ b/example/pubspec.lock @@ -0,0 +1,220 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: "1989d917fbe8e6b39806207df5a3fdd3d816cbd090fac2ce26fb45e9a71476e5" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: b543301ad291598523947dc534aaddc5aaad597b709d2426d3a0e0d44c5cb493 + url: "https://pub.dev" + source: hosted + version: "1.0.4" + flutter_presentation_display: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "2.0.4" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + url: "https://pub.dev" + source: hosted + version: "10.0.4" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + url: "https://pub.dev" + source: hosted + version: "3.0.3" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: a2c3d198cb5ea2e179926622d433331d8b58374ab8f29cdda6e863bd62fd369c + url: "https://pub.dev" + source: hosted + version: "1.0.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + url: "https://pub.dev" + source: hosted + version: "0.8.0" + meta: + dependency: transitive + description: + name: meta + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + url: "https://pub.dev" + source: hosted + version: "1.12.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + url: "https://pub.dev" + source: hosted + version: "0.7.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + url: "https://pub.dev" + source: hosted + version: "14.2.1" +sdks: + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/example/pubspec.yaml b/example/pubspec.yaml new file mode 100644 index 0000000..77f6cf1 --- /dev/null +++ b/example/pubspec.yaml @@ -0,0 +1,73 @@ +name: flutter_presentation_display_example +description: an example for flutter_presentation_display plugin. + +# The following line prevents the package from being accidentally published to +# pub.dev using `pub publish`. This is preferred for private packages. +publish_to: "none" # Remove this line if you wish to publish to pub.dev +version: 1.0.0+1 + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" + +dependencies: + flutter: + sdk: flutter + + flutter_presentation_display: + # When depending on this package from a real application you should use: + # flutter_presentation_display: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.4 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^1.0.4 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/lib/display.dart b/lib/display.dart new file mode 100644 index 0000000..8076645 --- /dev/null +++ b/lib/display.dart @@ -0,0 +1,34 @@ +/// A class representing a display device with various properties +class Display { + /// The unique identifier of the display + int? displayId = 0; + + /// The flag associated with the display (could represent various statuses or capabilities) + int? flag; + + /// The rotation of the display (e.g., 0, 90, 180, 270 degrees) + int? rotation; + + /// The name of the display + String? name; + + /// Constructor to initialize a Display object + /// - displayId is required to identify the display + /// - name is required to provide a label for the display + /// - flag and rotation are optional and may be null + Display({ + required this.displayId, + this.flag, + required this.name, + this.rotation, + }); + + /// Factory method to create a Display object from a JSON map + /// - Extracts displayId, flag, name, and rotation values from the given JSON + factory Display.fromJson(Map json) => Display( + displayId: json['displayId'], + flag: json['flags'], + name: json['name'], + rotation: json['rotation'], + ); +} diff --git a/lib/flutter_presentation_display.dart b/lib/flutter_presentation_display.dart new file mode 100644 index 0000000..faa60f2 --- /dev/null +++ b/lib/flutter_presentation_display.dart @@ -0,0 +1,130 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_presentation_display/display.dart'; + +/// Constant method names for communication between Flutter and platform (native code) +const _listDisplay = "listDisplay"; +const _showPresentation = "showPresentation"; +const _hidePresentation = "hidePresentation"; +const _transferDataToPresentation = "transferDataToPresentation"; +const _transferDataToMain = "transferDataToMain"; + +/// Constant for Android presentation display category +const String DISPLAY_CATEGORY_PRESENTATION = + "android.hardware.display.category.PRESENTATION"; + +class FlutterPresentationDisplay { + /// Event and method channel identifiers + final String _displayEventChannelId = "presentation_display_channel_events"; + final String _presentationDisplayMethodChannelId = + "presentation_display_channel"; + final String _mainDisplayMethodChannelId = "main_display_channel"; + + /// EventChannel to receive display connection events + late EventChannel _displayEventChannel; + + /// MethodChannel for managing the presentation display (secondary display) + late MethodChannel _presentationMethodChannel; + + /// MethodChannel for communicating with the main display + late MethodChannel _mainDisplayMethodChannel; + + /// Constructor to initialize event and method channels + FlutterPresentationDisplay() { + _displayEventChannel = EventChannel(_displayEventChannelId); + _presentationMethodChannel = + MethodChannel(_presentationDisplayMethodChannelId); + _mainDisplayMethodChannel = MethodChannel(_mainDisplayMethodChannelId); + } + + /// Retrieves a list of available displays, optionally filtered by category + Future?> getDisplays({String? category}) async { + /// Invoke the native method to list displays and decode the response + final listDisplays = + await _presentationMethodChannel.invokeMethod(_listDisplay, category); + + /// Decode the list of displays from JSON format + List origins = jsonDecode(listDisplays) ?? []; + + /// Convert each element into a Display object + return origins.map((element) { + final map = jsonDecode(jsonEncode(element)); + return Display.fromJson(map as Map); + }).toList(); + } + + /// Gets the name of a display by its ID, optionally filtered by category + Future getNameByDisplayId(int displayId, {String? category}) async { + List displays = await getDisplays(category: category) ?? []; + + /// Find and return the name of the display with the specified ID + return displays + .firstWhere((element) => element.displayId == displayId) + .name; + } + + /// Gets the name of a display by its index in the list of displays + Future getNameByIndex(int index, {String? category}) async { + List? displays = await getDisplays(category: category); + + /// Return the name at the specified index, or null if the index is out of range + return (index >= 0 && index < displays!.length) + ? displays[index].name + : null; + } + + /// Shows the secondary display (presentation) with the given display ID and router name + Future showSecondaryDisplay( + {required int displayId, required String routerName}) async { + return await _presentationMethodChannel.invokeMethod( + _showPresentation, + jsonEncode({"displayId": displayId, "routerName": routerName}), + ); + } + + /// Hides the secondary display (presentation) for the given display ID + Future hideSecondaryDisplay({required int displayId}) async { + return await _presentationMethodChannel.invokeMethod( + _hidePresentation, + jsonEncode({"displayId": displayId}), + ); + } + + /// Stream to listen for changes in connected displays (events from native side) + Stream get connectedDisplaysChangedStream { + return _displayEventChannel.receiveBroadcastStream().cast(); + } + + /// Transfers data to the secondary display (presentation) through the native platform + Future transferDataToPresentation(dynamic arguments) async { + return await _presentationMethodChannel.invokeMethod( + _transferDataToPresentation, arguments); + } + + /// Transfers data to the main display through the native platform + Future transferDataToMain(dynamic arguments) async { + return await _mainDisplayMethodChannel.invokeMethod( + _transferDataToMain, arguments); + } + + /// Listens for data sent from the secondary display (presentation) + /// Calls the provided callback function when data is received + void listenDataFromPresentationDisplay(Function(dynamic) onDataReceived) { + _mainDisplayMethodChannel.setMethodCallHandler((call) async { + debugPrint('Data from Presentation Display: ${call.arguments}'); + onDataReceived(call.arguments); + }); + } + + /// Listens for data sent from the main display + /// Calls the provided callback function when data is received + void listenDataFromMainDisplay(Function(dynamic) onDataReceived) { + _presentationMethodChannel.setMethodCallHandler((call) async { + debugPrint('Data from Main Display: ${call.arguments}'); + onDataReceived(call.arguments); + }); + } +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..a14bcc7 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,205 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: b543301ad291598523947dc534aaddc5aaad597b709d2426d3a0e0d44c5cb493 + url: "https://pub.dev" + source: hosted + version: "1.0.4" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + url: "https://pub.dev" + source: hosted + version: "10.0.4" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + url: "https://pub.dev" + source: hosted + version: "3.0.3" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: a2c3d198cb5ea2e179926622d433331d8b58374ab8f29cdda6e863bd62fd369c + url: "https://pub.dev" + source: hosted + version: "1.0.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + url: "https://pub.dev" + source: hosted + version: "0.8.0" + meta: + dependency: transitive + description: + name: meta + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + url: "https://pub.dev" + source: hosted + version: "1.12.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + url: "https://pub.dev" + source: hosted + version: "0.7.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + url: "https://pub.dev" + source: hosted + version: "14.2.1" +sdks: + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..e26da0e --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,34 @@ +name: flutter_presentation_display +description: A Flutter plugin designed to handling secondary (presentation) display +version: 2.0.4 +repository: https://github.com/elrizwiraswara/flutter_presentation_display + +environment: + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^1.0.4 + +flutter: + plugin: + platforms: + android: + package: com.elriztechnology.flutter_presentation_display + pluginClass: FlutterPresentationDisplayPlugin + +platforms: + android: + +topics: + - secondary-display + - presentation-display + - display-manager + - display-plugin + - multiple-displays \ No newline at end of file diff --git a/test/flutter_presentation_display_test.dart b/test/flutter_presentation_display_test.dart new file mode 100644 index 0000000..ab73b3a --- /dev/null +++ b/test/flutter_presentation_display_test.dart @@ -0,0 +1 @@ +void main() {}