27 changed files with 811 additions and 0 deletions
@ -0,0 +1,33 @@ |
|||||
|
# Miscellaneous |
||||
|
*.class |
||||
|
*.log |
||||
|
*.pyc |
||||
|
*.swp |
||||
|
.DS_Store |
||||
|
.atom/ |
||||
|
.build/ |
||||
|
.buildlog/ |
||||
|
.history |
||||
|
.svn/ |
||||
|
.swiftpm/ |
||||
|
migrate_working_dir/ |
||||
|
|
||||
|
# IntelliJ related |
||||
|
*.iml |
||||
|
*.ipr |
||||
|
*.iws |
||||
|
.idea/ |
||||
|
|
||||
|
# The .vscode folder contains launch configuration and tasks you configure in |
||||
|
# VS Code which you may wish to be included in version control, so this line |
||||
|
# is commented out by default. |
||||
|
#.vscode/ |
||||
|
|
||||
|
# Flutter/Dart/Pub related |
||||
|
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. |
||||
|
/pubspec.lock |
||||
|
**/doc/api/ |
||||
|
.dart_tool/ |
||||
|
.flutter-plugins |
||||
|
.flutter-plugins-dependencies |
||||
|
build/ |
||||
@ -0,0 +1,33 @@ |
|||||
|
# This file tracks properties of this Flutter project. |
||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc. |
||||
|
# |
||||
|
# This file should be version controlled and should not be manually edited. |
||||
|
|
||||
|
version: |
||||
|
revision: "35c388afb57ef061d06a39b537336c87e0e3d1b1" |
||||
|
channel: "stable" |
||||
|
|
||||
|
project_type: plugin |
||||
|
|
||||
|
# Tracks metadata for the flutter migrate command |
||||
|
migration: |
||||
|
platforms: |
||||
|
- platform: root |
||||
|
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 |
||||
|
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 |
||||
|
- platform: android |
||||
|
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 |
||||
|
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 |
||||
|
- platform: ios |
||||
|
create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 |
||||
|
base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1 |
||||
|
|
||||
|
# User provided section |
||||
|
|
||||
|
# List of Local paths (relative to this file) that should be |
||||
|
# ignored by the migrate tool. |
||||
|
# |
||||
|
# Files that are not part of the templates will be ignored by default. |
||||
|
unmanaged_files: |
||||
|
- 'lib/main.dart' |
||||
|
- 'ios/Runner.xcodeproj/project.pbxproj' |
||||
@ -0,0 +1,3 @@ |
|||||
|
## 0.0.1 |
||||
|
|
||||
|
* TODO: Describe initial release. |
||||
@ -0,0 +1 @@ |
|||||
|
TODO: Add your license here. |
||||
@ -0,0 +1,15 @@ |
|||||
|
# native_plugin |
||||
|
|
||||
|
A new Flutter plugin project. |
||||
|
|
||||
|
## Getting Started |
||||
|
|
||||
|
This project is a starting point for a Flutter |
||||
|
[plug-in package](https://flutter.dev/to/develop-plugins), |
||||
|
a specialized package that includes platform-specific implementation code for |
||||
|
Android and/or iOS. |
||||
|
|
||||
|
For help getting started with Flutter development, view the |
||||
|
[online documentation](https://docs.flutter.dev), which offers tutorials, |
||||
|
samples, guidance on mobile development, and a full API reference. |
||||
|
|
||||
@ -0,0 +1,4 @@ |
|||||
|
include: package:flutter_lints/flutter.yaml |
||||
|
|
||||
|
# Additional information about this file can be found at |
||||
|
# https://dart.dev/guides/language/analysis-options |
||||
@ -0,0 +1,9 @@ |
|||||
|
*.iml |
||||
|
.gradle |
||||
|
/local.properties |
||||
|
/.idea/workspace.xml |
||||
|
/.idea/libraries |
||||
|
.DS_Store |
||||
|
/build |
||||
|
/captures |
||||
|
.cxx |
||||
@ -0,0 +1,52 @@ |
|||||
|
group = "com.example.native_plugin" |
||||
|
version = "1.0" |
||||
|
|
||||
|
buildscript { |
||||
|
repositories { |
||||
|
google() |
||||
|
mavenCentral() |
||||
|
} |
||||
|
|
||||
|
dependencies { |
||||
|
classpath("com.android.tools.build:gradle:8.7.0") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
rootProject.allprojects { |
||||
|
repositories { |
||||
|
google() |
||||
|
mavenCentral() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
apply plugin: "com.android.library" |
||||
|
|
||||
|
android { |
||||
|
namespace = "com.example.native_plugin" |
||||
|
|
||||
|
compileSdk = 35 |
||||
|
|
||||
|
compileOptions { |
||||
|
sourceCompatibility = JavaVersion.VERSION_11 |
||||
|
targetCompatibility = JavaVersion.VERSION_11 |
||||
|
} |
||||
|
|
||||
|
defaultConfig { |
||||
|
minSdk = 21 |
||||
|
} |
||||
|
|
||||
|
dependencies { |
||||
|
testImplementation("junit:junit:4.13.2") |
||||
|
testImplementation("org.mockito:mockito-core:5.0.0") |
||||
|
} |
||||
|
|
||||
|
testOptions { |
||||
|
unitTests.all { |
||||
|
testLogging { |
||||
|
events "passed", "skipped", "failed", "standardOut", "standardError" |
||||
|
outputs.upToDateWhen {false} |
||||
|
showStandardStreams = true |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1 @@ |
|||||
|
rootProject.name = 'native_plugin' |
||||
@ -0,0 +1,3 @@ |
|||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
||||
|
package="com.example.native_plugin"> |
||||
|
</manifest> |
||||
@ -0,0 +1,38 @@ |
|||||
|
package com.example.native_plugin; |
||||
|
|
||||
|
import androidx.annotation.NonNull; |
||||
|
|
||||
|
import io.flutter.embedding.engine.plugins.FlutterPlugin; |
||||
|
import io.flutter.plugin.common.MethodCall; |
||||
|
import io.flutter.plugin.common.MethodChannel; |
||||
|
import io.flutter.plugin.common.MethodChannel.MethodCallHandler; |
||||
|
import io.flutter.plugin.common.MethodChannel.Result; |
||||
|
|
||||
|
/** NativePlugin */ |
||||
|
public class NativePlugin implements FlutterPlugin, MethodCallHandler { |
||||
|
/// The MethodChannel that will the communication between Flutter and native Android
|
||||
|
///
|
||||
|
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
|
||||
|
/// when the Flutter Engine is detached from the Activity
|
||||
|
private MethodChannel channel; |
||||
|
|
||||
|
@Override |
||||
|
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { |
||||
|
channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "native_plugin"); |
||||
|
channel.setMethodCallHandler(this); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { |
||||
|
if (call.method.equals("getPlatformVersion")) { |
||||
|
result.success("Android " + android.os.Build.VERSION.RELEASE); |
||||
|
} else { |
||||
|
result.notImplemented(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { |
||||
|
channel.setMethodCallHandler(null); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
package com.example.native_plugin; |
||||
|
|
||||
|
import static org.mockito.Mockito.mock; |
||||
|
import static org.mockito.Mockito.verify; |
||||
|
|
||||
|
import io.flutter.plugin.common.MethodCall; |
||||
|
import io.flutter.plugin.common.MethodChannel; |
||||
|
import org.junit.Test; |
||||
|
|
||||
|
/** |
||||
|
* This demonstrates a simple unit test of the Java portion of this plugin's implementation. |
||||
|
* |
||||
|
* Once you have built the plugin's example app, you can run these tests from the command |
||||
|
* line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or |
||||
|
* you can run them directly from IDEs that support JUnit such as Android Studio. |
||||
|
*/ |
||||
|
|
||||
|
public class NativePluginTest { |
||||
|
@Test |
||||
|
public void onMethodCall_getPlatformVersion_returnsExpectedValue() { |
||||
|
NativePlugin plugin = new NativePlugin(); |
||||
|
|
||||
|
final MethodCall call = new MethodCall("getPlatformVersion", null); |
||||
|
MethodChannel.Result mockResult = mock(MethodChannel.Result.class); |
||||
|
plugin.onMethodCall(call, mockResult); |
||||
|
|
||||
|
verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
.idea/ |
||||
|
.vagrant/ |
||||
|
.sconsign.dblite |
||||
|
.svn/ |
||||
|
|
||||
|
.DS_Store |
||||
|
*.swp |
||||
|
profile |
||||
|
|
||||
|
DerivedData/ |
||||
|
build/ |
||||
|
GeneratedPluginRegistrant.h |
||||
|
GeneratedPluginRegistrant.m |
||||
|
|
||||
|
.generated/ |
||||
|
|
||||
|
*.pbxuser |
||||
|
*.mode1v3 |
||||
|
*.mode2v3 |
||||
|
*.perspectivev3 |
||||
|
|
||||
|
!default.pbxuser |
||||
|
!default.mode1v3 |
||||
|
!default.mode2v3 |
||||
|
!default.perspectivev3 |
||||
|
|
||||
|
xcuserdata |
||||
|
|
||||
|
*.moved-aside |
||||
|
|
||||
|
*.pyc |
||||
|
*sync/ |
||||
|
Icon? |
||||
|
.tags* |
||||
|
|
||||
|
/Flutter/Generated.xcconfig |
||||
|
/Flutter/ephemeral/ |
||||
|
/Flutter/flutter_export_environment.sh |
||||
@ -0,0 +1,15 @@ |
|||||
|
#import <flutter/flutter.h> |
||||
|
|
||||
|
@interface FLNativeViewFactory : NSObject <FlutterPlatformViewFactory> |
||||
|
- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger> *)messenger; |
||||
|
@end |
||||
|
|
||||
|
@interface FLNativeView : NSObject <FlutterBinaryMessenger> |
||||
|
|
||||
|
- (instancetype)initWithFrame:(CGRect)frame |
||||
|
viewIdentifier:(int64_t)viewId |
||||
|
arguments:(id _Nullable)args |
||||
|
binaryMessenger:(NSObject<FlutterBinaryMessenger> *)messenger; |
||||
|
|
||||
|
- (UIView *)view; |
||||
|
@end |
||||
@ -0,0 +1,83 @@ |
|||||
|
#import "FLNativeView.h" |
||||
|
#import "PlayerView.h" |
||||
|
|
||||
|
@implementation FLNativeViewFactory { |
||||
|
NSObject<FlutterBinaryMessenger> *_messenger; |
||||
|
} |
||||
|
|
||||
|
- (instancetype)initWithMessenger: |
||||
|
(NSObject<FlutterBinaryMessenger> *)messenger { |
||||
|
self = [super init]; |
||||
|
if (self) { |
||||
|
_messenger = messenger; |
||||
|
} |
||||
|
return self; |
||||
|
} |
||||
|
|
||||
|
- (NSObject<FlutterPlatformView> *)createWithFrame:(CGRect)frame |
||||
|
viewIdentifier:(int64_t)viewId |
||||
|
arguments:(id _Nullable)args { |
||||
|
return [[FLNativeView alloc] initWithFrame:frame |
||||
|
viewIdentifier:viewId |
||||
|
arguments:args |
||||
|
binaryMessenger:_messenger]; |
||||
|
} |
||||
|
|
||||
|
/// Implementing this method is only necessary when the `arguments` in |
||||
|
/// `createWithFrame` is not `nil`. |
||||
|
- (NSObject<FlutterMessageCodec> *)createArgsCodec { |
||||
|
return [FlutterStandardMessageCodec sharedInstance]; |
||||
|
} |
||||
|
|
||||
|
@end |
||||
|
|
||||
|
@implementation FLNativeView { |
||||
|
PlayerView *_view; |
||||
|
FlutterMethodChannel *_methodChannel; |
||||
|
} |
||||
|
|
||||
|
- (instancetype)initWithFrame:(CGRect)frame |
||||
|
viewIdentifier:(int64_t)viewId |
||||
|
arguments:(id _Nullable)args |
||||
|
binaryMessenger:(NSObject<FlutterBinaryMessenger> *)messenger { |
||||
|
if (self = [super init]) { |
||||
|
_view = [[PlayerView alloc] init]; |
||||
|
_methodChannel = [FlutterMethodChannel |
||||
|
methodChannelWithName: |
||||
|
[NSString |
||||
|
stringWithFormat:@"native_plugin/native_view_%lld", viewId] |
||||
|
binaryMessenger:messenger]; |
||||
|
__weak typeof(self) weakSelf = self; |
||||
|
[_methodChannel setMethodCallHandler:^(FlutterMethodCall *_Nonnull call, |
||||
|
FlutterResult _Nonnull result) { |
||||
|
typeof(self) strongSelf = weakSelf; |
||||
|
if (strongSelf != nil) { |
||||
|
[strongSelf onMethodCall:call result:result]; |
||||
|
} |
||||
|
}]; |
||||
|
} |
||||
|
return self; |
||||
|
} |
||||
|
|
||||
|
- (UIView *)view { |
||||
|
return _view; |
||||
|
} |
||||
|
|
||||
|
- (void)onMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { |
||||
|
if ([@"getInternalView" isEqualToString:call.method]) { |
||||
|
result(@((uint64_t)(_view))); |
||||
|
} else if ([@"updateContent" isEqualToString:call.method]) { |
||||
|
NSDictionary *data = call.arguments; |
||||
|
NSString *lang = data[@"lang"]; |
||||
|
NSString *cn = data[@"cn"]; |
||||
|
NSString *en = data[@"en"]; |
||||
|
dispatch_async(dispatch_get_main_queue(), ^{ |
||||
|
[_view updateWithLang:lang cn:cn en:en]; |
||||
|
}); |
||||
|
result(nil); |
||||
|
} else { |
||||
|
result(@(0)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@end |
||||
@ -0,0 +1,4 @@ |
|||||
|
#import <Flutter/Flutter.h> |
||||
|
|
||||
|
@interface NativePlugin : NSObject<FlutterPlugin> |
||||
|
@end |
||||
@ -0,0 +1,114 @@ |
|||||
|
#import "NativePlugin.h" |
||||
|
#import "FLNativeView.h" |
||||
|
#import "PlayerView.h" |
||||
|
|
||||
|
#import <AVKit/AVKit.h> |
||||
|
|
||||
|
@interface NativePlugin () |
||||
|
|
||||
|
@property(nonatomic, strong) NSMutableArray *_Nonnull pipContentViewArray; |
||||
|
|
||||
|
@end |
||||
|
|
||||
|
@implementation NativePlugin |
||||
|
|
||||
|
+ (void)updateAudioSession { |
||||
|
AVAudioSession *audioSession = [AVAudioSession sharedInstance]; |
||||
|
NSError *categoryError = nil; |
||||
|
if (@available(iOS 14.5, *)) { |
||||
|
[audioSession |
||||
|
setCategory:AVAudioSessionCategoryPlayback |
||||
|
mode:AVAudioSessionModeMoviePlayback |
||||
|
options: |
||||
|
AVAudioSessionCategoryOptionOverrideMutedMicrophoneInterruption |
||||
|
error:&categoryError]; |
||||
|
} else { |
||||
|
// Fallback on earlier versions |
||||
|
} |
||||
|
if (categoryError) { |
||||
|
NSLog(@"Set audio session category error: %@", |
||||
|
categoryError.localizedDescription); |
||||
|
} |
||||
|
NSError *activeError = nil; |
||||
|
[audioSession setActive:YES error:&activeError]; |
||||
|
if (activeError) { |
||||
|
NSLog(@"Set audio session active error: %@", |
||||
|
activeError.localizedDescription); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar { |
||||
|
// must call this, otherwise pip will not possible |
||||
|
// https://github.com/jazzychad/PiPBugDemo |
||||
|
[NativePlugin updateAudioSession]; |
||||
|
|
||||
|
FlutterMethodChannel *channel = |
||||
|
[FlutterMethodChannel methodChannelWithName:@"native_plugin" |
||||
|
binaryMessenger:[registrar messenger]]; |
||||
|
NativePlugin *instance = [[NativePlugin alloc] init]; |
||||
|
|
||||
|
FLNativeViewFactory *factory = |
||||
|
[[FLNativeViewFactory alloc] initWithMessenger:registrar.messenger]; |
||||
|
[registrar registerViewFactory:factory withId:@"native_view"]; |
||||
|
|
||||
|
[registrar addMethodCallDelegate:instance channel:channel]; |
||||
|
} |
||||
|
|
||||
|
- (instancetype)init { |
||||
|
self = [super init]; |
||||
|
if (self) { |
||||
|
_pipContentViewArray = [NSMutableArray array]; |
||||
|
} |
||||
|
return self; |
||||
|
} |
||||
|
|
||||
|
- (void)handleMethodCall:(FlutterMethodCall *)call |
||||
|
result:(FlutterResult)result { |
||||
|
if ([@"getPlatformVersion" isEqualToString:call.method]) { |
||||
|
result([@"iOS " |
||||
|
stringByAppendingString:[[UIDevice currentDevice] systemVersion]]); |
||||
|
} else if ([@"createPipContentView" isEqualToString:call.method]) { |
||||
|
PlayerView *playerView = [[PlayerView alloc] init]; |
||||
|
|
||||
|
// should control the play state by event of pip, optimize it later |
||||
|
[playerView play]; |
||||
|
|
||||
|
[self.pipContentViewArray addObject:playerView]; |
||||
|
|
||||
|
result(@((uint64_t)playerView)); |
||||
|
} else if ([@"disposePipContentView" isEqualToString:call.method]) { |
||||
|
uint64_t viewId = [call.arguments unsignedLongLongValue]; |
||||
|
for (PlayerView *playerView in self.pipContentViewArray) { |
||||
|
if ((uint64_t)playerView == viewId) { |
||||
|
[playerView pause]; |
||||
|
[self.pipContentViewArray removeObject:playerView]; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} else if ([@"updatePipContentView" isEqualToString:call.method]) { |
||||
|
NSDictionary *data = call.arguments; |
||||
|
NSString *lang = data[@"lang"]; |
||||
|
NSString *cn = data[@"cn"]; |
||||
|
NSString *en = data[@"en"]; |
||||
|
NSNumber *viewIdNum = data[@"viewId"]; |
||||
|
dispatch_async(dispatch_get_main_queue(), ^{ |
||||
|
if (viewIdNum != nil) { |
||||
|
uint64_t targetId = viewIdNum.unsignedLongLongValue; |
||||
|
for (PlayerView *playerView in self.pipContentViewArray) { |
||||
|
if ((uint64_t)playerView == targetId) { |
||||
|
[playerView updateWithLang:lang cn:cn en:en]; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
PlayerView *playerView = [self.pipContentViewArray lastObject]; |
||||
|
[playerView updateWithLang:lang cn:cn en:en]; |
||||
|
} |
||||
|
}); |
||||
|
result(nil); |
||||
|
} else { |
||||
|
result(FlutterMethodNotImplemented); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@end |
||||
@ -0,0 +1,15 @@ |
|||||
|
#import <UIKit/UIKit.h> |
||||
|
|
||||
|
NS_ASSUME_NONNULL_BEGIN |
||||
|
|
||||
|
@interface PlayerView : UIView |
||||
|
|
||||
|
- (void)play; |
||||
|
|
||||
|
- (void)pause; |
||||
|
|
||||
|
- (void)updateWithLang:(NSString *)lang cn:(NSString *)cn en:(NSString *)en; |
||||
|
|
||||
|
@end |
||||
|
|
||||
|
NS_ASSUME_NONNULL_END |
||||
@ -0,0 +1,100 @@ |
|||||
|
#import "PlayerView.h" |
||||
|
|
||||
|
@interface PlayerView () |
||||
|
|
||||
|
@property(nonatomic, strong) UILabel *titleLabel; |
||||
|
@property(nonatomic, strong) UILabel *langLabel; |
||||
|
@property(nonatomic, strong) UILabel *cnLabel; |
||||
|
@property(nonatomic, strong) UILabel *enLabel; |
||||
|
@property(nonatomic, strong) UIStackView *stackView; |
||||
|
|
||||
|
@end |
||||
|
|
||||
|
@implementation PlayerView |
||||
|
|
||||
|
- (instancetype)init { |
||||
|
if (self = [super init]) { |
||||
|
self.backgroundColor = [UIColor whiteColor]; |
||||
|
|
||||
|
UILabel *title = [[UILabel alloc] init]; |
||||
|
title.text = @"实时翻译"; |
||||
|
title.font = [UIFont boldSystemFontOfSize:22]; |
||||
|
title.textColor = [UIColor blackColor]; |
||||
|
title.textAlignment = NSTextAlignmentCenter; |
||||
|
title.numberOfLines = 1; |
||||
|
self.titleLabel = title; |
||||
|
|
||||
|
UILabel *lang = [[UILabel alloc] init]; |
||||
|
lang.text = @"中文(简体) -> 英语"; |
||||
|
lang.font = [UIFont systemFontOfSize:14]; |
||||
|
lang.textColor = [UIColor darkGrayColor]; |
||||
|
lang.textAlignment = NSTextAlignmentLeft; |
||||
|
lang.numberOfLines = 1; |
||||
|
self.langLabel = lang; |
||||
|
|
||||
|
UILabel *cn = [[UILabel alloc] init]; |
||||
|
cn.font = [UIFont systemFontOfSize:18]; |
||||
|
cn.textColor = [UIColor blackColor]; |
||||
|
cn.textAlignment = NSTextAlignmentLeft; |
||||
|
cn.numberOfLines = 0; |
||||
|
self.cnLabel = cn; |
||||
|
|
||||
|
UILabel *en = [[UILabel alloc] init]; |
||||
|
en.font = [UIFont systemFontOfSize:18]; |
||||
|
en.textColor = [UIColor colorWithRed:0.24 green:0.54 blue:0.90 alpha:1.0]; |
||||
|
en.textAlignment = NSTextAlignmentLeft; |
||||
|
en.numberOfLines = 0; |
||||
|
self.enLabel = en; |
||||
|
|
||||
|
NSMutableParagraphStyle *para = [[NSMutableParagraphStyle alloc] init]; |
||||
|
para.lineSpacing = 4; |
||||
|
|
||||
|
self.cnLabel.attributedText = [[NSAttributedString alloc] initWithString:@"" attributes:@{NSParagraphStyleAttributeName: para}]; |
||||
|
|
||||
|
self.enLabel.attributedText = [[NSAttributedString alloc] initWithString:@"" attributes:@{NSParagraphStyleAttributeName: para}]; |
||||
|
|
||||
|
UIStackView *stack = [[UIStackView alloc] initWithArrangedSubviews:@[self.titleLabel, self.langLabel, self.cnLabel, self.enLabel]]; |
||||
|
stack.axis = UILayoutConstraintAxisVertical; |
||||
|
stack.spacing = 12; |
||||
|
stack.alignment = UIStackViewAlignmentFill; |
||||
|
stack.distribution = UIStackViewDistributionFill; |
||||
|
stack.translatesAutoresizingMaskIntoConstraints = NO; |
||||
|
self.stackView = stack; |
||||
|
|
||||
|
self.layoutMargins = UIEdgeInsetsMake(16, 16, 16, 16); |
||||
|
[self addSubview:self.stackView]; |
||||
|
|
||||
|
UILayoutGuide *guide = self.layoutMarginsGuide; |
||||
|
[NSLayoutConstraint activateConstraints:@[ |
||||
|
[self.stackView.topAnchor constraintEqualToAnchor:guide.topAnchor], |
||||
|
[self.stackView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor], |
||||
|
[self.stackView.trailingAnchor constraintEqualToAnchor:guide.trailingAnchor], |
||||
|
[self.stackView.bottomAnchor constraintLessThanOrEqualToAnchor:guide.bottomAnchor] |
||||
|
]]; |
||||
|
} |
||||
|
return self; |
||||
|
} |
||||
|
|
||||
|
- (void)play { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
- (void)pause { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
- (void)updateWithLang:(NSString *)lang cn:(NSString *)cn en:(NSString *)en { |
||||
|
if (lang != nil) { |
||||
|
self.langLabel.text = lang; |
||||
|
} |
||||
|
NSMutableParagraphStyle *para = [[NSMutableParagraphStyle alloc] init]; |
||||
|
para.lineSpacing = 4; |
||||
|
if (cn != nil) { |
||||
|
self.cnLabel.attributedText = [[NSAttributedString alloc] initWithString:cn attributes:@{NSParagraphStyleAttributeName: para}]; |
||||
|
} |
||||
|
if (en != nil) { |
||||
|
self.enLabel.attributedText = [[NSAttributedString alloc] initWithString:en attributes:@{NSParagraphStyleAttributeName: para}]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@end |
||||
@ -0,0 +1,14 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||
|
<plist version="1.0"> |
||||
|
<dict> |
||||
|
<key>NSPrivacyTrackingDomains</key> |
||||
|
<array/> |
||||
|
<key>NSPrivacyAccessedAPITypes</key> |
||||
|
<array/> |
||||
|
<key>NSPrivacyCollectedDataTypes</key> |
||||
|
<array/> |
||||
|
<key>NSPrivacyTracking</key> |
||||
|
<false/> |
||||
|
</dict> |
||||
|
</plist> |
||||
@ -0,0 +1,29 @@ |
|||||
|
# |
||||
|
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. |
||||
|
# Run `pod lib lint native_plugin.podspec` to validate before publishing. |
||||
|
# |
||||
|
Pod::Spec.new do |s| |
||||
|
s.name = 'native_plugin' |
||||
|
s.version = '0.0.1' |
||||
|
s.summary = 'A new Flutter plugin project.' |
||||
|
s.description = <<-DESC |
||||
|
A new Flutter plugin project. |
||||
|
DESC |
||||
|
s.homepage = 'http://example.com' |
||||
|
s.license = { :file => '../LICENSE' } |
||||
|
s.author = { 'Your Company' => 'email@example.com' } |
||||
|
s.source = { :path => '.' } |
||||
|
s.source_files = 'Classes/**/*' |
||||
|
s.public_header_files = 'Classes/**/*.h' |
||||
|
s.dependency 'Flutter' |
||||
|
s.platform = :ios, '12.0' |
||||
|
|
||||
|
# Flutter.framework does not contain a i386 slice. |
||||
|
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } |
||||
|
|
||||
|
# If your plugin requires a privacy manifest, for example if it uses any |
||||
|
# required reason APIs, update the PrivacyInfo.xcprivacy file to describe your |
||||
|
# plugin's privacy impact, and then uncomment this line. For more information, |
||||
|
# see https://developer.apple.com/documentation/bundleresources/privacy_manifest_files |
||||
|
# s.resource_bundles = {'native_plugin_privacy' => ['Resources/PrivacyInfo.xcprivacy']} |
||||
|
end |
||||
@ -0,0 +1,19 @@ |
|||||
|
import 'native_plugin_platform_interface.dart'; |
||||
|
|
||||
|
class NativePlugin { |
||||
|
Future<String?> getPlatformVersion() { |
||||
|
return NativePluginPlatform.instance.getPlatformVersion(); |
||||
|
} |
||||
|
|
||||
|
Future<int> createPipContentView() { |
||||
|
return NativePluginPlatform.instance.createPipContentView(); |
||||
|
} |
||||
|
|
||||
|
Future<void> updatePipContentView(Map data) { |
||||
|
return NativePluginPlatform.instance.updatePipContentView(data); |
||||
|
} |
||||
|
|
||||
|
Future<void> disposePipContentView(int viewId) { |
||||
|
return NativePluginPlatform.instance.disposePipContentView(viewId); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
import 'package:flutter/foundation.dart'; |
||||
|
import 'package:flutter/services.dart'; |
||||
|
|
||||
|
import 'native_plugin_platform_interface.dart'; |
||||
|
|
||||
|
/// An implementation of [NativePluginPlatform] that uses method channels. |
||||
|
class MethodChannelNativePlugin extends NativePluginPlatform { |
||||
|
/// The method channel used to interact with the native platform. |
||||
|
@visibleForTesting |
||||
|
final methodChannel = const MethodChannel('native_plugin'); |
||||
|
|
||||
|
@override |
||||
|
Future<String?> getPlatformVersion() async { |
||||
|
final version = await methodChannel.invokeMethod<String>( |
||||
|
'getPlatformVersion', |
||||
|
); |
||||
|
return version; |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Future<int> createPipContentView() async { |
||||
|
final viewId = await methodChannel.invokeMethod<int>( |
||||
|
'createPipContentView', |
||||
|
); |
||||
|
return viewId ?? 0; |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Future<void> updatePipContentView(Map data) async { |
||||
|
await methodChannel.invokeMethod<void>('updatePipContentView', data); |
||||
|
} |
||||
|
|
||||
|
@override |
||||
|
Future<void> disposePipContentView(int viewId) async { |
||||
|
await methodChannel.invokeMethod<void>('disposePipContentView', viewId); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
import 'package:plugin_platform_interface/plugin_platform_interface.dart'; |
||||
|
|
||||
|
import 'native_plugin_method_channel.dart'; |
||||
|
|
||||
|
abstract class NativePluginPlatform extends PlatformInterface { |
||||
|
/// Constructs a NativePluginPlatform. |
||||
|
NativePluginPlatform() : super(token: _token); |
||||
|
|
||||
|
static final Object _token = Object(); |
||||
|
|
||||
|
static NativePluginPlatform _instance = MethodChannelNativePlugin(); |
||||
|
|
||||
|
/// The default instance of [NativePluginPlatform] to use. |
||||
|
/// |
||||
|
/// Defaults to [MethodChannelNativePlugin]. |
||||
|
static NativePluginPlatform get instance => _instance; |
||||
|
|
||||
|
/// Platform-specific implementations should set this with their own |
||||
|
/// platform-specific class that extends [NativePluginPlatform] when |
||||
|
/// they register themselves. |
||||
|
static set instance(NativePluginPlatform instance) { |
||||
|
PlatformInterface.verifyToken(instance, _token); |
||||
|
_instance = instance; |
||||
|
} |
||||
|
|
||||
|
Future<String?> getPlatformVersion() { |
||||
|
throw UnimplementedError('platformVersion() has not been implemented.'); |
||||
|
} |
||||
|
|
||||
|
Future<int> createPipContentView() { |
||||
|
throw UnimplementedError( |
||||
|
'createPipContentView() has not been implemented.', |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
Future<void> updatePipContentView(Map data) { |
||||
|
throw UnimplementedError( |
||||
|
'updatePipContentView() has not been implemented.', |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
Future<void> disposePipContentView(int viewId) { |
||||
|
throw UnimplementedError( |
||||
|
'disposePipContentView() has not been implemented.', |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,72 @@ |
|||||
|
name: native_plugin |
||||
|
description: "A new Flutter plugin project." |
||||
|
version: 0.0.1 |
||||
|
homepage: |
||||
|
|
||||
|
environment: |
||||
|
sdk: ^3.7.0 |
||||
|
flutter: '>=3.3.0' |
||||
|
|
||||
|
dependencies: |
||||
|
flutter: |
||||
|
sdk: flutter |
||||
|
plugin_platform_interface: ^2.0.2 |
||||
|
|
||||
|
dev_dependencies: |
||||
|
flutter_test: |
||||
|
sdk: flutter |
||||
|
flutter_lints: ^5.0.0 |
||||
|
|
||||
|
# For information on the generic Dart part of this file, see the |
||||
|
# following page: https://dart.dev/tools/pub/pubspec |
||||
|
|
||||
|
# The following section is specific to Flutter packages. |
||||
|
flutter: |
||||
|
# This section identifies this Flutter project as a plugin project. |
||||
|
# The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) |
||||
|
# which should be registered in the plugin registry. This is required for |
||||
|
# using method channels. |
||||
|
# The Android 'package' specifies package in which the registered class is. |
||||
|
# This is required for using method channels on Android. |
||||
|
# The 'ffiPlugin' specifies that native code should be built and bundled. |
||||
|
# This is required for using `dart:ffi`. |
||||
|
# All these are used by the tooling to maintain consistency when |
||||
|
# adding or updating assets for this project. |
||||
|
plugin: |
||||
|
platforms: |
||||
|
android: |
||||
|
package: com.example.native_plugin |
||||
|
pluginClass: NativePlugin |
||||
|
ios: |
||||
|
pluginClass: NativePlugin |
||||
|
|
||||
|
# To add assets to your plugin package, add an assets section, like this: |
||||
|
# assets: |
||||
|
# - images/a_dot_burr.jpeg |
||||
|
# - images/a_dot_ham.jpeg |
||||
|
# |
||||
|
# For details regarding assets in packages, see |
||||
|
# https://flutter.dev/to/asset-from-package |
||||
|
# |
||||
|
# An image asset can refer to one or more resolution-specific "variants", see |
||||
|
# https://flutter.dev/to/resolution-aware-images |
||||
|
|
||||
|
# To add custom fonts to your plugin package, add a fonts section here, |
||||
|
# in this "flutter" section. Each entry in this list should have a |
||||
|
# "family" key with the font family name, and a "fonts" key with a |
||||
|
# list giving the asset and other descriptors for the font. For |
||||
|
# example: |
||||
|
# fonts: |
||||
|
# - family: Schyler |
||||
|
# fonts: |
||||
|
# - asset: fonts/Schyler-Regular.ttf |
||||
|
# - asset: fonts/Schyler-Italic.ttf |
||||
|
# style: italic |
||||
|
# - family: Trajan Pro |
||||
|
# fonts: |
||||
|
# - asset: fonts/TrajanPro.ttf |
||||
|
# - asset: fonts/TrajanPro_Bold.ttf |
||||
|
# weight: 700 |
||||
|
# |
||||
|
# For details regarding fonts in packages, see |
||||
|
# https://flutter.dev/to/font-from-package |
||||
Loading…
Reference in new issue