├── .gitignore ├── .idea ├── modules.xml ├── runConfigurations │ └── example_lib_main_dart.xml └── vcs.xml ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── libs │ ├── .gitignore │ └── go.jar ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── io │ └── github │ └── empirefox │ └── flutter_dial_go │ ├── DialService.java │ ├── Dialer.java │ ├── FlutterDialGoPlugin.java │ ├── Pipe.java │ └── SimpleForegroundNotification.java ├── doc.go ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ │ └── io │ │ │ │ │ └── github │ │ │ │ │ └── empirefox │ │ │ │ │ └── flutter_dial_go_example │ │ │ │ │ └── MainActivity.java │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── go │ │ ├── .gitignore │ │ └── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── flutter_dial_go_example.iml ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Frameworks │ │ └── .gitignore │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── main.dart │ └── src │ │ ├── app.dart │ │ └── generated │ │ ├── helloworld.pb.dart │ │ ├── helloworld.pbenum.dart │ │ ├── helloworld.pbgrpc.dart │ │ └── helloworld.pbjson.dart ├── pubspec.yaml └── test │ └── widget_test.dart ├── flutter_dial_go.iml ├── go ├── Makefile ├── example │ ├── Makefile │ ├── gomobile │ │ └── mobile.go │ ├── internal │ │ └── gomobile │ │ │ ├── proxy.go │ │ │ └── server.go │ └── protos │ │ ├── helloworld.pb.go │ │ └── helloworld.proto ├── forgo │ └── forgo.go ├── formobile │ └── formobile.go └── internal │ └── listener │ ├── listener.go │ ├── pipe.go │ └── pipefordialer.go ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── Dialer.swift │ ├── FlutterDialGoPlugin.h │ ├── FlutterDialGoPlugin.m │ ├── Pipe.swift │ └── SwiftFlutterDialGoPlugin.swift └── flutter_dial_go.podspec ├── lib └── flutter_dial_go.dart ├── pubspec.lock ├── pubspec.yaml └── test └── flutter_dial_go_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | 9 | .idea/codeStyles/Project.xml 10 | .idea/libraries/Dart_SDK.xml 11 | .idea/libraries/Flutter_Plugins.xml 12 | .idea/libraries/Flutter_for_Android.xml 13 | .idea/gradle.xml 14 | .idea/misc.xml 15 | .idea/workspace.xml 16 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/runConfigurations/example_lib_main_dart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 8661d8aecd626f7f57ccbcb735553edc05a2e713 8 | channel: dev 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | TODO: Add your license here. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutter_dial_go 2 | 3 | A flutter plugin for connecting to golang embeded servers via platform channel. 4 | 5 | ## Getting Started 6 | 7 | ### Example 8 | 9 | ```bash 10 | make -f ./go/Makefile protoc 11 | make -f ./go/Makefile bind-android 12 | make -f ./go/Makefile bind-ios 13 | flutter run 14 | ``` 15 | 16 | ### Install for golang 17 | 18 | ```bash 19 | go get -u github.com/empirefox/flutter_dial_go 20 | cd GOPATH/github.com/empirefox/flutter_dial_go/go 21 | make android 22 | ``` 23 | 24 | ### Install for flutter project 25 | 26 | Add to `pubsepc.yml`, replace `GOPATH` with real path. 27 | 28 | ```yaml 29 | dependencies: 30 | flutter_dial_go: 31 | path: GOPATH/github.com/empirefox/flutter_dial_go 32 | ``` 33 | 34 | ### Develop golang side 35 | 36 | When write go code: 37 | Only use `github.com/empirefox/flutter_dial_go/go/forgo`. 38 | Do not use `github.com/empirefox/flutter_dial_go/go/formobile`. 39 | 40 | ```go 41 | import "github.com/empirefox/flutter_dial_go/go/forgo" 42 | 43 | // dart usage: var conn = await Conn.dial(9999) 44 | listener, err := forgo.Listen(9999) 45 | ``` 46 | 47 | - Implement `gomobile` package exactly like: [Go Example](go/example/gomobile/mobile.go). 48 | - Copy [Makefile](go/example/Makefile) to flutter project, then replace the `GOMOBILE_PKG`, `APP_PATH` and `protoc:`. 49 | - Build with the new `Makefile`. 50 | - For Android Studio: import `go.aar` and add `api project(':go')` to `PROJECT_DIR$/android/app/build.gradle`. 51 | - For Xcode: add `PODS_ROOT/../Frameworks` to all Framework search paths. 52 | 53 | ### Develop flutter side 54 | 55 | Init with no service. 56 | 57 | ```dart 58 | import 'package:flutter_dial_go/flutter_dial_go.dart'; 59 | 60 | Future initGo() async { 61 | await Conn.startGo(); 62 | } 63 | ``` 64 | 65 | Or init with foreground service. For ios, service will not start and it will work like above. 66 | 67 | ```dart 68 | import 'package:flutter_dial_go/flutter_dial_go.dart'; 69 | 70 | Future initGo() async { 71 | await Conn.notificationChannel( 72 | channelId: channelId, 73 | importance: importance, 74 | name: 'fdg running', 75 | description: 'keep fdg running', 76 | ); 77 | await Conn.startGoWithService( 78 | channelId: channelId, 79 | notificationId: notificationId, 80 | title: 'flutter_dial_go example', 81 | text: 'make flutter dial go', 82 | ); 83 | } 84 | ``` 85 | 86 | Then dial: 87 | 88 | ```dart 89 | // raw http request 90 | // golang: forgo.Listen(9998) 91 | Conn c = await Conn.dial(9998); 92 | print('GET /\n'); 93 | c.receiveStream 94 | .fold(BytesBuilder(), (BytesBuilder a, List b) => a..add(b)) 95 | .then((a) => setState(() { 96 | _result = 'GET / HTTP/1.0\n\n' + utf8.decode(a.takeBytes()); 97 | })) 98 | .catchError((e) => setState(() { 99 | _result = 'Caught http error: e'; 100 | })) 101 | .then((_) => c.close()); 102 | c.add(utf8.encode('GET / HTTP/1.0\r\n\r\n')); 103 | ``` 104 | 105 | Or http2: 106 | 107 | ```dart 108 | // golang: forgo.Listen(9997) 109 | Conn c = await Conn.dial(9997); 110 | var transport = ClientTransportConnection.viaStreams(c.receiveStream, c); 111 | ``` 112 | 113 | Or grpc: 114 | 115 | ```dart 116 | Future _connect(String host, int port, ChannelCredentials credentials) async { 117 | // ignore: close_sinks 118 | final conn = await Conn.dial(port); 119 | return Http2Streams(conn.receiveStream, conn); 120 | } 121 | 122 | var channel = ClientChannel( 123 | 'go', 124 | port: 9999, 125 | options: ChannelOptions( 126 | credentials: ChannelCredentials.insecure(), 127 | connect: _connect, 128 | ), 129 | ); 130 | var stub = GreeterClient(_channel); 131 | 132 | ... 133 | 134 | channel.terminate(); 135 | ``` 136 | 137 | [Flutter Example](example/lib/src/app.dart). 138 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'io.github.empirefox.flutter_dial_go' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.2.1' 12 | } 13 | } 14 | 15 | rootProject.allprojects { 16 | repositories { 17 | flatDir { 18 | dirs 'libs' 19 | } 20 | google() 21 | jcenter() 22 | } 23 | } 24 | 25 | apply plugin: 'com.android.library' 26 | 27 | android { 28 | compileSdkVersion 28 29 | 30 | defaultConfig { 31 | minSdkVersion 16 32 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 33 | } 34 | lintOptions { 35 | disable 'InvalidPackage' 36 | } 37 | compileOptions { 38 | targetCompatibility 1.8 39 | sourceCompatibility 1.8 40 | } 41 | } 42 | 43 | dependencies { 44 | compileOnly fileTree(dir: 'libs', include: ['*.jar', '*.aar']) 45 | implementation "androidx.core:core:1.0.0" 46 | } 47 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /android/libs/.gitignore: -------------------------------------------------------------------------------- 1 | go-sources.jar -------------------------------------------------------------------------------- /android/libs/go.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/android/libs/go.jar -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'flutter_dial_go' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/java/io/github/empirefox/flutter_dial_go/DialService.java: -------------------------------------------------------------------------------- 1 | package io.github.empirefox.flutter_dial_go; 2 | 3 | import android.app.Notification; 4 | import android.app.PendingIntent; 5 | import android.app.Service; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.pm.PackageManager; 9 | import android.os.Binder; 10 | import android.os.Build; 11 | import android.os.IBinder; 12 | import android.os.Looper; 13 | import android.util.Log; 14 | 15 | import androidx.core.app.NotificationCompat; 16 | 17 | 18 | public class DialService extends Service { 19 | private static final String TAG = "DialService#"; 20 | public static final String ACTION_START = "ACTION_START"; 21 | public static final String ACTION_STOP = "ACTION_STOP"; 22 | private static final String INTENT_EXTRA_KEY_SFN = "sfn"; 23 | 24 | private Dialer dialer = null; 25 | private final IBinder binder = new DialerBinder(); 26 | 27 | public static Intent createIntent(Context context, SimpleForegroundNotification sfn) { 28 | Intent intent = new Intent(context, DialService.class); 29 | if (sfn != null) { 30 | intent.setAction(ACTION_START).putExtra(INTENT_EXTRA_KEY_SFN, sfn); 31 | } else { 32 | intent.setAction(ACTION_STOP); 33 | } 34 | return intent; 35 | } 36 | 37 | public static Dialer getDialer(IBinder binder) { 38 | return ((DialerBinder) binder).getDialer(); 39 | } 40 | 41 | private class DialerBinder extends Binder { 42 | Dialer getDialer() { 43 | return dialer; 44 | } 45 | } 46 | 47 | @Override 48 | public int onStartCommand(Intent intent, int flags, int startId) { 49 | String action = intent.getAction(); 50 | switch (action) { 51 | case ACTION_START: 52 | SimpleForegroundNotification sfn = (SimpleForegroundNotification) intent.getSerializableExtra(INTENT_EXTRA_KEY_SFN); 53 | showForegroundNotification(sfn); 54 | break; 55 | 56 | case ACTION_STOP: 57 | stopForeground(true); 58 | stopSelf(); 59 | break; 60 | } 61 | return START_STICKY; 62 | } 63 | 64 | @Override 65 | public IBinder onBind(Intent intent) { 66 | return binder; 67 | } 68 | 69 | @Override 70 | public void onCreate() { 71 | super.onCreate(); 72 | dialer = new Dialer(Looper.getMainLooper()); 73 | } 74 | 75 | @Override 76 | public void onDestroy() { 77 | dialer.stopDialThread(); 78 | try { 79 | dialer.doDestroySync(); 80 | } catch (Exception e) { 81 | Log.e(TAG, "Failed to destroy go resources", e); 82 | } 83 | super.onDestroy(); 84 | } 85 | 86 | private void showForegroundNotification(SimpleForegroundNotification sfn) { 87 | // Create notification default intent. 88 | PackageManager pm = getApplicationContext().getPackageManager(); 89 | Intent notificationIntent = pm.getLaunchIntentForPackage(getApplicationContext().getPackageName()); 90 | PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); 91 | 92 | Notification notification; 93 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 94 | notification = new Notification.Builder(this, sfn.channelId) 95 | .setWhen(System.currentTimeMillis()) 96 | .setSmallIcon(android.R.drawable.ic_media_play) 97 | .setContentTitle(sfn.title) 98 | .setContentText(sfn.text) 99 | .setContentIntent(pendingIntent) 100 | .build(); 101 | } else { 102 | notification = new NotificationCompat.Builder(this, sfn.channelId) 103 | .setWhen(System.currentTimeMillis()) 104 | .setSmallIcon(android.R.drawable.ic_media_play) 105 | .setContentTitle(sfn.title) 106 | .setContentText(sfn.text) 107 | .setPriority(Notification.PRIORITY_MAX) 108 | .setContentIntent(pendingIntent) 109 | .build(); 110 | } 111 | startForeground(sfn.notificationId, notification); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /android/src/main/java/io/github/empirefox/flutter_dial_go/Dialer.java: -------------------------------------------------------------------------------- 1 | package io.github.empirefox.flutter_dial_go; 2 | 3 | import android.os.Handler; 4 | import android.os.HandlerThread; 5 | import android.os.Looper; 6 | 7 | import formobile.Conn; 8 | import formobile.Formobile; 9 | import gomobile.ConcurrentRunner; 10 | import gomobile.FromGo; 11 | import gomobile.Gomobile; 12 | 13 | public class Dialer extends Handler implements formobile.Dialer { 14 | private final formobile.Dialer dialer = Formobile.getDialer(); 15 | private final HandlerThread dialThread; 16 | private final Handler dialHandler; 17 | private FromGo go = null; 18 | private ConcurrentRunner destroyRunner = null; 19 | 20 | public Dialer(Looper looper) { 21 | super(looper); 22 | dialThread = new HandlerThread("dial_thread"); 23 | dialThread.start(); 24 | dialHandler = new Handler(dialThread.getLooper()); 25 | } 26 | 27 | public void doInit(Callback cb) { 28 | if (go == null) { 29 | go = Gomobile.newFromGo(); 30 | } 31 | ConcurrentRunner runner = go.doInitOnce(); 32 | dialHandler.post(() -> { 33 | try { 34 | runner.done(); 35 | post(() -> cb.onResult(null)); 36 | } catch (Exception e) { 37 | post(() -> cb.onResult(e)); 38 | } 39 | }); 40 | } 41 | 42 | public void doDestroy(Callback cb) { 43 | if (go == null) { 44 | cb.onResult(null); 45 | return; 46 | } 47 | 48 | FromGo go = this.go; 49 | this.go = null; 50 | destroyRunner = go.doDestroyOnce(); 51 | 52 | dialHandler.post(() -> { 53 | try { 54 | destroyRunner.done(); 55 | post(() -> { 56 | cb.onResult(null); 57 | destroyRunner = null; 58 | }); 59 | } catch (Exception e) { 60 | post(() -> { 61 | cb.onResult(e); 62 | destroyRunner = null; 63 | }); 64 | } 65 | }); 66 | } 67 | 68 | public void doDestroySync() throws Exception { 69 | ConcurrentRunner runner = null; 70 | if (go != null) { 71 | runner = go.doDestroyOnce(); 72 | } 73 | if (runner == null) { 74 | runner = destroyRunner; 75 | destroyRunner = null; 76 | } 77 | if (runner != null) { 78 | runner.done(); 79 | } 80 | go = null; 81 | } 82 | 83 | public Handler getDialHandler() { 84 | return dialHandler; 85 | } 86 | 87 | public void stopDialThread() { 88 | dialThread.quit(); 89 | } 90 | 91 | public boolean isNotInitialized() { 92 | return go == null; 93 | } 94 | 95 | @Override 96 | public Conn dial(int port, long channelId, long timeoutnano) throws Exception { 97 | return dialer.dial(port, channelId, timeoutnano); 98 | } 99 | 100 | public interface Callback { 101 | // fatal error if e is not null, bugs here!!! 102 | void onResult(Exception e); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /android/src/main/java/io/github/empirefox/flutter_dial_go/FlutterDialGoPlugin.java: -------------------------------------------------------------------------------- 1 | package io.github.empirefox.flutter_dial_go; 2 | 3 | import android.app.Activity; 4 | import android.app.NotificationChannel; 5 | import android.app.NotificationManager; 6 | import android.content.ComponentName; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.content.ServiceConnection; 10 | import android.os.Build; 11 | import android.os.Handler; 12 | import android.os.IBinder; 13 | import android.os.Looper; 14 | 15 | import java.util.ArrayList; 16 | import java.util.HashMap; 17 | import java.util.List; 18 | import java.util.Map; 19 | 20 | import formobile.Conn; 21 | import io.flutter.plugin.common.MethodCall; 22 | import io.flutter.plugin.common.MethodChannel; 23 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler; 24 | import io.flutter.plugin.common.MethodChannel.Result; 25 | import io.flutter.plugin.common.PluginRegistry.Registrar; 26 | import io.flutter.plugin.common.PluginRegistry.ViewDestroyListener; 27 | import io.flutter.view.FlutterNativeView; 28 | 29 | /** 30 | * FlutterDialGoPlugin 31 | */ 32 | public class FlutterDialGoPlugin extends Handler implements MethodCallHandler, ServiceConnection, ViewDestroyListener { 33 | private final MethodChannel controller; 34 | private final Registrar registrar; 35 | private final Map pipes = new HashMap<>(); 36 | 37 | private Dialer dialer = null; 38 | private Result bindResult = null; 39 | private Result unbindResult = null; 40 | private boolean serviceMode = false; 41 | 42 | public FlutterDialGoPlugin(Registrar registrar, MethodChannel controller) { 43 | this.registrar = registrar; 44 | this.controller = controller; 45 | } 46 | 47 | /** 48 | * Plugin registration. 49 | */ 50 | public static void registerWith(Registrar registrar) { 51 | final MethodChannel controller = new MethodChannel(registrar.messenger(), "flutter_dial_go"); 52 | final FlutterDialGoPlugin instance = new FlutterDialGoPlugin(registrar, controller); 53 | controller.setMethodCallHandler(instance); 54 | registrar.addViewDestroyListener(instance); 55 | } 56 | 57 | @Override 58 | public void onMethodCall(MethodCall call, Result result) { 59 | switch (call.method) { 60 | case "notificationChannel": 61 | onMethodCreateNotificationChannel(call, result); 62 | return; 63 | 64 | case "noService": 65 | if (dialer != null || serviceMode) { 66 | result.error("onMethodCall", "service mode or dialer already exist", null); 67 | return; 68 | } 69 | dialer = new Dialer(Looper.getMainLooper()); 70 | result.success(null); 71 | return; 72 | 73 | case "startService": 74 | if (dialer != null || serviceMode) { 75 | result.error("onMethodCall", "dialer already exist", null); 76 | return; 77 | } 78 | serviceMode = true; 79 | onMethodStartService(call, result); 80 | return; 81 | 82 | case "stopService": 83 | if (!serviceMode) { 84 | dialer = null; 85 | result.success(null); 86 | return; 87 | } 88 | if (dialer == null) { 89 | result.success(null); 90 | return; 91 | } 92 | onMethodStopService(call, result); 93 | return; 94 | } 95 | 96 | if (dialer == null) { 97 | result.error(call.method, "service stopped", null); 98 | return; 99 | } 100 | 101 | switch (call.method) { 102 | case "initGo": 103 | onMethodInitGo(call, result); 104 | return; 105 | 106 | case "destroyGo": 107 | onMethodDestroyGo(call, result); 108 | return; 109 | } 110 | 111 | if (dialer.isNotInitialized()) { 112 | result.error(call.method, "go not initialized", null); 113 | return; 114 | } 115 | 116 | switch (call.method) { 117 | // dial Conn 118 | case "dial": 119 | onMethodDial(call, result); 120 | return; 121 | 122 | // close Conn 123 | case "close": 124 | onMethodClose(call, result); 125 | return; 126 | } 127 | 128 | result.notImplemented(); 129 | } 130 | 131 | private void onMethodCreateNotificationChannel(MethodCall call, Result result) { 132 | // Create the NotificationChannel, but only on API 26+ because 133 | // the NotificationChannel class is new and not in the support library 134 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 135 | ArrayList args = call.arguments(); 136 | String channelId = (String) args.get(0); 137 | int importance = (int) args.get(1); 138 | CharSequence name = (String) args.get(2); 139 | String description = (String) args.get(3); 140 | 141 | NotificationChannel channel = new NotificationChannel(channelId, name, importance); 142 | channel.setDescription(description); 143 | // Register the channel with the system; you can't change the importance 144 | // or other notification behaviors after this 145 | NotificationManager notificationManager = registrar.context().getSystemService(NotificationManager.class); 146 | notificationManager.createNotificationChannel(channel); 147 | } 148 | result.success(null); 149 | } 150 | 151 | private void onMethodStartService(MethodCall call, Result result) { 152 | ArrayList args = call.arguments(); 153 | SimpleForegroundNotification sfn = new SimpleForegroundNotification(args); 154 | Activity activity = registrar.activity(); 155 | Intent intent = DialService.createIntent(activity, sfn); 156 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 157 | activity.startForegroundService(intent); 158 | } else { 159 | activity.startService(intent); 160 | } 161 | bindResult = result; 162 | activity.bindService(intent, this, Context.BIND_AUTO_CREATE); 163 | } 164 | 165 | private void onMethodStopService(MethodCall call, Result result) { 166 | Activity activity = registrar.activity(); 167 | Intent intent = DialService.createIntent(activity, null); 168 | unbindResult = result; 169 | activity.unbindService(this); 170 | activity.stopService(intent); 171 | } 172 | 173 | private void onMethodInitGo(MethodCall call, Result result) { 174 | dialer.doInit((e) -> { 175 | if (e != null) { 176 | result.error("initGo", e.toString(), null); 177 | } else { 178 | result.success(null); 179 | } 180 | }); 181 | } 182 | 183 | private void onMethodDestroyGo(MethodCall call, Result result) { 184 | dialer.doDestroy((e) -> { 185 | if (e != null) { 186 | result.error("destroyGo", e.toString(), null); 187 | } else { 188 | result.success(null); 189 | } 190 | }); 191 | } 192 | 193 | private void onMethodDial(MethodCall call, Result result) { 194 | dialer.getDialHandler().post(() -> { 195 | List args = call.arguments(); 196 | int port = (int) args.get(0); 197 | long id = ((Number) args.get(1)).longValue(); 198 | long timeoutnano = ((Number) args.get(2)).longValue(); 199 | try { 200 | Conn conn = dialer.dial(port, id, timeoutnano); 201 | post(() -> { 202 | Pipe pipe = new Pipe(registrar.messenger(), controller, conn, id); 203 | pipe.start(); 204 | pipes.put(id, pipe); 205 | result.success(null); 206 | }); 207 | } catch (Exception e) { 208 | post(() -> result.error("dial failed", e.getMessage(), null)); 209 | } 210 | }); 211 | } 212 | 213 | private void onMethodClose(MethodCall call, Result result) { 214 | long id = ((Number) call.arguments()).longValue(); 215 | Pipe pipe = pipes.get(id); 216 | if (pipe != null) { 217 | pipe.stop(); 218 | } 219 | result.success(null); 220 | } 221 | 222 | @Override 223 | public void onServiceConnected(ComponentName name, IBinder service) { 224 | dialer = DialService.getDialer(service); 225 | bindResult.success(null); 226 | bindResult = null; 227 | } 228 | 229 | @Override 230 | public void onServiceDisconnected(ComponentName name) { 231 | dialer = null; 232 | unbindResult.success(null); 233 | unbindResult = null; 234 | serviceMode = false; 235 | } 236 | 237 | @Override 238 | public boolean onViewDestroy(FlutterNativeView flutterNativeView) { 239 | if (dialer != null && serviceMode) { 240 | registrar.activity().unbindService(this); 241 | } 242 | for (Pipe pipe : pipes.values()) { 243 | pipe.stop(); 244 | } 245 | pipes.clear(); 246 | return true; 247 | } 248 | } 249 | 250 | -------------------------------------------------------------------------------- /android/src/main/java/io/github/empirefox/flutter_dial_go/Pipe.java: -------------------------------------------------------------------------------- 1 | package io.github.empirefox.flutter_dial_go; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | 6 | import java.nio.ByteBuffer; 7 | import java.util.ArrayList; 8 | 9 | import formobile.Conn; 10 | import formobile.WriteReturn; 11 | import io.flutter.plugin.common.BinaryMessenger; 12 | import io.flutter.plugin.common.BinaryMessenger.BinaryReply; 13 | import io.flutter.plugin.common.MethodChannel; 14 | 15 | import static formobile.Formobile.ErrNone; 16 | 17 | public class Pipe extends Handler { 18 | private static final String streamPrefix = "fdgs#"; 19 | 20 | private final BinaryMessenger messenger; 21 | private final MethodChannel controller; 22 | private final Conn conn; 23 | private final long id; 24 | private final String streamName; 25 | 26 | private boolean closed = false; 27 | 28 | public Pipe(BinaryMessenger messenger, MethodChannel controller, Conn conn, long id) { 29 | this(messenger, controller, conn, id, Looper.myLooper()); 30 | } 31 | 32 | public Pipe(BinaryMessenger messenger, MethodChannel controller, Conn conn, long id, Looper looper) { 33 | super(looper); 34 | this.messenger = messenger; 35 | this.controller = controller; 36 | this.conn = conn; 37 | this.id = id; 38 | this.streamName = streamPrefix + String.valueOf(id); 39 | } 40 | 41 | public void start() { 42 | messenger.setMessageHandler(streamName, (ByteBuffer message, final BinaryReply reply) -> { 43 | WriteReturn r = conn.write(message.array()); 44 | byte err = r.getErr(); 45 | if (err != ErrNone) { 46 | closeWithErr(err); 47 | } 48 | }); 49 | 50 | conn.startRead((byte[] b, int n, byte err) -> { 51 | ByteBuffer data = n > 0 ? ByteBuffer.allocateDirect(n).put(b) : null; 52 | post(() -> { 53 | if (n > 0) { 54 | messenger.send(streamName, data); 55 | } 56 | if (err != ErrNone) { 57 | closeWithErr(err); 58 | } 59 | }); 60 | }); 61 | } 62 | 63 | public void stop() { 64 | closeWithErr(ErrNone); 65 | } 66 | 67 | private void closeWithErr(byte err) { 68 | if (closed) { 69 | return; 70 | } 71 | closed = true; 72 | 73 | messenger.setMessageHandler(streamName, null); 74 | ArrayList args = new ArrayList<>(); 75 | args.add(id); 76 | args.add(Byte.valueOf(err).intValue()); 77 | controller.invokeMethod("close", args); 78 | conn.close(); 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /android/src/main/java/io/github/empirefox/flutter_dial_go/SimpleForegroundNotification.java: -------------------------------------------------------------------------------- 1 | package io.github.empirefox.flutter_dial_go; 2 | 3 | import java.io.Serializable; 4 | import java.util.List; 5 | 6 | public final class SimpleForegroundNotification implements Serializable { 7 | public final String channelId; 8 | public final int notificationId; 9 | public final CharSequence title; 10 | public final CharSequence text; 11 | 12 | public SimpleForegroundNotification(List args) { 13 | this.channelId = (String) args.get(0); 14 | this.notificationId = (int) args.get(1); 15 | this.title = (String) args.get(2); 16 | this.text = (String) args.get(3); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // flutter_dial_go is a flutter plugin for connecting to golang embeded 2 | // servers via platform channel. 3 | // 4 | // For golang side, import "github.com/empirefox/flutter_dial_go/go/forgo". 5 | // A "gomobile" package is expected, and all public apis must be exactly the 6 | // same with `go/example/gomobile/mobile.go`. 7 | // 8 | // Created by: 9 | // `flutter create --template=plugin -i swift --org io.github.empirefox --description "A flutter plugin for connecting to golang embeded servers via platform channel." flutter_dial_go` 10 | package flutter_dial_go 11 | 12 | import ( 13 | _ "github.com/empirefox/flutter_dial_go/go/forgo" 14 | _ "github.com/empirefox/flutter_dial_go/go/formobile" 15 | ) 16 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .vscode/ 21 | 22 | # Flutter/Dart/Pub related 23 | **/doc/api/ 24 | .dart_tool/ 25 | .flutter-plugins 26 | .packages 27 | .pub-cache/ 28 | .pub/ 29 | /build/ 30 | 31 | # Android related 32 | **/android/**/gradle-wrapper.jar 33 | **/android/.gradle 34 | **/android/captures/ 35 | **/android/gradlew 36 | **/android/gradlew.bat 37 | **/android/local.properties 38 | **/android/**/GeneratedPluginRegistrant.java 39 | 40 | # iOS/XCode related 41 | **/ios/**/*.mode1v3 42 | **/ios/**/*.mode2v3 43 | **/ios/**/*.moved-aside 44 | **/ios/**/*.pbxuser 45 | **/ios/**/*.perspectivev3 46 | **/ios/**/*sync/ 47 | **/ios/**/.sconsign.dblite 48 | **/ios/**/.tags* 49 | **/ios/**/.vagrant/ 50 | **/ios/**/DerivedData/ 51 | **/ios/**/Icon? 52 | **/ios/**/Pods/ 53 | **/ios/**/.symlinks/ 54 | **/ios/**/profile 55 | **/ios/**/xcuserdata 56 | **/ios/.generated/ 57 | **/ios/Flutter/App.framework 58 | **/ios/Flutter/Flutter.framework 59 | **/ios/Flutter/Generated.xcconfig 60 | **/ios/Flutter/app.flx 61 | **/ios/Flutter/app.zip 62 | **/ios/Flutter/flutter_assets/ 63 | **/ios/ServiceDefinitions.json 64 | **/ios/Runner/GeneratedPluginRegistrant.* 65 | 66 | # Exceptions to above rules. 67 | !**/ios/**/default.mode1v3 68 | !**/ios/**/default.mode2v3 69 | !**/ios/**/default.pbxuser 70 | !**/ios/**/default.perspectivev3 71 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 72 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 8661d8aecd626f7f57ccbcb735553edc05a2e713 8 | channel: dev 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # flutter_dial_go_example 2 | 3 | Demonstrates how to use the flutter_dial_go plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.io/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.io/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.io/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.class 3 | .gradle 4 | /local.properties 5 | /.idea/workspace.xml 6 | /.idea/libraries 7 | .DS_Store 8 | /build 9 | /captures 10 | GeneratedPluginRegistrant.java 11 | 12 | /.idea/caches -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 28 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "io.github.empirefox.flutter_dial_go_example" 37 | minSdkVersion 16 38 | targetSdkVersion 28 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | 52 | compileOptions { 53 | targetCompatibility 1.8 54 | sourceCompatibility 1.8 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | testImplementation 'junit:junit:4.12' 64 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 65 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 66 | api project(':go') 67 | } 68 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 10 | 14 | 21 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/io/github/empirefox/flutter_dial_go_example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package io.github.empirefox.flutter_dial_go_example; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | GeneratedPluginRegistrant.registerWith(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.2.1' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /example/android/go/.gitignore: -------------------------------------------------------------------------------- 1 | *.aar 2 | *.jar -------------------------------------------------------------------------------- /example/android/go/build.gradle: -------------------------------------------------------------------------------- 1 | configurations.maybeCreate("default") 2 | artifacts.add("default", file('go.aar')) -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | include ':go' 3 | 4 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 5 | 6 | def plugins = new Properties() 7 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 8 | if (pluginsFile.exists()) { 9 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 10 | } 11 | 12 | plugins.each { name, path -> 13 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 14 | include ":$name" 15 | project(":$name").projectDir = pluginDirectory 16 | } 17 | -------------------------------------------------------------------------------- /example/flutter_dial_go_example.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/app.flx 37 | /Flutter/app.zip 38 | /Flutter/flutter_assets/ 39 | /Flutter/App.framework 40 | /Flutter/Flutter.framework 41 | /Flutter/Generated.xcconfig 42 | /ServiceDefinitions.json 43 | 44 | Pods/ 45 | .symlinks/ -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Frameworks/.gitignore: -------------------------------------------------------------------------------- 1 | *.framework -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | def parse_KV_file(file, separator='=') 8 | file_abs_path = File.expand_path(file) 9 | if !File.exists? file_abs_path 10 | return []; 11 | end 12 | pods_ary = [] 13 | skip_line_start_symbols = ["#", "/"] 14 | File.foreach(file_abs_path) { |line| 15 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 16 | plugin = line.split(pattern=separator) 17 | if plugin.length == 2 18 | podname = plugin[0].strip() 19 | path = plugin[1].strip() 20 | podpath = File.expand_path("#{path}", file_abs_path) 21 | pods_ary.push({:name => podname, :path => podpath}); 22 | else 23 | puts "Invalid plugin specification: #{line}" 24 | end 25 | } 26 | return pods_ary 27 | end 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | 32 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 33 | # referring to absolute paths on developers' machines. 34 | system('rm -rf .symlinks') 35 | system('mkdir -p .symlinks/plugins') 36 | 37 | # Flutter Pods 38 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 39 | if generated_xcode_build_settings.empty? 40 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." 41 | end 42 | generated_xcode_build_settings.map { |p| 43 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 44 | symlink = File.join('.symlinks', 'flutter') 45 | File.symlink(File.dirname(p[:path]), symlink) 46 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 47 | end 48 | } 49 | 50 | # Plugin Pods 51 | plugin_pods = parse_KV_file('../.flutter-plugins') 52 | plugin_pods.map { |p| 53 | symlink = File.join('.symlinks', 'plugins', p[:name]) 54 | File.symlink(p[:path], symlink) 55 | pod p[:name], :path => File.join(symlink, 'ios') 56 | } 57 | end 58 | 59 | post_install do |installer| 60 | installer.pods_project.targets.each do |target| 61 | target.build_configurations.each do |config| 62 | config.build_settings['ENABLE_BITCODE'] = 'NO' 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_dial_go (0.0.1): 4 | - Flutter 5 | 6 | DEPENDENCIES: 7 | - Flutter (from `.symlinks/flutter/ios`) 8 | - flutter_dial_go (from `.symlinks/plugins/flutter_dial_go/ios`) 9 | 10 | EXTERNAL SOURCES: 11 | Flutter: 12 | :path: ".symlinks/flutter/ios" 13 | flutter_dial_go: 14 | :path: ".symlinks/plugins/flutter_dial_go/ios" 15 | 16 | SPEC CHECKSUMS: 17 | Flutter: 9d0fac939486c9aba2809b7982dfdbb47a7b0296 18 | flutter_dial_go: 1a97a44ab836207942379340e5abc90be2967fda 19 | 20 | PODFILE CHECKSUM: 7765ea4305eaab0b3dfd384c7de11902aa3195fd 21 | 22 | COCOAPODS: 1.5.3 23 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; }; 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 13 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 14 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 15 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 16 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 17 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 18 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 19 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 20 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 21 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 22 | E14681A3FF8C249166CDC318 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 538970031E8AFF82217B898E /* Pods_Runner.framework */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXCopyFilesBuildPhase section */ 26 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 27 | isa = PBXCopyFilesBuildPhase; 28 | buildActionMask = 2147483647; 29 | dstPath = ""; 30 | dstSubfolderSpec = 10; 31 | files = ( 32 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, 33 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, 34 | ); 35 | name = "Embed Frameworks"; 36 | runOnlyForDeploymentPostprocessing = 0; 37 | }; 38 | /* End PBXCopyFilesBuildPhase section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 42 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 43 | 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; }; 44 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 45 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 46 | 538970031E8AFF82217B898E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 48 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 49 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 50 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 51 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 52 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 53 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 54 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 55 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 56 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 57 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 66 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 67 | E14681A3FF8C249166CDC318 /* Pods_Runner.framework in Frameworks */, 68 | ); 69 | runOnlyForDeploymentPostprocessing = 0; 70 | }; 71 | /* End PBXFrameworksBuildPhase section */ 72 | 73 | /* Begin PBXGroup section */ 74 | 652225C279DA4C5B7CF282F7 /* Frameworks */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | 538970031E8AFF82217B898E /* Pods_Runner.framework */, 78 | ); 79 | name = Frameworks; 80 | sourceTree = ""; 81 | }; 82 | 69694D78D00130395742FC75 /* Pods */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | ); 86 | name = Pods; 87 | sourceTree = ""; 88 | }; 89 | 9740EEB11CF90186004384FC /* Flutter */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 2D5378251FAA1A9400D5DBA9 /* flutter_assets */, 93 | 3B80C3931E831B6300D905FE /* App.framework */, 94 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 95 | 9740EEBA1CF902C7004384FC /* Flutter.framework */, 96 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 97 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 98 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 99 | ); 100 | name = Flutter; 101 | sourceTree = ""; 102 | }; 103 | 97C146E51CF9000F007C117D = { 104 | isa = PBXGroup; 105 | children = ( 106 | 9740EEB11CF90186004384FC /* Flutter */, 107 | 97C146F01CF9000F007C117D /* Runner */, 108 | 97C146EF1CF9000F007C117D /* Products */, 109 | 69694D78D00130395742FC75 /* Pods */, 110 | 652225C279DA4C5B7CF282F7 /* Frameworks */, 111 | ); 112 | sourceTree = ""; 113 | }; 114 | 97C146EF1CF9000F007C117D /* Products */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | 97C146EE1CF9000F007C117D /* Runner.app */, 118 | ); 119 | name = Products; 120 | sourceTree = ""; 121 | }; 122 | 97C146F01CF9000F007C117D /* Runner */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 126 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 127 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 128 | 97C147021CF9000F007C117D /* Info.plist */, 129 | 97C146F11CF9000F007C117D /* Supporting Files */, 130 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 131 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 132 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 133 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 134 | ); 135 | path = Runner; 136 | sourceTree = ""; 137 | }; 138 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | ); 142 | name = "Supporting Files"; 143 | sourceTree = ""; 144 | }; 145 | /* End PBXGroup section */ 146 | 147 | /* Begin PBXNativeTarget section */ 148 | 97C146ED1CF9000F007C117D /* Runner */ = { 149 | isa = PBXNativeTarget; 150 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 151 | buildPhases = ( 152 | D70FF24AF2D59B2BBA4979C3 /* [CP] Check Pods Manifest.lock */, 153 | 9740EEB61CF901F6004384FC /* Run Script */, 154 | 97C146EA1CF9000F007C117D /* Sources */, 155 | 97C146EB1CF9000F007C117D /* Frameworks */, 156 | 97C146EC1CF9000F007C117D /* Resources */, 157 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 158 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 159 | F304D45F0A7BB8C9C1336EF5 /* [CP] Embed Pods Frameworks */, 160 | ); 161 | buildRules = ( 162 | ); 163 | dependencies = ( 164 | ); 165 | name = Runner; 166 | productName = Runner; 167 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 168 | productType = "com.apple.product-type.application"; 169 | }; 170 | /* End PBXNativeTarget section */ 171 | 172 | /* Begin PBXProject section */ 173 | 97C146E61CF9000F007C117D /* Project object */ = { 174 | isa = PBXProject; 175 | attributes = { 176 | LastUpgradeCheck = 0910; 177 | ORGANIZATIONNAME = "The Chromium Authors"; 178 | TargetAttributes = { 179 | 97C146ED1CF9000F007C117D = { 180 | CreatedOnToolsVersion = 7.3.1; 181 | LastSwiftMigration = 0910; 182 | }; 183 | }; 184 | }; 185 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 186 | compatibilityVersion = "Xcode 3.2"; 187 | developmentRegion = English; 188 | hasScannedForEncodings = 0; 189 | knownRegions = ( 190 | en, 191 | Base, 192 | ); 193 | mainGroup = 97C146E51CF9000F007C117D; 194 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 195 | projectDirPath = ""; 196 | projectRoot = ""; 197 | targets = ( 198 | 97C146ED1CF9000F007C117D /* Runner */, 199 | ); 200 | }; 201 | /* End PBXProject section */ 202 | 203 | /* Begin PBXResourcesBuildPhase section */ 204 | 97C146EC1CF9000F007C117D /* Resources */ = { 205 | isa = PBXResourcesBuildPhase; 206 | buildActionMask = 2147483647; 207 | files = ( 208 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 209 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 210 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 211 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 212 | 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */, 213 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 214 | ); 215 | runOnlyForDeploymentPostprocessing = 0; 216 | }; 217 | /* End PBXResourcesBuildPhase section */ 218 | 219 | /* Begin PBXShellScriptBuildPhase section */ 220 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 221 | isa = PBXShellScriptBuildPhase; 222 | buildActionMask = 2147483647; 223 | files = ( 224 | ); 225 | inputPaths = ( 226 | ); 227 | name = "Thin Binary"; 228 | outputPaths = ( 229 | ); 230 | runOnlyForDeploymentPostprocessing = 0; 231 | shellPath = /bin/sh; 232 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; 233 | }; 234 | 9740EEB61CF901F6004384FC /* Run Script */ = { 235 | isa = PBXShellScriptBuildPhase; 236 | buildActionMask = 2147483647; 237 | files = ( 238 | ); 239 | inputPaths = ( 240 | ); 241 | name = "Run Script"; 242 | outputPaths = ( 243 | ); 244 | runOnlyForDeploymentPostprocessing = 0; 245 | shellPath = /bin/sh; 246 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 247 | }; 248 | D70FF24AF2D59B2BBA4979C3 /* [CP] Check Pods Manifest.lock */ = { 249 | isa = PBXShellScriptBuildPhase; 250 | buildActionMask = 2147483647; 251 | files = ( 252 | ); 253 | inputPaths = ( 254 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 255 | "${PODS_ROOT}/Manifest.lock", 256 | ); 257 | name = "[CP] Check Pods Manifest.lock"; 258 | outputPaths = ( 259 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 260 | ); 261 | runOnlyForDeploymentPostprocessing = 0; 262 | shellPath = /bin/sh; 263 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 264 | showEnvVarsInLog = 0; 265 | }; 266 | F304D45F0A7BB8C9C1336EF5 /* [CP] Embed Pods Frameworks */ = { 267 | isa = PBXShellScriptBuildPhase; 268 | buildActionMask = 2147483647; 269 | files = ( 270 | ); 271 | inputPaths = ( 272 | "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", 273 | "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework", 274 | "${BUILT_PRODUCTS_DIR}/flutter_dial_go/flutter_dial_go.framework", 275 | ); 276 | name = "[CP] Embed Pods Frameworks"; 277 | outputPaths = ( 278 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", 279 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_dial_go.framework", 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | shellPath = /bin/sh; 283 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 284 | showEnvVarsInLog = 0; 285 | }; 286 | /* End PBXShellScriptBuildPhase section */ 287 | 288 | /* Begin PBXSourcesBuildPhase section */ 289 | 97C146EA1CF9000F007C117D /* Sources */ = { 290 | isa = PBXSourcesBuildPhase; 291 | buildActionMask = 2147483647; 292 | files = ( 293 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 294 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 295 | ); 296 | runOnlyForDeploymentPostprocessing = 0; 297 | }; 298 | /* End PBXSourcesBuildPhase section */ 299 | 300 | /* Begin PBXVariantGroup section */ 301 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 302 | isa = PBXVariantGroup; 303 | children = ( 304 | 97C146FB1CF9000F007C117D /* Base */, 305 | ); 306 | name = Main.storyboard; 307 | sourceTree = ""; 308 | }; 309 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 310 | isa = PBXVariantGroup; 311 | children = ( 312 | 97C147001CF9000F007C117D /* Base */, 313 | ); 314 | name = LaunchScreen.storyboard; 315 | sourceTree = ""; 316 | }; 317 | /* End PBXVariantGroup section */ 318 | 319 | /* Begin XCBuildConfiguration section */ 320 | 97C147031CF9000F007C117D /* Debug */ = { 321 | isa = XCBuildConfiguration; 322 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 323 | buildSettings = { 324 | ALWAYS_SEARCH_USER_PATHS = NO; 325 | CLANG_ANALYZER_NONNULL = YES; 326 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 327 | CLANG_CXX_LIBRARY = "libc++"; 328 | CLANG_ENABLE_MODULES = YES; 329 | CLANG_ENABLE_OBJC_ARC = YES; 330 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 331 | CLANG_WARN_BOOL_CONVERSION = YES; 332 | CLANG_WARN_COMMA = YES; 333 | CLANG_WARN_CONSTANT_CONVERSION = YES; 334 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 335 | CLANG_WARN_EMPTY_BODY = YES; 336 | CLANG_WARN_ENUM_CONVERSION = YES; 337 | CLANG_WARN_INFINITE_RECURSION = YES; 338 | CLANG_WARN_INT_CONVERSION = YES; 339 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 340 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 341 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 342 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 343 | CLANG_WARN_STRICT_PROTOTYPES = YES; 344 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 345 | CLANG_WARN_UNREACHABLE_CODE = YES; 346 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 347 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 348 | COPY_PHASE_STRIP = NO; 349 | DEBUG_INFORMATION_FORMAT = dwarf; 350 | ENABLE_STRICT_OBJC_MSGSEND = YES; 351 | ENABLE_TESTABILITY = YES; 352 | FRAMEWORK_SEARCH_PATHS = ( 353 | "$(inherited)", 354 | "\"${PODS_CONFIGURATION_BUILD_DIR}/flutter_dial_go\"", 355 | "\"${PODS_ROOT}/../.symlinks/flutter/ios\"", 356 | "\"${PODS_ROOT}/../Frameworks\"", 357 | ); 358 | GCC_C_LANGUAGE_STANDARD = gnu99; 359 | GCC_DYNAMIC_NO_PIC = NO; 360 | GCC_NO_COMMON_BLOCKS = YES; 361 | GCC_OPTIMIZATION_LEVEL = 0; 362 | GCC_PREPROCESSOR_DEFINITIONS = ( 363 | "DEBUG=1", 364 | "$(inherited)", 365 | ); 366 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 367 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 368 | GCC_WARN_UNDECLARED_SELECTOR = YES; 369 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 370 | GCC_WARN_UNUSED_FUNCTION = YES; 371 | GCC_WARN_UNUSED_VARIABLE = YES; 372 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 373 | MTL_ENABLE_DEBUG_INFO = YES; 374 | ONLY_ACTIVE_ARCH = YES; 375 | SDKROOT = iphoneos; 376 | TARGETED_DEVICE_FAMILY = "1,2"; 377 | }; 378 | name = Debug; 379 | }; 380 | 97C147041CF9000F007C117D /* Release */ = { 381 | isa = XCBuildConfiguration; 382 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 383 | buildSettings = { 384 | ALWAYS_SEARCH_USER_PATHS = NO; 385 | CLANG_ANALYZER_NONNULL = YES; 386 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 387 | CLANG_CXX_LIBRARY = "libc++"; 388 | CLANG_ENABLE_MODULES = YES; 389 | CLANG_ENABLE_OBJC_ARC = YES; 390 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 391 | CLANG_WARN_BOOL_CONVERSION = YES; 392 | CLANG_WARN_COMMA = YES; 393 | CLANG_WARN_CONSTANT_CONVERSION = YES; 394 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 395 | CLANG_WARN_EMPTY_BODY = YES; 396 | CLANG_WARN_ENUM_CONVERSION = YES; 397 | CLANG_WARN_INFINITE_RECURSION = YES; 398 | CLANG_WARN_INT_CONVERSION = YES; 399 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 400 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 401 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 402 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 403 | CLANG_WARN_STRICT_PROTOTYPES = YES; 404 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 405 | CLANG_WARN_UNREACHABLE_CODE = YES; 406 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 407 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 408 | COPY_PHASE_STRIP = NO; 409 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 410 | ENABLE_NS_ASSERTIONS = NO; 411 | ENABLE_STRICT_OBJC_MSGSEND = YES; 412 | FRAMEWORK_SEARCH_PATHS = ( 413 | "$(inherited)", 414 | "\"${PODS_CONFIGURATION_BUILD_DIR}/flutter_dial_go\"", 415 | "\"${PODS_ROOT}/../.symlinks/flutter/ios\"", 416 | "\"${PODS_ROOT}/../Frameworks\"", 417 | ); 418 | GCC_C_LANGUAGE_STANDARD = gnu99; 419 | GCC_NO_COMMON_BLOCKS = YES; 420 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 421 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 422 | GCC_WARN_UNDECLARED_SELECTOR = YES; 423 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 424 | GCC_WARN_UNUSED_FUNCTION = YES; 425 | GCC_WARN_UNUSED_VARIABLE = YES; 426 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 427 | MTL_ENABLE_DEBUG_INFO = NO; 428 | SDKROOT = iphoneos; 429 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 430 | TARGETED_DEVICE_FAMILY = "1,2"; 431 | VALIDATE_PRODUCT = YES; 432 | }; 433 | name = Release; 434 | }; 435 | 97C147061CF9000F007C117D /* Debug */ = { 436 | isa = XCBuildConfiguration; 437 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 438 | buildSettings = { 439 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 440 | CLANG_ENABLE_MODULES = YES; 441 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 442 | ENABLE_BITCODE = NO; 443 | FRAMEWORK_SEARCH_PATHS = ( 444 | "$(inherited)", 445 | "$(PROJECT_DIR)/Flutter", 446 | ); 447 | INFOPLIST_FILE = Runner/Info.plist; 448 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 449 | LIBRARY_SEARCH_PATHS = ( 450 | "$(inherited)", 451 | "$(PROJECT_DIR)/Flutter", 452 | ); 453 | PRODUCT_BUNDLE_IDENTIFIER = io.github.empirefox.flutterDialGoExample; 454 | PRODUCT_NAME = "$(TARGET_NAME)"; 455 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 456 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 457 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 458 | SWIFT_VERSION = 4.0; 459 | VERSIONING_SYSTEM = "apple-generic"; 460 | }; 461 | name = Debug; 462 | }; 463 | 97C147071CF9000F007C117D /* Release */ = { 464 | isa = XCBuildConfiguration; 465 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 466 | buildSettings = { 467 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 468 | CLANG_ENABLE_MODULES = YES; 469 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 470 | ENABLE_BITCODE = NO; 471 | FRAMEWORK_SEARCH_PATHS = ( 472 | "$(inherited)", 473 | "$(PROJECT_DIR)/Flutter", 474 | ); 475 | INFOPLIST_FILE = Runner/Info.plist; 476 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 477 | LIBRARY_SEARCH_PATHS = ( 478 | "$(inherited)", 479 | "$(PROJECT_DIR)/Flutter", 480 | ); 481 | PRODUCT_BUNDLE_IDENTIFIER = io.github.empirefox.flutterDialGoExample; 482 | PRODUCT_NAME = "$(TARGET_NAME)"; 483 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 484 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 485 | SWIFT_VERSION = 4.0; 486 | VERSIONING_SYSTEM = "apple-generic"; 487 | }; 488 | name = Release; 489 | }; 490 | /* End XCBuildConfiguration section */ 491 | 492 | /* Begin XCConfigurationList section */ 493 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 494 | isa = XCConfigurationList; 495 | buildConfigurations = ( 496 | 97C147031CF9000F007C117D /* Debug */, 497 | 97C147041CF9000F007C117D /* Release */, 498 | ); 499 | defaultConfigurationIsVisible = 0; 500 | defaultConfigurationName = Release; 501 | }; 502 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 503 | isa = XCConfigurationList; 504 | buildConfigurations = ( 505 | 97C147061CF9000F007C117D /* Debug */, 506 | 97C147071CF9000F007C117D /* Release */, 507 | ); 508 | defaultConfigurationIsVisible = 0; 509 | defaultConfigurationName = Release; 510 | }; 511 | /* End XCConfigurationList section */ 512 | }; 513 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 514 | } 515 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_dial_go_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/widgets.dart'; 3 | 4 | import './src/app.dart'; 5 | 6 | void main() => runApp(MyApp()); 7 | -------------------------------------------------------------------------------- /example/lib/src/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:async'; 3 | import 'dart:convert'; 4 | import 'dart:io'; 5 | 6 | import 'package:grpc/grpc.dart'; 7 | import 'package:flutter_dial_go/flutter_dial_go.dart'; 8 | 9 | import './generated/helloworld.pb.dart'; 10 | import './generated/helloworld.pbgrpc.dart'; 11 | 12 | const channelId = 'flutter_dial_go'; 13 | const notificationId = 1; 14 | const importance = AndroidNotificationChannel.IMPORTANCE_HIGH; 15 | const httpPort = 9998; 16 | const grpcPort = 9999; 17 | 18 | class MyApp extends StatefulWidget { 19 | @override 20 | _MyAppState createState() => _MyAppState(); 21 | } 22 | 23 | class _MyAppState extends State { 24 | ClientChannel _channel; 25 | GreeterClient _stub; 26 | final String _name = 'world'; 27 | String _result = '...'; 28 | bool _requesting = false; 29 | 30 | // do grpc request 31 | Future _request() async { 32 | setState(() { 33 | _requesting = true; 34 | }); 35 | try { 36 | final response = await _stub.sayHello(HelloRequest()..name = _name); 37 | setState(() { 38 | _result = 'Greeter client received: ${response.message}'; 39 | _requesting = false; 40 | }); 41 | } catch (e) { 42 | setState(() { 43 | _result = 'Caught error: $e'; 44 | _requesting = false; 45 | }); 46 | } 47 | } 48 | 49 | @override 50 | void initState() { 51 | super.initState(); 52 | initGrpc(); 53 | } 54 | 55 | @override 56 | void dispose() { 57 | print('channel.terminate'); 58 | _channel.terminate(); 59 | super.dispose(); 60 | } 61 | 62 | Future _connect( 63 | String host, int port, ChannelCredentials credentials) async { 64 | // ignore: close_sinks 65 | final conn = await Conn.dial(port); 66 | return Http2Streams(conn.receiveStream, conn); 67 | } 68 | 69 | Future initGrpc() async { 70 | await Conn.notificationChannel( 71 | channelId: channelId, 72 | importance: importance, 73 | name: 'fdg running', 74 | description: 'keep fdg running', 75 | ); 76 | await Conn.startGoWithService( 77 | channelId: channelId, 78 | notificationId: notificationId, 79 | title: 'flutter_dial_go example', 80 | text: 'make flutter dial go', 81 | ); 82 | 83 | // http request 84 | Conn c = await Conn.dial(httpPort); 85 | print('GET /\n'); 86 | c.receiveStream 87 | .fold(BytesBuilder(), (BytesBuilder a, List b) => a..add(b)) 88 | .then((a) => setState(() { 89 | _result = 'GET / HTTP/1.0\n\n' + utf8.decode(a.takeBytes()); 90 | })) 91 | .catchError((e) => setState(() { 92 | _result = 'Caught http error: $e'; 93 | })) 94 | .then((_) => c.close()); 95 | c.add(utf8.encode('GET / HTTP/1.0\r\n\r\n')); 96 | 97 | // init grpc channel 98 | _channel = ClientChannel( 99 | 'go', 100 | port: grpcPort, 101 | options: ChannelOptions( 102 | credentials: ChannelCredentials.insecure(), 103 | connect: _connect, 104 | ), 105 | ); 106 | _stub = GreeterClient(_channel); 107 | } 108 | 109 | @override 110 | Widget build(BuildContext context) { 111 | return MaterialApp( 112 | home: Scaffold( 113 | appBar: AppBar( 114 | title: const Text('Plugin example app'), 115 | ), 116 | body: Center( 117 | child: Text('$_result\n'), 118 | ), 119 | floatingActionButton: FloatingActionButton( 120 | onPressed: _channel == null || _requesting 121 | ? null 122 | : () { 123 | _request(); 124 | }, 125 | tooltip: 'send request', 126 | child: Icon(Icons.send), 127 | ), 128 | ), 129 | ); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /example/lib/src/generated/helloworld.pb.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: helloworld.proto 4 | /// 5 | // ignore_for_file: non_constant_identifier_names,library_prefixes,unused_import 6 | 7 | // ignore: UNUSED_SHOWN_NAME 8 | import 'dart:core' show int, bool, double, String, List, Map, override; 9 | 10 | import 'package:protobuf/protobuf.dart' as $pb; 11 | 12 | class HelloRequest extends $pb.GeneratedMessage { 13 | static final $pb.BuilderInfo _i = new $pb.BuilderInfo('HelloRequest', package: const $pb.PackageName('helloworld')) 14 | ..aOS(1, 'name') 15 | ..hasRequiredFields = false 16 | ; 17 | 18 | HelloRequest() : super(); 19 | HelloRequest.fromBuffer(List i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) : super.fromBuffer(i, r); 20 | HelloRequest.fromJson(String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) : super.fromJson(i, r); 21 | HelloRequest clone() => new HelloRequest()..mergeFromMessage(this); 22 | HelloRequest copyWith(void Function(HelloRequest) updates) => super.copyWith((message) => updates(message as HelloRequest)); 23 | $pb.BuilderInfo get info_ => _i; 24 | static HelloRequest create() => new HelloRequest(); 25 | HelloRequest createEmptyInstance() => create(); 26 | static $pb.PbList createRepeated() => new $pb.PbList(); 27 | static HelloRequest getDefault() => _defaultInstance ??= create()..freeze(); 28 | static HelloRequest _defaultInstance; 29 | static void $checkItem(HelloRequest v) { 30 | if (v is! HelloRequest) $pb.checkItemFailed(v, _i.qualifiedMessageName); 31 | } 32 | 33 | String get name => $_getS(0, ''); 34 | set name(String v) { $_setString(0, v); } 35 | bool hasName() => $_has(0); 36 | void clearName() => clearField(1); 37 | } 38 | 39 | class HelloReply extends $pb.GeneratedMessage { 40 | static final $pb.BuilderInfo _i = new $pb.BuilderInfo('HelloReply', package: const $pb.PackageName('helloworld')) 41 | ..aOS(1, 'message') 42 | ..hasRequiredFields = false 43 | ; 44 | 45 | HelloReply() : super(); 46 | HelloReply.fromBuffer(List i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) : super.fromBuffer(i, r); 47 | HelloReply.fromJson(String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) : super.fromJson(i, r); 48 | HelloReply clone() => new HelloReply()..mergeFromMessage(this); 49 | HelloReply copyWith(void Function(HelloReply) updates) => super.copyWith((message) => updates(message as HelloReply)); 50 | $pb.BuilderInfo get info_ => _i; 51 | static HelloReply create() => new HelloReply(); 52 | HelloReply createEmptyInstance() => create(); 53 | static $pb.PbList createRepeated() => new $pb.PbList(); 54 | static HelloReply getDefault() => _defaultInstance ??= create()..freeze(); 55 | static HelloReply _defaultInstance; 56 | static void $checkItem(HelloReply v) { 57 | if (v is! HelloReply) $pb.checkItemFailed(v, _i.qualifiedMessageName); 58 | } 59 | 60 | String get message => $_getS(0, ''); 61 | set message(String v) { $_setString(0, v); } 62 | bool hasMessage() => $_has(0); 63 | void clearMessage() => clearField(1); 64 | } 65 | 66 | -------------------------------------------------------------------------------- /example/lib/src/generated/helloworld.pbenum.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: helloworld.proto 4 | /// 5 | // ignore_for_file: non_constant_identifier_names,library_prefixes,unused_import 6 | 7 | -------------------------------------------------------------------------------- /example/lib/src/generated/helloworld.pbgrpc.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: helloworld.proto 4 | /// 5 | // ignore_for_file: non_constant_identifier_names,library_prefixes,unused_import 6 | 7 | import 'dart:async' as $async; 8 | 9 | import 'package:grpc/grpc.dart' as $grpc; 10 | import 'helloworld.pb.dart'; 11 | export 'helloworld.pb.dart'; 12 | 13 | class GreeterClient extends $grpc.Client { 14 | static final _$sayHello = new $grpc.ClientMethod( 15 | '/helloworld.Greeter/SayHello', 16 | (HelloRequest value) => value.writeToBuffer(), 17 | (List value) => new HelloReply.fromBuffer(value)); 18 | 19 | GreeterClient($grpc.ClientChannel channel, {$grpc.CallOptions options}) 20 | : super(channel, options: options); 21 | 22 | $grpc.ResponseFuture sayHello(HelloRequest request, 23 | {$grpc.CallOptions options}) { 24 | final call = $createCall( 25 | _$sayHello, new $async.Stream.fromIterable([request]), 26 | options: options); 27 | return new $grpc.ResponseFuture(call); 28 | } 29 | } 30 | 31 | abstract class GreeterServiceBase extends $grpc.Service { 32 | String get $name => 'helloworld.Greeter'; 33 | 34 | GreeterServiceBase() { 35 | $addMethod(new $grpc.ServiceMethod( 36 | 'SayHello', 37 | sayHello_Pre, 38 | false, 39 | false, 40 | (List value) => new HelloRequest.fromBuffer(value), 41 | (HelloReply value) => value.writeToBuffer())); 42 | } 43 | 44 | $async.Future sayHello_Pre( 45 | $grpc.ServiceCall call, $async.Future request) async { 46 | return sayHello(call, await request); 47 | } 48 | 49 | $async.Future sayHello( 50 | $grpc.ServiceCall call, HelloRequest request); 51 | } 52 | -------------------------------------------------------------------------------- /example/lib/src/generated/helloworld.pbjson.dart: -------------------------------------------------------------------------------- 1 | /// 2 | // Generated code. Do not modify. 3 | // source: helloworld.proto 4 | /// 5 | // ignore_for_file: non_constant_identifier_names,library_prefixes,unused_import 6 | 7 | const HelloRequest$json = const { 8 | '1': 'HelloRequest', 9 | '2': const [ 10 | const {'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'}, 11 | ], 12 | }; 13 | 14 | const HelloReply$json = const { 15 | '1': 'HelloReply', 16 | '2': const [ 17 | const {'1': 'message', '3': 1, '4': 1, '5': 9, '10': 'message'}, 18 | ], 19 | }; 20 | 21 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_dial_go_example 2 | description: Demonstrates how to use the flutter_dial_go plugin. 3 | publish_to: 'none' 4 | 5 | environment: 6 | sdk: ">=2.1.0 <3.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | # The following adds the Cupertino Icons font to your application. 13 | # Use with the CupertinoIcons class for iOS style icons. 14 | cupertino_icons: ^0.1.2 15 | grpc: 16 | git: https://github.com/empirefox/grpc-dart.git 17 | protobuf: ^0.13.3 18 | dev_dependencies: 19 | flutter_test: 20 | sdk: flutter 21 | 22 | flutter_dial_go: 23 | path: ../ 24 | 25 | # For information on the generic Dart part of this file, see the 26 | # following page: https://www.dartlang.org/tools/pub/pubspec 27 | 28 | # The following section is specific to Flutter. 29 | flutter: 30 | 31 | # The following line ensures that the Material Icons font is 32 | # included with your application, so that you can use the icons in 33 | # the material Icons class. 34 | uses-material-design: true 35 | 36 | # To add assets to your application, add an assets section, like this: 37 | # assets: 38 | # - images/a_dot_burr.jpeg 39 | # - images/a_dot_ham.jpeg 40 | 41 | # An image asset can refer to one or more resolution-specific "variants", see 42 | # https://flutter.io/assets-and-images/#resolution-aware. 43 | 44 | # For details regarding adding assets from package dependencies, see 45 | # https://flutter.io/assets-and-images/#from-packages 46 | 47 | # To add custom fonts to your application, add a fonts section here, 48 | # in this "flutter" section. Each entry in this list should have a 49 | # "family" key with the font family name, and a "fonts" key with a 50 | # list giving the asset and other descriptors for the font. For 51 | # example: 52 | # fonts: 53 | # - family: Schyler 54 | # fonts: 55 | # - asset: fonts/Schyler-Regular.ttf 56 | # - asset: fonts/Schyler-Italic.ttf 57 | # style: italic 58 | # - family: Trajan Pro 59 | # fonts: 60 | # - asset: fonts/TrajanPro.ttf 61 | # - asset: fonts/TrajanPro_Bold.ttf 62 | # weight: 700 63 | # 64 | # For details regarding fonts from package dependencies, 65 | # see https://flutter.io/custom-fonts/#from-packages 66 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:flutter_dial_go_example/src/app.dart'; 12 | 13 | void main() { 14 | testWidgets('Verify Platform version', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => widget is Text && 22 | widget.data.startsWith('Running on:'), 23 | ), 24 | findsOneWidget, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /flutter_dial_go.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /go/Makefile: -------------------------------------------------------------------------------- 1 | #VERSION := $(shell git describe --tags) 2 | 3 | makefile_path := $(abspath $(lastword $(MAKEFILE_LIST))) 4 | makefile_dir := $(shell dirname ${makefile_path}) 5 | app_path := $(abspath $(makefile_dir)/../example) 6 | gomobile_pkg := github.com/empirefox/flutter_dial_go/go/example/gomobile 7 | 8 | echo: 9 | @APP_PATH=${app_path} \ 10 | GOMOBILE_PKG=${gomobile_pkg} \ 11 | make -f ${makefile_dir}/example/Makefile echo 12 | 13 | bind-android: 14 | @APP_PATH=${app_path} \ 15 | GOMOBILE_PKG=${gomobile_pkg} \ 16 | make -f ${makefile_dir}/example/Makefile bind-android 17 | 18 | cd ${makefile_dir}/../example/android/go && \ 19 | jar xf go.aar classes.jar && \ 20 | mv classes.jar go.jar && \ 21 | cp go.jar ${makefile_dir}/../android/libs/ && \ 22 | cp go-sources.jar ${makefile_dir}/../android/libs/ 23 | 24 | bind-ios: 25 | @APP_PATH=${app_path} \ 26 | GOMOBILE_PKG=${gomobile_pkg} \ 27 | make -f ${makefile_dir}/example/Makefile bind-ios 28 | 29 | protoc: 30 | @APP_PATH=${app_path} \ 31 | GOMOBILE_PKG=${gomobile_pkg} \ 32 | make -f ${makefile_dir}/example/Makefile protoc 33 | 34 | clean: 35 | @APP_PATH=${app_path} \ 36 | make -f ${makefile_dir}/example/Makefile clean 37 | rm -f ${makefile_dir}/../android/libs/*.aar 38 | rm -f ${makefile_dir}/../android/libs/*.jar 39 | -------------------------------------------------------------------------------- /go/example/Makefile: -------------------------------------------------------------------------------- 1 | #VERSION := $(shell git describe --tags) 2 | 3 | GOMOBILE_PKG := ${GOMOBILE_PKG} 4 | APP_PATH := ${APP_PATH} 5 | 6 | makefile_dir := $(shell dirname ${GOPATH}/src/${GOMOBILE_PKG}) 7 | 8 | echo: 9 | @echo ${GOMOBILE_PKG} 10 | @echo ${APP_PATH} 11 | 12 | bind-android: 13 | gomobile bind -target android -o ${APP_PATH}/android/go/go.aar \ 14 | ${GOMOBILE_PKG} \ 15 | github.com/empirefox/flutter_dial_go/go/formobile 16 | 17 | bind-ios: 18 | gomobile bind -target ios -o ${APP_PATH}/ios/Frameworks/Gomobile.framework \ 19 | ${GOMOBILE_PKG} \ 20 | github.com/empirefox/flutter_dial_go/go/formobile 21 | 22 | protoc: 23 | cd ${makefile_dir} && \ 24 | protoc -I ./protos --go_out=plugins=grpc:./protos ./protos/helloworld.proto 25 | 26 | cd ${makefile_dir} && \ 27 | protoc --dart_out=grpc:../../example/lib/src/generated -Iprotos protos/helloworld.proto 28 | 29 | clean: 30 | rm -f ${APP_PATH}/android/go/*.aar 31 | rm -f ${APP_PATH}/android/go/*.jar 32 | rm -rf ${APP_PATH}/ios/Frameworks/Gomobile.framework 33 | -------------------------------------------------------------------------------- /go/example/gomobile/mobile.go: -------------------------------------------------------------------------------- 1 | // gomobile must be created by developer. The package name must be gomobile. 2 | // These apis must/only be here: ConcurrentRunner, FromGo, NewFromGo. 3 | // For ios, do not forget add "${PODS_ROOT}/../Frameworks" to Framework search 4 | // path. Use `make ios` and `make android`. 5 | package gomobile 6 | 7 | import ( 8 | "io" 9 | 10 | "github.com/empirefox/flutter_dial_go/go/example/internal/gomobile" 11 | "github.com/empirefox/flutter_dial_go/go/forgo" 12 | ) 13 | 14 | type ConcurrentRunner interface { 15 | // Done will wait the runner to end. 16 | // Safe to be called multi times with multi threads. 17 | Done() error 18 | } 19 | 20 | type FromGo interface { 21 | // DoInitOnce do init things like listening. Do clean internally if err. 22 | // Safe to be called multi times with multi threads. 23 | DoInitOnce() ConcurrentRunner 24 | 25 | // DoDestroyOnce do clean things like freeing resources, closing listeners. 26 | // Safe to be called multi times with multi threads. 27 | DoDestroyOnce() ConcurrentRunner 28 | } 29 | 30 | func NewFromGo() FromGo { 31 | return &fromGo{ 32 | init: forgo.NewConcurrentRunner(), 33 | destroy: forgo.NewConcurrentRunner(), 34 | } 35 | } 36 | 37 | type fromGo struct { 38 | init *forgo.ConcurrentRunner 39 | destroy *forgo.ConcurrentRunner 40 | 41 | s1 io.Closer 42 | s2 io.Closer 43 | } 44 | 45 | func (m *fromGo) DoInitOnce() ConcurrentRunner { 46 | m.init.Once(func() error { 47 | // TODO init code here 48 | m.s1 = gomobile.StartServer1() 49 | m.s2 = gomobile.StartServer2() 50 | return nil 51 | }) 52 | return m.init 53 | } 54 | 55 | func (m *fromGo) DoDestroyOnce() ConcurrentRunner { 56 | m.destroy.Once(func() error { 57 | // TODO clean code here 58 | err := m.s1.Close() 59 | if e := m.s2.Close(); e != nil { 60 | err = e 61 | } 62 | return err 63 | }) 64 | return m.destroy 65 | } 66 | -------------------------------------------------------------------------------- /go/example/internal/gomobile/proxy.go: -------------------------------------------------------------------------------- 1 | package gomobile 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "net" 7 | "sync" 8 | ) 9 | 10 | var ( 11 | errProxyClosed = errors.New("proxy is closed") 12 | ) 13 | 14 | // proxy 15 | // TODO proxy conn of Dial to Accept 16 | type proxy struct { 17 | addr string 18 | pool sync.Pool 19 | connCh chan net.Conn 20 | doneOnce sync.Once 21 | done chan struct{} 22 | } 23 | 24 | func newProxy(addr string) *proxy { 25 | return &proxy{ 26 | addr: addr, 27 | connCh: make(chan net.Conn), 28 | done: make(chan struct{}), 29 | } 30 | } 31 | 32 | func (p *proxy) Dial(recvSafe io.WriteCloser) (io.WriteCloser, error) { 33 | dst, err := net.Dial("tcp", p.addr) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | closed := make(chan struct{}) 39 | go func() { 40 | select { 41 | case <-p.done: 42 | recvSafe.Close() 43 | dst.Close() 44 | case <-closed: 45 | } 46 | }() 47 | 48 | go func() { 49 | io.Copy(recvSafe, dst) 50 | recvSafe.Close() 51 | dst.Close() 52 | close(closed) 53 | }() 54 | 55 | return dst, nil 56 | } 57 | 58 | func (p *proxy) Put(b []byte) { 59 | p.pool.Put(b) 60 | } 61 | 62 | // Accept waits for and returns the next connection to the listener. 63 | func (p *proxy) Accept() (net.Conn, error) { 64 | select { 65 | case c := <-p.connCh: 66 | return c, nil 67 | case <-p.done: 68 | return nil, errProxyClosed 69 | } 70 | } 71 | 72 | // Close closes the listener. 73 | // Any blocked Accept operations will be unblocked and return errors. 74 | func (p *proxy) Close() error { 75 | p.doneOnce.Do(func() { close(p.done) }) 76 | return nil 77 | } 78 | 79 | // Addr returns the listener's network address. 80 | func (p *proxy) Addr() net.Addr { return proxyAddr{} } 81 | 82 | type proxyAddr struct{} 83 | 84 | func (proxyAddr) Network() string { return "proxy" } 85 | func (proxyAddr) String() string { return "proxy" } 86 | -------------------------------------------------------------------------------- /go/example/internal/gomobile/server.go: -------------------------------------------------------------------------------- 1 | package gomobile 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "net/http" 8 | 9 | pb "github.com/empirefox/flutter_dial_go/go/example/protos" 10 | "github.com/empirefox/flutter_dial_go/go/forgo" 11 | "github.com/labstack/echo" 12 | "github.com/labstack/echo/middleware" 13 | "golang.org/x/net/context" 14 | "google.golang.org/grpc" 15 | "google.golang.org/grpc/reflection" 16 | ) 17 | 18 | func StartServer1() io.Closer { 19 | ln, err := forgo.Listen(9998) 20 | if err != nil { 21 | // TODO report error 22 | log.Fatal(err) 23 | } 24 | 25 | // Echo instance 26 | e := echo.New() 27 | 28 | e.Listener = ln 29 | e.Debug = true 30 | 31 | // Middleware 32 | e.Use(middleware.Recover()) 33 | 34 | // Routes 35 | e.GET("/", hello) 36 | 37 | go func() { 38 | err := e.Start("") 39 | if err != nil { 40 | log.Fatal(err) 41 | } 42 | }() 43 | 44 | return e 45 | } 46 | 47 | func hello(c echo.Context) error { 48 | return c.String(http.StatusOK, "Hello from golang!") 49 | } 50 | 51 | // server is used to implement helloworld.GreeterServer. 52 | type server struct { 53 | no int 54 | } 55 | 56 | // SayHello implements helloworld.GreeterServer 57 | func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { 58 | s.no++ 59 | return &pb.HelloReply{Message: fmt.Sprintf("Hello %s: %d", in.Name, s.no)}, nil 60 | } 61 | 62 | type closer struct { 63 | *grpc.Server 64 | } 65 | 66 | func (c *closer) Close() error { 67 | c.Stop() 68 | return nil 69 | } 70 | 71 | func StartServer2() io.Closer { 72 | ln, err := forgo.Listen(9999) 73 | if err != nil { 74 | // TODO report error 75 | log.Fatal(err) 76 | } 77 | 78 | s := grpc.NewServer() 79 | pb.RegisterGreeterServer(s, &server{}) 80 | // Register reflection service on gRPC server. 81 | reflection.Register(s) 82 | go func() { 83 | err = s.Serve(ln) 84 | if err != nil { 85 | log.Fatalf("failed to serve grpc: %v", err) 86 | } 87 | }() 88 | 89 | return &closer{s} 90 | } 91 | -------------------------------------------------------------------------------- /go/example/protos/helloworld.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: helloworld.proto 3 | 4 | package helloworld 5 | 6 | import proto "github.com/golang/protobuf/proto" 7 | import fmt "fmt" 8 | import math "math" 9 | 10 | import ( 11 | context "golang.org/x/net/context" 12 | grpc "google.golang.org/grpc" 13 | ) 14 | 15 | // Reference imports to suppress errors if they are not otherwise used. 16 | var _ = proto.Marshal 17 | var _ = fmt.Errorf 18 | var _ = math.Inf 19 | 20 | // This is a compile-time assertion to ensure that this generated file 21 | // is compatible with the proto package it is being compiled against. 22 | // A compilation error at this line likely means your copy of the 23 | // proto package needs to be updated. 24 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 25 | 26 | // The request message containing the user's name. 27 | type HelloRequest struct { 28 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 29 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 30 | XXX_unrecognized []byte `json:"-"` 31 | XXX_sizecache int32 `json:"-"` 32 | } 33 | 34 | func (m *HelloRequest) Reset() { *m = HelloRequest{} } 35 | func (m *HelloRequest) String() string { return proto.CompactTextString(m) } 36 | func (*HelloRequest) ProtoMessage() {} 37 | func (*HelloRequest) Descriptor() ([]byte, []int) { 38 | return fileDescriptor_helloworld_b6b5131ee0360b9d, []int{0} 39 | } 40 | func (m *HelloRequest) XXX_Unmarshal(b []byte) error { 41 | return xxx_messageInfo_HelloRequest.Unmarshal(m, b) 42 | } 43 | func (m *HelloRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 44 | return xxx_messageInfo_HelloRequest.Marshal(b, m, deterministic) 45 | } 46 | func (dst *HelloRequest) XXX_Merge(src proto.Message) { 47 | xxx_messageInfo_HelloRequest.Merge(dst, src) 48 | } 49 | func (m *HelloRequest) XXX_Size() int { 50 | return xxx_messageInfo_HelloRequest.Size(m) 51 | } 52 | func (m *HelloRequest) XXX_DiscardUnknown() { 53 | xxx_messageInfo_HelloRequest.DiscardUnknown(m) 54 | } 55 | 56 | var xxx_messageInfo_HelloRequest proto.InternalMessageInfo 57 | 58 | func (m *HelloRequest) GetName() string { 59 | if m != nil { 60 | return m.Name 61 | } 62 | return "" 63 | } 64 | 65 | // The response message containing the greetings 66 | type HelloReply struct { 67 | Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` 68 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 69 | XXX_unrecognized []byte `json:"-"` 70 | XXX_sizecache int32 `json:"-"` 71 | } 72 | 73 | func (m *HelloReply) Reset() { *m = HelloReply{} } 74 | func (m *HelloReply) String() string { return proto.CompactTextString(m) } 75 | func (*HelloReply) ProtoMessage() {} 76 | func (*HelloReply) Descriptor() ([]byte, []int) { 77 | return fileDescriptor_helloworld_b6b5131ee0360b9d, []int{1} 78 | } 79 | func (m *HelloReply) XXX_Unmarshal(b []byte) error { 80 | return xxx_messageInfo_HelloReply.Unmarshal(m, b) 81 | } 82 | func (m *HelloReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 83 | return xxx_messageInfo_HelloReply.Marshal(b, m, deterministic) 84 | } 85 | func (dst *HelloReply) XXX_Merge(src proto.Message) { 86 | xxx_messageInfo_HelloReply.Merge(dst, src) 87 | } 88 | func (m *HelloReply) XXX_Size() int { 89 | return xxx_messageInfo_HelloReply.Size(m) 90 | } 91 | func (m *HelloReply) XXX_DiscardUnknown() { 92 | xxx_messageInfo_HelloReply.DiscardUnknown(m) 93 | } 94 | 95 | var xxx_messageInfo_HelloReply proto.InternalMessageInfo 96 | 97 | func (m *HelloReply) GetMessage() string { 98 | if m != nil { 99 | return m.Message 100 | } 101 | return "" 102 | } 103 | 104 | func init() { 105 | proto.RegisterType((*HelloRequest)(nil), "helloworld.HelloRequest") 106 | proto.RegisterType((*HelloReply)(nil), "helloworld.HelloReply") 107 | } 108 | 109 | // Reference imports to suppress errors if they are not otherwise used. 110 | var _ context.Context 111 | var _ grpc.ClientConn 112 | 113 | // This is a compile-time assertion to ensure that this generated file 114 | // is compatible with the grpc package it is being compiled against. 115 | const _ = grpc.SupportPackageIsVersion4 116 | 117 | // GreeterClient is the client API for Greeter service. 118 | // 119 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. 120 | type GreeterClient interface { 121 | // Sends a greeting 122 | SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) 123 | } 124 | 125 | type greeterClient struct { 126 | cc *grpc.ClientConn 127 | } 128 | 129 | func NewGreeterClient(cc *grpc.ClientConn) GreeterClient { 130 | return &greeterClient{cc} 131 | } 132 | 133 | func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) { 134 | out := new(HelloReply) 135 | err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, opts...) 136 | if err != nil { 137 | return nil, err 138 | } 139 | return out, nil 140 | } 141 | 142 | // GreeterServer is the server API for Greeter service. 143 | type GreeterServer interface { 144 | // Sends a greeting 145 | SayHello(context.Context, *HelloRequest) (*HelloReply, error) 146 | } 147 | 148 | func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) { 149 | s.RegisterService(&_Greeter_serviceDesc, srv) 150 | } 151 | 152 | func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 153 | in := new(HelloRequest) 154 | if err := dec(in); err != nil { 155 | return nil, err 156 | } 157 | if interceptor == nil { 158 | return srv.(GreeterServer).SayHello(ctx, in) 159 | } 160 | info := &grpc.UnaryServerInfo{ 161 | Server: srv, 162 | FullMethod: "/helloworld.Greeter/SayHello", 163 | } 164 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 165 | return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest)) 166 | } 167 | return interceptor(ctx, in, info, handler) 168 | } 169 | 170 | var _Greeter_serviceDesc = grpc.ServiceDesc{ 171 | ServiceName: "helloworld.Greeter", 172 | HandlerType: (*GreeterServer)(nil), 173 | Methods: []grpc.MethodDesc{ 174 | { 175 | MethodName: "SayHello", 176 | Handler: _Greeter_SayHello_Handler, 177 | }, 178 | }, 179 | Streams: []grpc.StreamDesc{}, 180 | Metadata: "helloworld.proto", 181 | } 182 | 183 | func init() { proto.RegisterFile("helloworld.proto", fileDescriptor_helloworld_b6b5131ee0360b9d) } 184 | 185 | var fileDescriptor_helloworld_b6b5131ee0360b9d = []byte{ 186 | // 140 bytes of a gzipped FileDescriptorProto 187 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0xc8, 0x48, 0xcd, 0xc9, 188 | 0xc9, 0x2f, 0xcf, 0x2f, 0xca, 0x49, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x42, 0x88, 189 | 0x28, 0x29, 0x71, 0xf1, 0x78, 0x80, 0x78, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x42, 190 | 0x5c, 0x2c, 0x79, 0x89, 0xb9, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x60, 0xb6, 0x92, 191 | 0x1a, 0x17, 0x17, 0x54, 0x4d, 0x41, 0x4e, 0xa5, 0x90, 0x04, 0x17, 0x7b, 0x6e, 0x6a, 0x71, 0x71, 192 | 0x62, 0x3a, 0x4c, 0x11, 0x8c, 0x6b, 0xe4, 0xc9, 0xc5, 0xee, 0x5e, 0x94, 0x9a, 0x5a, 0x92, 0x5a, 193 | 0x24, 0x64, 0xc7, 0xc5, 0x11, 0x9c, 0x58, 0x09, 0xd6, 0x25, 0x24, 0xa1, 0x87, 0xe4, 0x02, 0x64, 194 | 0xcb, 0xa4, 0xc4, 0xb0, 0xc8, 0x00, 0xad, 0x50, 0x62, 0x48, 0x62, 0x03, 0xbb, 0xd4, 0x18, 0x10, 195 | 0x00, 0x00, 0xff, 0xff, 0xd6, 0x53, 0x4e, 0xf7, 0xbd, 0x00, 0x00, 0x00, 196 | } 197 | -------------------------------------------------------------------------------- /go/example/protos/helloworld.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package helloworld; 4 | 5 | // The greeting service definition. 6 | service Greeter { 7 | // Sends a greeting 8 | rpc SayHello (HelloRequest) returns (HelloReply) {} 9 | } 10 | 11 | // The request message containing the user's name. 12 | message HelloRequest { 13 | string name = 1; 14 | } 15 | 16 | // The response message containing the greetings 17 | message HelloReply { 18 | string message = 1; 19 | } -------------------------------------------------------------------------------- /go/forgo/forgo.go: -------------------------------------------------------------------------------- 1 | // forgo only expose api for go. Do not compile directly with gomobile. 2 | package forgo 3 | 4 | import ( 5 | "net" 6 | "sync" 7 | 8 | "github.com/empirefox/flutter_dial_go/go/internal/listener" 9 | ) 10 | 11 | // ConcurrentRunner implement gomobile.ConcurrentRunner which exposed to mobile. 12 | type ConcurrentRunner struct { 13 | once sync.Once 14 | err error 15 | done chan struct{} 16 | } 17 | 18 | func NewConcurrentRunner() *ConcurrentRunner { 19 | return &ConcurrentRunner{ 20 | done: make(chan struct{}), 21 | } 22 | } 23 | 24 | func (a *ConcurrentRunner) Once(fn func() error) { 25 | a.once.Do(func() { 26 | go func() { 27 | a.err = fn() 28 | close(a.done) 29 | }() 30 | }) 31 | } 32 | 33 | func (a *ConcurrentRunner) Done() error { 34 | <-a.done 35 | return a.err 36 | } 37 | 38 | func Listen(port uint16) (net.Listener, error) { 39 | return listener.Listen(port) 40 | } 41 | -------------------------------------------------------------------------------- /go/formobile/formobile.go: -------------------------------------------------------------------------------- 1 | // formobile must be compiled with gomible. Do not use it in go. 2 | package formobile 3 | 4 | import ( 5 | "math" 6 | "time" 7 | 8 | "github.com/empirefox/flutter_dial_go/go/internal/listener" 9 | ) 10 | 11 | // for DataHandler/ClientRead and Write/ClientWrite, must sync with listener 12 | const ( 13 | ErrNone int8 = iota 14 | EOF 15 | ErrClosedPipe 16 | ErrTimeout 17 | ) 18 | 19 | type DataHandler interface { 20 | OnData(b []byte, n int32, err int8) 21 | } 22 | 23 | type WriteReturn struct { 24 | N int32 25 | Err int8 26 | } 27 | 28 | type Conn interface { 29 | // StartRead read buffered data to h. 30 | StartRead(h DataHandler) 31 | 32 | // Write writes data to the connection. 33 | // Write can be made to time out and return an Error with Timeout() == true 34 | // after a fixed time limit; see SetDeadline and SetWriteDeadline. 35 | Write(b []byte) *WriteReturn 36 | 37 | // Close closes the connection. 38 | // Any blocked Read or Write operations will be unblocked and return errors. 39 | Close() 40 | } 41 | 42 | type Dialer interface { 43 | Dial(port int32, channelId int64, timeoutnano int64) (Conn, error) 44 | } 45 | 46 | type conn struct { 47 | cc listener.ClientConn 48 | } 49 | 50 | func (c *conn) StartRead(h DataHandler) { 51 | go func() { 52 | onData := func(b []byte, n int32) { h.OnData(b, n, ErrNone) } 53 | err := ErrNone 54 | for { 55 | err = c.cc.ClientRead(onData) 56 | if err != ErrNone { 57 | h.OnData(nil, 0, err) 58 | return 59 | } 60 | } 61 | }() 62 | } 63 | 64 | func (c *conn) Write(b []byte) *WriteReturn { 65 | n, err := c.cc.ClientWrite(b) 66 | return &WriteReturn{int32(n), err} 67 | } 68 | 69 | func (c *conn) Close() { c.cc.Close() } 70 | 71 | type dialer struct{} 72 | 73 | func (d *dialer) Dial(port int32, channelId int64, timeoutnano int64) (Conn, error) { 74 | if port < 0 || port > math.MaxUint16 { 75 | return nil, listener.ErrInvalidPort 76 | } 77 | 78 | c, err := listener.Dial(uint16(port), channelId, time.Duration(timeoutnano)) 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | return &conn{c}, nil 84 | } 85 | 86 | var defaultDialer Dialer = new(dialer) 87 | 88 | func GetDialer() Dialer { return defaultDialer } 89 | -------------------------------------------------------------------------------- /go/internal/listener/listener.go: -------------------------------------------------------------------------------- 1 | package listener 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "net" 7 | "strconv" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | var ( 13 | ErrInvalidPort = errors.New("invalid port") 14 | errPortUsed = errors.New("port is already used") 15 | errDialTimeout = errors.New("dial timeout") 16 | errListenerNotFound = errors.New("listener not found") 17 | errListenerClosed = errors.New("listener is closed") 18 | ) 19 | 20 | var listeners sync.Map 21 | 22 | type Listener struct { 23 | port uint16 24 | addr addr 25 | connCh chan net.Conn 26 | doneCh chan struct{} 27 | closeOnce sync.Once 28 | } 29 | 30 | // Accept waits for and returns the next connection to the listener. 31 | func (ln *Listener) Accept() (net.Conn, error) { 32 | select { 33 | case c := <-ln.connCh: 34 | return c, nil 35 | case <-ln.doneCh: 36 | return nil, errListenerClosed 37 | } 38 | } 39 | 40 | // Close closes the listener. 41 | // Any blocked Accept operations will be unblocked and return errors. 42 | func (ln *Listener) Close() error { 43 | ln.closeOnce.Do(func() { 44 | listeners.Delete(ln.port) 45 | close(ln.doneCh) 46 | }) 47 | return nil 48 | } 49 | 50 | // Addr returns the listener's network address. 51 | func (ln *Listener) Addr() net.Addr { return &ln.addr } 52 | 53 | func Listen(port uint16) (net.Listener, error) { 54 | if port == 0 { 55 | return nil, ErrInvalidPort 56 | } 57 | 58 | _, ok := listeners.Load(port) 59 | if ok { 60 | return nil, errPortUsed 61 | } 62 | 63 | ln := &Listener{ 64 | port: port, 65 | addr: addr("go:" + strconv.FormatUint(uint64(port), 10)), 66 | connCh: make(chan net.Conn), 67 | doneCh: make(chan struct{}), 68 | } 69 | listeners.Store(port, ln) 70 | return ln, nil 71 | } 72 | 73 | func Dial(port uint16, channelId int64, timeout time.Duration) (ClientConn, error) { 74 | if port == 0 { 75 | return nil, ErrInvalidPort 76 | } 77 | 78 | v, ok := listeners.Load(port) 79 | if !ok { 80 | return nil, errListenerNotFound 81 | } 82 | 83 | ctx := context.Background() 84 | var cancel context.CancelFunc 85 | if timeout > 0 { 86 | ctx, cancel = context.WithTimeout(ctx, timeout) 87 | defer cancel() 88 | } 89 | 90 | ln := v.(*Listener) 91 | dialerAddr := addr("flutter:" + strconv.FormatInt(channelId, 10)) 92 | client, server := Pipe(&dialerAddr, ln.Addr()) 93 | select { 94 | case ln.connCh <- server: 95 | return client, nil 96 | case <-ln.doneCh: 97 | return nil, errListenerClosed 98 | case <-ctx.Done(): 99 | return nil, errDialTimeout 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /go/internal/listener/pipe.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package listener 6 | 7 | import ( 8 | "io" 9 | "net" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | // pipeDeadline is an abstraction for handling timeouts. 15 | type pipeDeadline struct { 16 | mu sync.Mutex // Guards timer and cancel 17 | timer *time.Timer 18 | cancel chan struct{} // Must be non-nil 19 | } 20 | 21 | func makePipeDeadline() pipeDeadline { 22 | return pipeDeadline{cancel: make(chan struct{})} 23 | } 24 | 25 | // set sets the point in time when the deadline will time out. 26 | // A timeout event is signaled by closing the channel returned by waiter. 27 | // Once a timeout has occurred, the deadline can be refreshed by specifying a 28 | // t value in the future. 29 | // 30 | // A zero value for t prevents timeout. 31 | func (d *pipeDeadline) set(t time.Time) { 32 | d.mu.Lock() 33 | defer d.mu.Unlock() 34 | 35 | if d.timer != nil && !d.timer.Stop() { 36 | <-d.cancel // Wait for the timer callback to finish and close cancel 37 | } 38 | d.timer = nil 39 | 40 | // Time is zero, then there is no deadline. 41 | closed := isClosedChan(d.cancel) 42 | if t.IsZero() { 43 | if closed { 44 | d.cancel = make(chan struct{}) 45 | } 46 | return 47 | } 48 | 49 | // Time in the future, setup a timer to cancel in the future. 50 | if dur := time.Until(t); dur > 0 { 51 | if closed { 52 | d.cancel = make(chan struct{}) 53 | } 54 | d.timer = time.AfterFunc(dur, func() { 55 | close(d.cancel) 56 | }) 57 | return 58 | } 59 | 60 | // Time in the past, so close immediately. 61 | if !closed { 62 | close(d.cancel) 63 | } 64 | } 65 | 66 | // wait returns a channel that is closed when the deadline is exceeded. 67 | func (d *pipeDeadline) wait() chan struct{} { 68 | d.mu.Lock() 69 | defer d.mu.Unlock() 70 | return d.cancel 71 | } 72 | 73 | func isClosedChan(c <-chan struct{}) bool { 74 | select { 75 | case <-c: 76 | return true 77 | default: 78 | return false 79 | } 80 | } 81 | 82 | type timeoutError struct{} 83 | 84 | func (timeoutError) Error() string { return "deadline exceeded" } 85 | func (timeoutError) Timeout() bool { return true } 86 | func (timeoutError) Temporary() bool { return true } 87 | 88 | type addr string 89 | 90 | func (a *addr) Network() string { return "fdg" } 91 | func (a *addr) String() string { return string(*a) } 92 | 93 | type pipe struct { 94 | wrMu sync.Mutex // Serialize Write operations 95 | 96 | // Used by local Read to interact with remote Write. 97 | // Successful receive on rdRx is always followed by send on rdTx. 98 | rdRx <-chan []byte 99 | rdTx chan<- int 100 | 101 | // Used by local Write to interact with remote Read. 102 | // Successful send on wrTx is always followed by receive on wrRx. 103 | wrTx chan<- []byte 104 | wrRx <-chan int 105 | 106 | once sync.Once // Protects closing localDone 107 | localDone chan struct{} 108 | remoteDone <-chan struct{} 109 | 110 | readDeadline pipeDeadline 111 | writeDeadline pipeDeadline 112 | 113 | localAddr net.Addr 114 | remoteAddr net.Addr 115 | } 116 | 117 | // Pipe creates a synchronous, in-memory, full duplex 118 | // network connection; both ends implement the Conn interface. 119 | // Reads on one end are matched with writes on the other, 120 | // copying data directly between the two; there is no internal 121 | // buffering. 122 | func Pipe(dialerAddr, listenerAddr net.Addr) (fordialer ClientConn, forlistener net.Conn) { 123 | cb1 := make(chan []byte) 124 | cb2 := make(chan []byte) 125 | cn1 := make(chan int) 126 | cn2 := make(chan int) 127 | done1 := make(chan struct{}) 128 | done2 := make(chan struct{}) 129 | 130 | fordialer = &pipe{ 131 | rdRx: cb1, rdTx: cn1, 132 | wrTx: cb2, wrRx: cn2, 133 | localDone: done1, remoteDone: done2, 134 | readDeadline: makePipeDeadline(), 135 | writeDeadline: makePipeDeadline(), 136 | localAddr: dialerAddr, 137 | remoteAddr: listenerAddr, 138 | } 139 | forlistener = &pipe{ 140 | rdRx: cb2, rdTx: cn2, 141 | wrTx: cb1, wrRx: cn1, 142 | localDone: done2, remoteDone: done1, 143 | readDeadline: makePipeDeadline(), 144 | writeDeadline: makePipeDeadline(), 145 | localAddr: listenerAddr, 146 | remoteAddr: dialerAddr, 147 | } 148 | return fordialer, forlistener 149 | } 150 | 151 | func (p *pipe) LocalAddr() net.Addr { return p.localAddr } 152 | func (p *pipe) RemoteAddr() net.Addr { return p.remoteAddr } 153 | 154 | func (p *pipe) Read(b []byte) (int, error) { 155 | n, err := p.read(b) 156 | if err != nil && err != io.EOF && err != io.ErrClosedPipe { 157 | err = &net.OpError{Op: "read", Net: "pipe", Err: err} 158 | } 159 | return n, err 160 | } 161 | 162 | func (p *pipe) read(b []byte) (n int, err error) { 163 | switch { 164 | case isClosedChan(p.localDone): 165 | return 0, io.ErrClosedPipe 166 | case isClosedChan(p.remoteDone): 167 | return 0, io.EOF 168 | case isClosedChan(p.readDeadline.wait()): 169 | return 0, timeoutError{} 170 | } 171 | 172 | select { 173 | case bw := <-p.rdRx: 174 | nr := copy(b, bw) 175 | p.rdTx <- nr 176 | return nr, nil 177 | case <-p.localDone: 178 | return 0, io.ErrClosedPipe 179 | case <-p.remoteDone: 180 | return 0, io.EOF 181 | case <-p.readDeadline.wait(): 182 | return 0, timeoutError{} 183 | } 184 | } 185 | 186 | func (p *pipe) Write(b []byte) (int, error) { 187 | n, err := p.write(b) 188 | if err != nil && err != io.ErrClosedPipe { 189 | err = &net.OpError{Op: "write", Net: "pipe", Err: err} 190 | } 191 | return n, err 192 | } 193 | 194 | func (p *pipe) write(b []byte) (n int, err error) { 195 | switch { 196 | case isClosedChan(p.localDone): 197 | return 0, io.ErrClosedPipe 198 | case isClosedChan(p.remoteDone): 199 | return 0, io.ErrClosedPipe 200 | case isClosedChan(p.writeDeadline.wait()): 201 | return 0, timeoutError{} 202 | } 203 | 204 | p.wrMu.Lock() // Ensure entirety of b is written together 205 | defer p.wrMu.Unlock() 206 | for once := true; once || len(b) > 0; once = false { 207 | select { 208 | case p.wrTx <- b: 209 | nw := <-p.wrRx 210 | b = b[nw:] 211 | n += nw 212 | case <-p.localDone: 213 | return n, io.ErrClosedPipe 214 | case <-p.remoteDone: 215 | return n, io.ErrClosedPipe 216 | case <-p.writeDeadline.wait(): 217 | return n, timeoutError{} 218 | } 219 | } 220 | return n, nil 221 | } 222 | 223 | func (p *pipe) SetDeadline(t time.Time) error { 224 | if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) { 225 | return io.ErrClosedPipe 226 | } 227 | p.readDeadline.set(t) 228 | p.writeDeadline.set(t) 229 | return nil 230 | } 231 | 232 | func (p *pipe) SetReadDeadline(t time.Time) error { 233 | if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) { 234 | return io.ErrClosedPipe 235 | } 236 | p.readDeadline.set(t) 237 | return nil 238 | } 239 | 240 | func (p *pipe) SetWriteDeadline(t time.Time) error { 241 | if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) { 242 | return io.ErrClosedPipe 243 | } 244 | p.writeDeadline.set(t) 245 | return nil 246 | } 247 | 248 | func (p *pipe) Close() error { 249 | p.once.Do(func() { close(p.localDone) }) 250 | return nil 251 | } 252 | -------------------------------------------------------------------------------- /go/internal/listener/pipefordialer.go: -------------------------------------------------------------------------------- 1 | package listener 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | // must sync with formobile 8 | const ( 9 | ErrNone int8 = iota 10 | EOF 11 | ErrClosedPipe 12 | ErrTimeout 13 | ) 14 | 15 | type ClientConn interface { 16 | net.Conn 17 | ClientRead(onData func(b []byte, n int32)) (err int8) 18 | ClientWrite(b []byte) (n int, err int8) 19 | } 20 | 21 | func (p *pipe) ClientRead(onData func(b []byte, n int32)) (err int8) { 22 | switch { 23 | case isClosedChan(p.localDone): 24 | return ErrClosedPipe 25 | case isClosedChan(p.remoteDone): 26 | return EOF 27 | case isClosedChan(p.readDeadline.wait()): 28 | return ErrTimeout 29 | } 30 | 31 | select { 32 | case bw := <-p.rdRx: 33 | n := len(bw) 34 | onData(bw, int32(n)) 35 | p.rdTx <- n 36 | return ErrNone 37 | case <-p.localDone: 38 | return ErrClosedPipe 39 | case <-p.remoteDone: 40 | return EOF 41 | case <-p.readDeadline.wait(): 42 | return ErrTimeout 43 | } 44 | } 45 | 46 | func (p *pipe) ClientWrite(b []byte) (n int, err int8) { 47 | switch { 48 | case isClosedChan(p.localDone): 49 | return 0, ErrClosedPipe 50 | case isClosedChan(p.remoteDone): 51 | return 0, ErrClosedPipe 52 | case isClosedChan(p.writeDeadline.wait()): 53 | return 0, ErrTimeout 54 | } 55 | 56 | p.wrMu.Lock() // Ensure entirety of b is written together 57 | defer p.wrMu.Unlock() 58 | for once := true; once || len(b) > 0; once = false { 59 | select { 60 | case p.wrTx <- b: 61 | nw := <-p.wrRx 62 | b = b[nw:] 63 | n += nw 64 | case <-p.localDone: 65 | return n, ErrClosedPipe 66 | case <-p.remoteDone: 67 | return n, ErrClosedPipe 68 | case <-p.writeDeadline.wait(): 69 | return n, ErrTimeout 70 | } 71 | } 72 | return n, ErrNone 73 | } 74 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/empirefox/flutter_dial_go/5bd3bae6801d81588d3c26ecdadae915c390f5a9/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/Dialer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Gomobile 3 | 4 | class Dialer { 5 | typealias OnResult = (_ error: Error?) -> Void 6 | 7 | private let dialer:FormobileDialerProtocol = FormobileGetDialer()! 8 | let dialQueue = DispatchQueue(label: "go.dial") 9 | private var go:GomobileFromGoProtocol? = nil 10 | private var destroyRunner:GomobileConcurrentRunnerProtocol? = nil 11 | 12 | func doInit(_ onResult: @escaping OnResult) { 13 | if go == nil { 14 | go = GomobileNewFromGo() 15 | } 16 | 17 | let runner = go!.doInitOnce()! 18 | dialQueue.async { 19 | do { 20 | try runner.done() 21 | DispatchQueue.main.async { onResult(nil) } 22 | } catch { 23 | DispatchQueue.main.async { onResult(error) } 24 | } 25 | } 26 | } 27 | 28 | func doDestroy(_ onResult: @escaping OnResult) { 29 | guard let go = self.go else { 30 | onResult(nil) 31 | return 32 | } 33 | self.go = nil 34 | 35 | let runner = go.doDestroyOnce()! 36 | destroyRunner = runner 37 | dialQueue.async { 38 | do { 39 | try runner.done() 40 | DispatchQueue.main.async { 41 | onResult(nil) 42 | self.destroyRunner = nil 43 | } 44 | } catch { 45 | DispatchQueue.main.async { 46 | onResult(error) 47 | self.destroyRunner = nil 48 | } 49 | } 50 | } 51 | } 52 | 53 | func isNotInitialized() -> Bool { 54 | return go == nil 55 | } 56 | 57 | func dial(port: Int32, channelId: Int64, timeoutnano:Int64) throws -> FormobileConnProtocol { 58 | return try dialer.dial(port, channelId:channelId, timeoutnano: timeoutnano) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ios/Classes/FlutterDialGoPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface FlutterDialGoPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /ios/Classes/FlutterDialGoPlugin.m: -------------------------------------------------------------------------------- 1 | #import "FlutterDialGoPlugin.h" 2 | #import 3 | 4 | @implementation FlutterDialGoPlugin 5 | + (void)registerWithRegistrar:(NSObject*)registrar { 6 | [SwiftFlutterDialGoPlugin registerWithRegistrar:registrar]; 7 | } 8 | @end 9 | -------------------------------------------------------------------------------- /ios/Classes/Pipe.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Flutter 3 | import Gomobile 4 | 5 | class Pipe: NSObject { 6 | static let streamPrefix = "fdgs#" 7 | 8 | let messenger:FlutterBinaryMessenger 9 | let controller:FlutterMethodChannel 10 | let conn:FormobileConnProtocol 11 | let id:Int64 12 | let streamName:String 13 | 14 | var closed = false 15 | 16 | init(messenger:FlutterBinaryMessenger, controller:FlutterMethodChannel, conn:FormobileConnProtocol, id:Int64) { 17 | self.messenger = messenger 18 | self.controller = controller 19 | self.conn = conn 20 | self.id = id 21 | self.streamName = Pipe.streamPrefix + String(id) 22 | } 23 | 24 | func start() { 25 | messenger.setMessageHandlerOnChannel(streamName) { 26 | message, reply in 27 | let r = self.conn.write(message)! 28 | let err = r.err() 29 | if err != FormobileErrNone { 30 | self.closeWithErr(err) 31 | } 32 | } 33 | 34 | conn.startRead(self) 35 | } 36 | 37 | func stop() { 38 | closeWithErr(FormobileErrNone) 39 | } 40 | 41 | private func closeWithErr(_ err: Int8) { 42 | if closed { 43 | return 44 | } 45 | closed = true 46 | 47 | messenger.setMessageHandlerOnChannel(streamName, binaryMessageHandler: nil) 48 | let args:NSArray = [id, err] 49 | controller.invokeMethod("close", arguments: args) 50 | conn.close() 51 | } 52 | } 53 | 54 | extension Pipe: FormobileDataHandlerProtocol { 55 | func onData(_ b: Data!, n: Int32, err: Int8) { 56 | DispatchQueue.main.sync { 57 | if n > 0 { 58 | messenger.send(onChannel: streamName, message: b) 59 | } 60 | if err != FormobileErrNone { 61 | closeWithErr(err) 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /ios/Classes/SwiftFlutterDialGoPlugin.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | public class SwiftFlutterDialGoPlugin: NSObject, FlutterPlugin { 5 | private let registrar: FlutterPluginRegistrar 6 | private let controller: FlutterMethodChannel 7 | private let dialer = Dialer() 8 | private var pipes = [Int64: Pipe]() 9 | 10 | public static func register(with registrar: FlutterPluginRegistrar) { 11 | let channel = FlutterMethodChannel(name: "flutter_dial_go", binaryMessenger: registrar.messenger()) 12 | let instance = SwiftFlutterDialGoPlugin(registrar: registrar, controller: channel) 13 | registrar.addMethodCallDelegate(instance, channel: channel) 14 | } 15 | 16 | init(registrar: FlutterPluginRegistrar, controller: FlutterMethodChannel) { 17 | self.registrar = registrar 18 | self.controller = controller 19 | } 20 | 21 | deinit { 22 | for pipe in pipes.values { 23 | pipe.stop() 24 | } 25 | pipes = [:] 26 | } 27 | 28 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 29 | switch call.method { 30 | case "initGo": 31 | handleInitGo(call, result: result) 32 | return 33 | case "destroyGo": 34 | handleDestroyGo(call, result: result) 35 | return 36 | default: 37 | break 38 | } 39 | 40 | if dialer.isNotInitialized() { 41 | result(FlutterError.init(code: call.method, message: "go not initialized", details: nil)) 42 | return 43 | } 44 | 45 | switch call.method { 46 | case "dial": 47 | handleDial(call, result: result) 48 | return 49 | case "close": 50 | handleClose(call, result: result) 51 | return 52 | default: 53 | break 54 | } 55 | 56 | result(FlutterMethodNotImplemented) 57 | } 58 | 59 | func handleInitGo(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 60 | dialer.doInit { 61 | error in 62 | if let err = error { 63 | result(FlutterError.init(code: "initGo", message: err.localizedDescription, details: nil)) 64 | } else { 65 | result(nil) 66 | } 67 | } 68 | } 69 | 70 | func handleDestroyGo(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 71 | dialer.doDestroy { 72 | error in 73 | if let err = error { 74 | result(FlutterError(code: "destroyGo", message: err.localizedDescription, details: nil)) 75 | } else { 76 | result(nil) 77 | } 78 | } 79 | } 80 | 81 | func handleDial(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 82 | dialer.dialQueue.async { 83 | let args:NSArray = call.arguments as! NSArray 84 | let port:Int32 = args[0] as! Int32 85 | let id:Int64 = args[1] as! Int64 86 | let timeoutnano:Int64 = args[2] as! Int64 87 | 88 | do { 89 | let conn = try self.dialer.dial(port: port, channelId: id, timeoutnano: timeoutnano) 90 | let pipe = Pipe(messenger: self.registrar.messenger(), controller: self.controller, conn: conn, id: id) 91 | DispatchQueue.main.async { 92 | pipe.start() 93 | self.pipes[id] = pipe 94 | result(nil) 95 | } 96 | } catch { 97 | DispatchQueue.main.async { 98 | result(FlutterError(code: "dial", message: error.localizedDescription, details: nil)) 99 | } 100 | } 101 | } 102 | } 103 | 104 | func handleClose(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 105 | let id:Int64 = call.arguments as! Int64 106 | if let pipe = pipes[id] { 107 | pipe.stop() 108 | pipes[id] = nil 109 | } 110 | result(nil) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /ios/flutter_dial_go.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 3 | # 4 | Pod::Spec.new do |s| 5 | s.name = 'flutter_dial_go' 6 | s.version = '0.0.1' 7 | s.summary = 'A new flutter plugin project.' 8 | s.description = <<-DESC 9 | A new flutter plugin project. 10 | DESC 11 | s.homepage = 'http://example.com' 12 | s.license = { :file => '../LICENSE' } 13 | s.author = { 'Your Company' => 'email@example.com' } 14 | s.source = { :path => '.' } 15 | s.source_files = 'Classes/**/*' 16 | s.public_header_files = 'Classes/**/*.h' 17 | s.dependency 'Flutter' 18 | 19 | s.ios.deployment_target = '8.0' 20 | end 21 | 22 | -------------------------------------------------------------------------------- /lib/flutter_dial_go.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'dart:typed_data'; 4 | 5 | import 'package:flutter/foundation.dart'; 6 | import 'package:flutter/services.dart'; 7 | 8 | // Conn is StreamSink>. Conn.receiveStream is Stream>. 9 | class Conn implements StreamSink> { 10 | static Map>> _localStreams = {}; 11 | 12 | static MethodChannel _initController() { 13 | MethodChannel controller = MethodChannel('flutter_dial_go'); 14 | controller.setMethodCallHandler((MethodCall call) async { 15 | switch (call.method) { 16 | case 'close': 17 | { 18 | List args = call.arguments; 19 | var id = args[0] as int; 20 | var err = _GoConnErr.values[args[1] as int]; 21 | var stream = _localStreams[id]; 22 | if (stream != null) { 23 | _localStreams.remove(id); 24 | switch (err) { 25 | case _GoConnErr.ErrClosedPipe: 26 | stream.addError('Go Closed Pipe'); 27 | break; 28 | case _GoConnErr.ErrTimeout: 29 | stream.addError('Go Timeout'); 30 | break; 31 | default: 32 | // not a real error 33 | } 34 | stream.close(); 35 | } 36 | return null; 37 | } 38 | } 39 | return null; 40 | }); 41 | return controller; 42 | } 43 | 44 | static MethodChannel _controller = _initController(); 45 | 46 | static Future notificationChannel({ 47 | String channelId, 48 | int importance, 49 | String name, 50 | String description, 51 | }) async { 52 | if (Platform.isIOS) return null; 53 | var args = [channelId, importance, name, description]; 54 | return await _controller.invokeMethod('notificationChannel', args); 55 | } 56 | 57 | static Future startGo() async { 58 | if (Platform.isAndroid) { 59 | await _controller.invokeMethod('noService'); 60 | } 61 | return await _controller.invokeMethod('initGo'); 62 | } 63 | 64 | static Future startGoWithService({ 65 | String channelId, 66 | int notificationId, 67 | String title, 68 | String text, 69 | }) async { 70 | if (Platform.isAndroid) { 71 | var args = [channelId, notificationId, title, text]; 72 | await _controller.invokeMethod('startService', args); 73 | } 74 | return await _controller.invokeMethod('initGo'); 75 | } 76 | 77 | static Future stopGo() async { 78 | await _controller.invokeMethod('destroyGo').catchError((err) { 79 | FlutterError.reportError(FlutterErrorDetails( 80 | exception: err, 81 | library: 'flutter_dial_go library', 82 | context: 'while destroyGo', 83 | )); 84 | }); 85 | if (Platform.isAndroid) await _controller.invokeMethod('stopService'); 86 | } 87 | 88 | static const String _streamPrefix = 'fdgs#'; 89 | static int _id = 0; 90 | final int id; 91 | 92 | StreamController> _localStream; 93 | final String _streamName; 94 | final Completer _sinkDone = Completer(); 95 | bool _sinkClosed = false; 96 | 97 | static Future dial(int port) async { 98 | var id = _id++; 99 | var timeoutnano = 0; 100 | var args = [port, id, timeoutnano]; 101 | await _controller.invokeMethod('dial', args); 102 | return Conn._private(id); 103 | } 104 | 105 | Conn._private(this.id) : _streamName = _streamPrefix + id.toString(); 106 | 107 | Stream> get receiveStream { 108 | if (_localStream == null) { 109 | _localStream = StreamController>( 110 | onListen: () async { 111 | _localStream.done.then((_) => close()); 112 | BinaryMessages.setMessageHandler(_streamName, (ByteData reply) async { 113 | if (_sinkClosed || reply == null) { 114 | _localStream.close(); 115 | return null; 116 | } 117 | 118 | try { 119 | _localStream.add(reply.buffer.asUint8List()); 120 | } on PlatformException catch (e) { 121 | _localStream.addError(e); 122 | } 123 | return null; 124 | }); 125 | }, 126 | onCancel: () => close(), 127 | ); 128 | _localStreams[id] = _localStream; 129 | } 130 | return _localStream.stream; 131 | } 132 | 133 | @override 134 | void add(List event) { 135 | var data = ByteData.view(Uint8List.fromList(event).buffer); 136 | BinaryMessages.send(_streamName, data); 137 | } 138 | 139 | @override 140 | void addError(Object err, [StackTrace stack]) { 141 | FlutterError.reportError(FlutterErrorDetails( 142 | exception: err, 143 | stack: stack, 144 | library: 'flutter_dial_go library', 145 | context: 'while de-activating platform stream on channel $_streamName', 146 | )); 147 | } 148 | 149 | @override 150 | Future addStream(Stream> stream) { 151 | return stream 152 | .listen( 153 | (data) => add(data), 154 | onDone: () => close(), 155 | onError: (Object err, [StackTrace stack]) => addError(err, stack), 156 | cancelOnError: false, 157 | ) 158 | .asFuture(); 159 | } 160 | 161 | @override 162 | Future close() async { 163 | if (_sinkClosed) return await done; 164 | _sinkClosed = true; 165 | 166 | BinaryMessages.setMessageHandler(_streamName, null); 167 | await _controller.invokeMethod('close', id); 168 | 169 | _sinkDone.complete(true); 170 | } 171 | 172 | @override 173 | Future get done => _sinkDone.future; 174 | } 175 | 176 | class AndroidNotificationChannel { 177 | static const int IMPORTANCE_UNSPECIFIED = 0xfffffc18; 178 | static const int IMPORTANCE_NONE = 0x00000000; 179 | static const int IMPORTANCE_MIN = 0x00000001; 180 | static const int IMPORTANCE_LOW = 0x00000002; 181 | static const int IMPORTANCE_DEFAULT = 0x00000003; 182 | static const int IMPORTANCE_HIGH = 0x00000004; 183 | } 184 | 185 | // github.com/empirefox/flutter_dial_go/go/formobile/formobile.go 186 | enum _GoConnErr { 187 | ErrNone, 188 | EOF, 189 | ErrClosedPipe, 190 | ErrTimeout, 191 | } 192 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://www.dartlang.org/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.0.8" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "1.0.4" 18 | charcode: 19 | dependency: transitive 20 | description: 21 | name: charcode 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.1.2" 25 | collection: 26 | dependency: transitive 27 | description: 28 | name: collection 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.14.11" 32 | flutter: 33 | dependency: "direct main" 34 | description: flutter 35 | source: sdk 36 | version: "0.0.0" 37 | flutter_test: 38 | dependency: "direct dev" 39 | description: flutter 40 | source: sdk 41 | version: "0.0.0" 42 | matcher: 43 | dependency: transitive 44 | description: 45 | name: matcher 46 | url: "https://pub.flutter-io.cn" 47 | source: hosted 48 | version: "0.12.3+1" 49 | meta: 50 | dependency: transitive 51 | description: 52 | name: meta 53 | url: "https://pub.flutter-io.cn" 54 | source: hosted 55 | version: "1.1.6" 56 | path: 57 | dependency: transitive 58 | description: 59 | name: path 60 | url: "https://pub.flutter-io.cn" 61 | source: hosted 62 | version: "1.6.2" 63 | pedantic: 64 | dependency: transitive 65 | description: 66 | name: pedantic 67 | url: "https://pub.flutter-io.cn" 68 | source: hosted 69 | version: "1.4.0" 70 | quiver: 71 | dependency: transitive 72 | description: 73 | name: quiver 74 | url: "https://pub.flutter-io.cn" 75 | source: hosted 76 | version: "2.0.1" 77 | sky_engine: 78 | dependency: transitive 79 | description: flutter 80 | source: sdk 81 | version: "0.0.99" 82 | source_span: 83 | dependency: transitive 84 | description: 85 | name: source_span 86 | url: "https://pub.flutter-io.cn" 87 | source: hosted 88 | version: "1.5.4" 89 | stack_trace: 90 | dependency: transitive 91 | description: 92 | name: stack_trace 93 | url: "https://pub.flutter-io.cn" 94 | source: hosted 95 | version: "1.9.3" 96 | stream_channel: 97 | dependency: transitive 98 | description: 99 | name: stream_channel 100 | url: "https://pub.flutter-io.cn" 101 | source: hosted 102 | version: "1.6.8" 103 | string_scanner: 104 | dependency: transitive 105 | description: 106 | name: string_scanner 107 | url: "https://pub.flutter-io.cn" 108 | source: hosted 109 | version: "1.0.4" 110 | term_glyph: 111 | dependency: transitive 112 | description: 113 | name: term_glyph 114 | url: "https://pub.flutter-io.cn" 115 | source: hosted 116 | version: "1.1.0" 117 | test_api: 118 | dependency: transitive 119 | description: 120 | name: test_api 121 | url: "https://pub.flutter-io.cn" 122 | source: hosted 123 | version: "0.2.2" 124 | typed_data: 125 | dependency: transitive 126 | description: 127 | name: typed_data 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "1.1.6" 131 | vector_math: 132 | dependency: transitive 133 | description: 134 | name: vector_math 135 | url: "https://pub.flutter-io.cn" 136 | source: hosted 137 | version: "2.0.8" 138 | sdks: 139 | dart: ">=2.1.0 <3.0.0" 140 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_dial_go 2 | description: A flutter plugin for connecting to golang embeded servers via platform channel. 3 | version: 0.0.1 4 | author: 5 | homepage: 6 | 7 | environment: 8 | sdk: ">=2.1.0 <3.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | dev_dependencies: 15 | flutter_test: 16 | sdk: flutter 17 | 18 | # For information on the generic Dart part of this file, see the 19 | # following page: https://www.dartlang.org/tools/pub/pubspec 20 | 21 | # The following section is specific to Flutter. 22 | flutter: 23 | # This section identifies this Flutter project as a plugin project. 24 | # The androidPackage and pluginClass identifiers should not ordinarily 25 | # be modified. They are used by the tooling to maintain consistency when 26 | # adding or updating assets for this project. 27 | plugin: 28 | androidPackage: io.github.empirefox.flutter_dial_go 29 | pluginClass: FlutterDialGoPlugin 30 | 31 | # To add assets to your plugin package, add an assets section, like this: 32 | # assets: 33 | # - images/a_dot_burr.jpeg 34 | # - images/a_dot_ham.jpeg 35 | # 36 | # For details regarding assets in packages, see 37 | # https://flutter.io/assets-and-images/#from-packages 38 | # 39 | # An image asset can refer to one or more resolution-specific "variants", see 40 | # https://flutter.io/assets-and-images/#resolution-aware. 41 | 42 | # To add custom fonts to your plugin package, add a fonts section here, 43 | # in this "flutter" section. Each entry in this list should have a 44 | # "family" key with the font family name, and a "fonts" key with a 45 | # list giving the asset and other descriptors for the font. For 46 | # example: 47 | # fonts: 48 | # - family: Schyler 49 | # fonts: 50 | # - asset: fonts/Schyler-Regular.ttf 51 | # - asset: fonts/Schyler-Italic.ttf 52 | # style: italic 53 | # - family: Trajan Pro 54 | # fonts: 55 | # - asset: fonts/TrajanPro.ttf 56 | # - asset: fonts/TrajanPro_Bold.ttf 57 | # weight: 700 58 | # 59 | # For details regarding fonts in packages, see 60 | # https://flutter.io/custom-fonts/#from-packages 61 | -------------------------------------------------------------------------------- /test/flutter_dial_go_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:flutter_dial_go/flutter_dial_go.dart'; 4 | 5 | void main() { 6 | const MethodChannel channel = MethodChannel('flutter_dial_go'); 7 | 8 | setUp(() { 9 | channel.setMockMethodCallHandler((MethodCall methodCall) async { 10 | return '42'; 11 | }); 12 | }); 13 | 14 | tearDown(() { 15 | channel.setMockMethodCallHandler(null); 16 | }); 17 | 18 | test('getPlatformVersion', () async { 19 | expect(await FlutterDialGo.platformVersion, '42'); 20 | }); 21 | } 22 | --------------------------------------------------------------------------------