Commit 7ead767f authored by nightfallsad's avatar nightfallsad Committed by GitHub

Merge pull request #9 from alibaba/master

update
parents 14d8bfa0 1571d96c
......@@ -17,3 +17,4 @@ example/android/app/.classpath
example/android/app/.project
example/android/.project
flutter_boost
.flutter-plugins-dependencies
......@@ -9,7 +9,7 @@ addons:
- libstdc++6
# - fonts-droid
before_script:
- git clone https://github.com/flutter/flutter.git -b v1.12.13-hotfixes --depth 1
- git clone https://github.com/flutter/flutter.git -b flutter-1.17-candidate.3 --depth 1
- ./flutter/bin/flutter doctor
script:
- ./flutter/bin/flutter test --coverage --coverage-path=lcov.info
......
......@@ -30,7 +30,7 @@ bool isTopContainer = FlutterBoost.BoostContainer.of(context).onstage
回答:无障碍模式下目前Flutter Engine有bug,已经提交issue和PR给flutter啦。请参考这个issue:https://github.com/alibaba/flutter_boost/issues/488及其分析。提交给flutter的PR见这里:https://github.com/flutter/engine/pull/14155
### 5. 在ios模拟器下运行最新的flutter boost会闪退
回答:如上面第4条所说的,最新的flutter engine在voice over下有bug,会导致crash。因为模拟器下flutter默认会将voice over模式打开,所以其实就是辅助模式,这触发上面的bug:“在ios中voice over打开,demo在点击交互会crash”。
回答:如上面第4条所说的,最新的flutter engine在voice over下有bug,会导致crash。因为模拟器下flutter默认会将voice over模式打开,所以其实就是辅助模式,这触发上面的bug:“在ios中voice over打开,demo在点击交互会crash”。
可参考Engine的代码注释:
```c++
#if TARGET_OS_SIMULATOR
......
......@@ -56,7 +56,11 @@ flutter_boost:
# Boost Integration
Please see the boost example for details.
Please see
1. Boost detail example
2. integrated document <a href="INTEGRATION.md">Integration </a>
# FAQ
please read this document:
......
......@@ -55,7 +55,10 @@ flutter_boost:
## boost集成
集成请看boost的Examples
集成请看:
1. boost的Examples
2. 集成文档 <a href="INTEGRATION.md">Integration </a>
......
......@@ -64,6 +64,18 @@ public class FlutterBoost {
//fix crash:'FlutterBoostPlugin not register yet'
//case: initFlutter after Activity.OnCreate method,and then called start/stop crash
// In SplashActivity ,showDialog(in OnCreate method) to check permission, if authorized, then init sdk and jump homePage)
// fix bug : The LauncherActivity will be launch by clicking app icon when app enter background in HuaWei Rom, cause missing forgoround event
if(mEnterActivityCreate && mCurrentActiveActivity == null) {
Intent intent = activity.getIntent();
if (!activity.isTaskRoot()
&& intent != null
&& intent.hasCategory(Intent.CATEGORY_LAUNCHER)
&& intent.getAction() != null
&& intent.getAction().equals(Intent.ACTION_MAIN)) {
return;
}
}
mEnterActivityCreate = true;
mCurrentActiveActivity = activity;
if (mPlatform.whenEngineStart() == ConfigBuilder.ANY_ACTIVITY_CREATED) {
......
......@@ -74,6 +74,8 @@ public class FlutterViewContainerManager implements IContainerManager {
}
void popRecord(IContainerRecord record) {
if(mRecordStack.empty()) return;
if(mRecordStack.peek() == record) {
mRecordStack.pop();
}
......
package com.idlefish.flutterboost;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.util.AttributeSet;
import android.view.Surface;
import android.view.TextureView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.Log;
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.RenderSurface;
public class XFlutterTextureView extends TextureView implements RenderSurface {
private static final String TAG = "FlutterTextureView";
private boolean isSurfaceAvailableForRendering = false;
private boolean isAttachedToFlutterRenderer = false;
@Nullable
private FlutterRenderer flutterRenderer;
private Surface renderSurface;
// Connects the {@code SurfaceTexture} beneath this {@code TextureView} with Flutter's native code.
// Callbacks are received by this Object and then those messages are forwarded to our
// FlutterRenderer, and then on to the JNI bridge over to native Flutter code.
private final SurfaceTextureListener surfaceTextureListener = new SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
Log.v(TAG, "SurfaceTextureListener.onSurfaceTextureAvailable()");
isSurfaceAvailableForRendering = true;
// If we're already attached to a FlutterRenderer then we're now attached to both a renderer
// and the Android window, so we can begin rendering now.
if (isAttachedToFlutterRenderer) {
connectSurfaceToRenderer();
}
}
@Override
public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
Log.v(TAG, "SurfaceTextureListener.onSurfaceTextureSizeChanged()");
if (isAttachedToFlutterRenderer) {
changeSurfaceSize(width, height);
}
}
@Override
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
// Invoked every time a new frame is available. We don't care.
}
@Override
public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
Log.v(TAG, "SurfaceTextureListener.onSurfaceTextureDestroyed()");
isSurfaceAvailableForRendering = false;
// If we're attached to a FlutterRenderer then we need to notify it that our SurfaceTexture
// has been destroyed.
if (isAttachedToFlutterRenderer) {
disconnectSurfaceFromRenderer();
}
// Return true to indicate that no further painting will take place
// within this SurfaceTexture.
return true;
}
};
/**
* Constructs a {@code FlutterTextureView} programmatically, without any XML attributes.
*/
public XFlutterTextureView(@NonNull Context context) {
this(context, null);
}
/**
* Constructs a {@code FlutterTextureView} in an XML-inflation-compliant manner.
*/
public XFlutterTextureView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
// Listen for when our underlying SurfaceTexture becomes available, changes size, or
// gets destroyed, and take the appropriate actions.
setSurfaceTextureListener(surfaceTextureListener);
}
@Nullable
@Override
public FlutterRenderer getAttachedRenderer() {
return flutterRenderer;
}
/**
* Invoked by the owner of this {@code FlutterTextureView} when it wants to begin rendering
* a Flutter UI to this {@code FlutterTextureView}.
*
* If an Android {@link SurfaceTexture} is available, this method will give that
* {@link SurfaceTexture} to the given {@link FlutterRenderer} to begin rendering
* Flutter's UI to this {@code FlutterTextureView}.
*
* If no Android {@link SurfaceTexture} is available yet, this {@code FlutterTextureView}
* will wait until a {@link SurfaceTexture} becomes available and then give that
* {@link SurfaceTexture} to the given {@link FlutterRenderer} to begin rendering
* Flutter's UI to this {@code FlutterTextureView}.
*/
public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {
Log.v(TAG, "Attaching to FlutterRenderer.");
if (this.flutterRenderer != null) {
Log.v(TAG, "Already connected to a FlutterRenderer. Detaching from old one and attaching to new one.");
this.flutterRenderer.stopRenderingToSurface();
}
this.flutterRenderer = flutterRenderer;
isAttachedToFlutterRenderer = true;
// If we're already attached to an Android window then we're now attached to both a renderer
// and the Android window. We can begin rendering now.
if (isSurfaceAvailableForRendering) {
Log.v(TAG, "Surface is available for rendering. Connecting FlutterRenderer to Android surface.");
connectSurfaceToRenderer();
}
}
/**
* Invoked by the owner of this {@code FlutterTextureView} when it no longer wants to render
* a Flutter UI to this {@code FlutterTextureView}.
*
* This method will cease any on-going rendering from Flutter to this {@code FlutterTextureView}.
*/
public void detachFromRenderer() {
if (flutterRenderer != null) {
// If we're attached to an Android window then we were rendering a Flutter UI. Now that
// this FlutterTextureView is detached from the FlutterRenderer, we need to stop rendering.
// TODO(mattcarroll): introduce a isRendererConnectedToSurface() to wrap "getWindowToken() != null"
if (getWindowToken() != null) {
Log.v(TAG, "Disconnecting FlutterRenderer from Android surface.");
disconnectSurfaceFromRenderer();
}
flutterRenderer = null;
isAttachedToFlutterRenderer = false;
} else {
Log.w(TAG, "detachFromRenderer() invoked when no FlutterRenderer was attached.");
}
}
// FlutterRenderer and getSurfaceTexture() must both be non-null.
private void connectSurfaceToRenderer() {
if (flutterRenderer == null || getSurfaceTexture() == null) {
throw new IllegalStateException("connectSurfaceToRenderer() should only be called when flutterRenderer and getSurfaceTexture() are non-null.");
}
// flutterRenderer.startRenderingToSurface(new Surface(getSurfaceTexture()));
renderSurface = new Surface(getSurfaceTexture());
flutterRenderer.startRenderingToSurface(renderSurface);
}
// FlutterRenderer must be non-null.
private void changeSurfaceSize(int width, int height) {
if (flutterRenderer == null) {
throw new IllegalStateException("changeSurfaceSize() should only be called when flutterRenderer is non-null.");
}
Log.v(TAG, "Notifying FlutterRenderer that Android surface size has changed to " + width + " x " + height);
flutterRenderer.surfaceChanged(width, height);
}
// FlutterRenderer must be non-null.
private void disconnectSurfaceFromRenderer() {
if (flutterRenderer == null) {
throw new IllegalStateException("disconnectSurfaceFromRenderer() should only be called when flutterRenderer is non-null.");
}
flutterRenderer.stopRenderingToSurface();
if(renderSurface!=null){
renderSurface.release();
renderSurface = null;
}
}
}
\ No newline at end of file
......@@ -38,32 +38,12 @@ import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
import io.flutter.embedding.engine.renderer.RenderSurface;
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
import io.flutter.plugin.editing.TextInputPlugin;
import io.flutter.plugin.platform.PlatformViewsController;
import io.flutter.view.AccessibilityBridge;
/**
* Displays a Flutter UI on an Android device.
* <p>
* A {@code FlutterView}'s UI is painted by a corresponding {@link FlutterEngine}.
* <p>
* A {@code FlutterView} can operate in 2 different {@link RenderMode}s:
* <ol>
* <li>{@link RenderMode#surface}, which paints a Flutter UI to a {@link android.view.SurfaceView}.
* This mode has the best performance, but a {@code FlutterView} in this mode cannot be positioned
* between 2 other Android {@code View}s in the z-index, nor can it be animated/transformed.
* Unless the special capabilities of a {@link android.graphics.SurfaceTexture} are required,
* developers should strongly prefer this render mode.</li>
* <li>{@link RenderMode#texture}, which paints a Flutter UI to a {@link android.graphics.SurfaceTexture}.
* This mode is not as performant as {@link RenderMode#surface}, but a {@code FlutterView} in this
* mode can be animated and transformed, as well as positioned in the z-index between 2+ other
* Android {@code Views}. Unless the special capabilities of a {@link android.graphics.SurfaceTexture}
* are required, developers should strongly prefer the {@link RenderMode#surface} render mode.</li>
* </ol>
* See <a>https://source.android.com/devices/graphics/arch-tv#surface_or_texture</a> for more
* information comparing {@link android.view.SurfaceView} and {@link android.view.TextureView}.
*/
public class XFlutterView extends FrameLayout {
private static final String TAG = "FlutterView";
......@@ -150,26 +130,12 @@ public class XFlutterView extends FrameLayout {
this(context, null, null, null);
}
/**
* Constructs a {@code FlutterView} programmatically, without any XML attributes,
* and allows selection of a {@link #renderMode}.
* <p>
* {@link #transparencyMode} defaults to {@link TransparencyMode#opaque}.
* <p>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/
public XFlutterView(@NonNull Context context, @NonNull FlutterView.RenderMode renderMode) {
this(context, null, renderMode, null);
}
/**
* Constructs a {@code FlutterView} programmatically, without any XML attributes,
* assumes the use of {@link RenderMode#surface}, and allows selection of a {@link #transparencyMode}.
* <p>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/
public XFlutterView(@NonNull Context context, @NonNull FlutterView.TransparencyMode transparencyMode) {
this(context, null, FlutterView.RenderMode.surface, transparencyMode);
}
......@@ -217,7 +183,7 @@ public class XFlutterView extends FrameLayout {
break;
case texture:
Log.v(TAG, "Internally using a FlutterTextureView.");
FlutterTextureView flutterTextureView = new FlutterTextureView(getContext());
XFlutterTextureView flutterTextureView = new XFlutterTextureView(getContext());
renderSurface = flutterTextureView;
addView(flutterTextureView);
break;
......@@ -434,7 +400,7 @@ public class XFlutterView extends FrameLayout {
*/
@Override
public boolean checkInputConnectionProxy(View view) {
return flutterEngine != null
return flutterEngine != null&&view!=null
? flutterEngine.getPlatformViewsController().checkInputConnectionProxy(view)
: super.checkInputConnectionProxy(view);
}
......@@ -609,16 +575,9 @@ public class XFlutterView extends FrameLayout {
this.flutterEngine.getPlatformViewsController().attachToView(this);
if(textInputPlugin==null){
textInputPlugin = new XTextInputPlugin(
this,
flutterEngine.getTextInputChannel(),
this.flutterEngine.getPlatformViewsController()
);
}
textInputPlugin.setTextInputMethodHandler();
textInputPlugin= XTextInputPlugin.getTextInputPlugin( this.flutterEngine.getDartExecutor(),
this.flutterEngine.getPlatformViewsController());
textInputPlugin.updateView(this);
textInputPlugin.getInputMethodManager().restartInput(this);
......@@ -717,7 +676,7 @@ public class XFlutterView extends FrameLayout {
}
public void release(){
if(textInputPlugin!=null){
textInputPlugin.release();
textInputPlugin.release(this);
}
}
......@@ -788,9 +747,17 @@ public class XFlutterView extends FrameLayout {
*/
private void sendUserSettingsToFlutter() {
if(flutterEngine!=null&&flutterEngine.getSettingsChannel()!=null){
flutterEngine.getSettingsChannel().startMessage()
// Lookup the current brightness of the Android OS.
boolean isNightModeOn = (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
SettingsChannel.PlatformBrightness brightness = isNightModeOn
? SettingsChannel.PlatformBrightness.dark
: SettingsChannel.PlatformBrightness.light;
flutterEngine.getSettingsChannel().startMessage()
.setTextScaleFactor(getResources().getConfiguration().fontScale)
.setUse24HourFormat(DateFormat.is24HourFormat(getContext()))
.setPlatformBrightness(brightness)
.send();
}
}
......
......@@ -31,6 +31,7 @@ import io.flutter.plugin.common.ErrorLogResult;
import io.flutter.plugin.common.MethodChannel;
class XInputConnectionAdaptor extends BaseInputConnection {
private final View mFlutterView;
private final int mClient;
private final TextInputChannel textInputChannel;
......@@ -221,7 +222,6 @@ class XInputConnectionAdaptor extends BaseInputConnection {
mEditable.delete(selStart, selEnd);
mEditable.insert(selStart, String.valueOf((char) character));
setSelection(selStart + 1, selStart + 1);
updateEditingState();
}
return true;
}
......
......@@ -28,8 +28,8 @@ public class XPlatformPlugin {
public static final int DEFAULT_SYSTEM_UI = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
private Activity activity;
private PlatformChannel platformChannel;
private Activity activity;
private PlatformChannel platformChannel;
private PlatformChannel.SystemChromeStyle currentTheme;
private int mEnabledOverlays;
......@@ -95,26 +95,25 @@ public class XPlatformPlugin {
}
};
public XPlatformPlugin( PlatformChannel platformChannel) {
public XPlatformPlugin(PlatformChannel platformChannel) {
this.platformChannel = platformChannel;
this.platformChannel.setPlatformMessageHandler(mPlatformMessageHandler);
mEnabledOverlays = DEFAULT_SYSTEM_UI;
}
public void attachToActivity(Activity activity ){
this.activity = activity;
this.platformChannel.setPlatformMessageHandler(mPlatformMessageHandler);
}
/**
* Releases all resources held by this {@code PlatformPlugin}.
* <p>
* Do not invoke any methods on a {@code PlatformPlugin} after invoking this method.
*/
public void detachActivity() {
this.activity=null;
this.mPlatformMessageHandler=null;
public void detachActivity(Activity activity) {
if (activity == this.activity) {
this.activity = null;
}
}
private void playSystemSound(PlatformChannel.SoundType soundType) {
......
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package com.idlefish.flutterboost;
import android.annotation.SuppressLint;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.os.Build;
import android.provider.Settings;
import android.text.Editable;
import android.text.InputType;
import android.text.Selection;
......@@ -11,6 +17,11 @@ import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
......@@ -21,11 +32,11 @@ import io.flutter.plugin.platform.PlatformViewsController;
*/
public class XTextInputPlugin {
@NonNull
private View mView;
private View mView;
@NonNull
private final InputMethodManager mImm;
private InputMethodManager mImm;
@NonNull
private final TextInputChannel textInputChannel;
private TextInputChannel textInputChannel;
@NonNull
private InputTarget inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0);
@Nullable
......@@ -37,26 +48,43 @@ public class XTextInputPlugin {
private InputConnection lastInputConnection;
@NonNull
private PlatformViewsController platformViewsController;
private boolean restartAlwaysRequired;
// When true following calls to createInputConnection will return the cached lastInputConnection if the input
// target is a platform view. See the comments on lockPlatformViewInputConnection for more details.
private boolean isInputConnectionLocked;
private static XTextInputPlugin xTextInputPlugin;
public static XTextInputPlugin getTextInputPlugin( DartExecutor dartExecutor, @NonNull PlatformViewsController platformViewsController){
if(xTextInputPlugin!=null) return xTextInputPlugin;
xTextInputPlugin =new XTextInputPlugin(dartExecutor,platformViewsController);
return xTextInputPlugin;
}
public XTextInputPlugin(@NonNull DartExecutor dartExecutor, @NonNull PlatformViewsController platformViewsController) {
public XTextInputPlugin(View view, @NonNull TextInputChannel textInputChannel, @NonNull PlatformViewsController platformViewsController) {
mView = view;
mImm = (InputMethodManager) view.getContext().getSystemService(
Context.INPUT_METHOD_SERVICE);
this.textInputChannel = textInputChannel;
textInputChannel = new TextInputChannel(dartExecutor);
textInputChannel.requestExistingInputState();
this.platformViewsController = platformViewsController;
// this.platformViewsController.attachTextInputPlugin(this);
}
public void release() {
mView = null;
public void release(View v){
if(mView!=null && mView.hashCode()==v.hashCode()){
mView= null;
}
}
public void setTextInputMethodHandler() {
public void updateView(View view){
mView = view;
mImm = (InputMethodManager) view.getContext().getSystemService(
Context.INPUT_METHOD_SERVICE);
textInputChannel.setTextInputMethodHandler(new TextInputChannel.TextInputMethodHandler() {
@Override
......@@ -89,8 +117,12 @@ public class XTextInputPlugin {
clearTextInputClient();
}
});
restartAlwaysRequired = isRestartAlwaysRequired();
}
@NonNull
public InputMethodManager getInputMethodManager() {
return mImm;
......@@ -114,7 +146,7 @@ public class XTextInputPlugin {
/**
* Unlocks the input connection.
* <p>
*
* See also: @{link lockPlatformViewInputConnection}.
*/
public void unlockPlatformViewInputConnection() {
......@@ -123,7 +155,7 @@ public class XTextInputPlugin {
/**
* Detaches the text input plugin from the platform views controller.
* <p>
*
* The TextInputPlugin instance should not be used after calling this.
*/
public void destroy() {
......@@ -134,6 +166,7 @@ public class XTextInputPlugin {
TextInputChannel.InputType type,
boolean obscureText,
boolean autocorrect,
boolean enableSuggestions,
TextInputChannel.TextCapitalization textCapitalization
) {
if (type.type == TextInputChannel.TextInputType.DATETIME) {
......@@ -168,6 +201,7 @@ public class XTextInputPlugin {
textType |= InputType.TYPE_TEXT_VARIATION_PASSWORD;
} else {
if (autocorrect) textType |= InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
if (!enableSuggestions) textType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
}
if (textCapitalization == TextInputChannel.TextCapitalization.CHARACTERS) {
......@@ -191,19 +225,15 @@ public class XTextInputPlugin {
if (isInputConnectionLocked) {
return lastInputConnection;
}
View platformView = platformViewsController.getPlatformViewById(inputTarget.id);
if (platformView != null) {
lastInputConnection = platformView.onCreateInputConnection(outAttrs);
return lastInputConnection;
} else {
return null;
}
lastInputConnection = platformViewsController.getPlatformViewById(inputTarget.id).onCreateInputConnection(outAttrs);
return lastInputConnection;
}
outAttrs.inputType = inputTypeFromTextInputType(
configuration.inputType,
configuration.obscureText,
configuration.autocorrect,
configuration.enableSuggestions,
configuration.textCapitalization
);
outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN;
......@@ -243,7 +273,7 @@ public class XTextInputPlugin {
/**
* Clears a platform view text input client if it is the current input target.
* <p>
*
* This is called when a platform view is disposed to make sure we're not hanging to a stale input
* connection.
*/
......@@ -269,7 +299,8 @@ public class XTextInputPlugin {
mImm.hideSoftInputFromWindow(view.getApplicationWindowToken(), 0);
}
private void setTextInputClient(int client, TextInputChannel.Configuration configuration) {
@VisibleForTesting
void setTextInputClient(int client, TextInputChannel.Configuration configuration) {
inputTarget = new InputTarget(InputTarget.Type.FRAMEWORK_CLIENT, client);
this.configuration = configuration;
mEditable = Editable.Factory.getInstance().newEditable("");
......@@ -301,8 +332,8 @@ public class XTextInputPlugin {
}
}
private void setTextInputEditingState(View view, TextInputChannel.TextEditState state) {
if (!mRestartInputPending && state.text.equals(mEditable.toString())) {
@VisibleForTesting void setTextInputEditingState(View view, TextInputChannel.TextEditState state) {
if (!restartAlwaysRequired && !mRestartInputPending && state.text.equals(mEditable.toString())) {
applyStateToSelection(state);
mImm.updateSelection(mView, Math.max(Selection.getSelectionStart(mEditable), 0),
Math.max(Selection.getSelectionEnd(mEditable), 0),
......@@ -316,6 +347,30 @@ public class XTextInputPlugin {
}
}
// Samsung's Korean keyboard has a bug where it always attempts to combine characters based on
// its internal state, ignoring if and when the cursor is moved programmatically. The same bug
// also causes non-korean keyboards to occasionally duplicate text when tapping in the middle
// of existing text to edit it.
//
// Fully restarting the IMM works around this because it flushes the keyboard's internal state
// and stops it from trying to incorrectly combine characters. However this also has some
// negative performance implications, so we don't want to apply this workaround in every case.
@SuppressLint("NewApi") // New API guard is inline, the linter can't see it.
@SuppressWarnings("deprecation")
private boolean isRestartAlwaysRequired() {
InputMethodSubtype subtype = mImm.getCurrentInputMethodSubtype();
// Impacted devices all shipped with Android Lollipop or newer.
if (subtype == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP || !Build.MANUFACTURER.equals("samsung")) {
return false;
}
String keyboardName = Settings.Secure.getString(mView.getContext().getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
// The Samsung keyboard is called "com.sec.android.inputmethod/.SamsungKeypad" but look
// for "Samsung" just in case Samsung changes the name of the keyboard.
if(keyboardName==null) return false;
return keyboardName.contains("Samsung");
}
private void clearTextInputClient() {
if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW) {
// Focus changes in the framework tree have no guarantees on the order focus nodes are notified. A node
......@@ -358,4 +413,4 @@ public class XTextInputPlugin {
// For platform views this is the platform view's ID.
int id;
}
}
\ No newline at end of file
}
package com.idlefish.flutterboost.containers;
import android.annotation.SuppressLint;
import android.app.Activity;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
......@@ -56,6 +57,7 @@ public class BoostFlutterActivity extends Activity
// Default configuration.
protected static final String DEFAULT_BACKGROUND_MODE = BackgroundMode.opaque.name();
private static XPlatformPlugin sXPlatformPlugin;
public static Intent createDefaultIntent(@NonNull Context launchContext) {
return withNewEngine().build(launchContext);
......@@ -185,6 +187,7 @@ public class BoostFlutterActivity extends Activity
*/
@Nullable
@SuppressWarnings("deprecation")
@SuppressLint("WrongConstant")
private Drawable getSplashScreenFromManifest() {
try {
ActivityInfo activityInfo = getPackageManager().getActivityInfo(
......@@ -440,8 +443,8 @@ public class BoostFlutterActivity extends Activity
@Nullable
@Override
public XPlatformPlugin providePlatformPlugin( @NonNull FlutterEngine flutterEngine) {
return new XPlatformPlugin( flutterEngine.getPlatformChannel());
public XPlatformPlugin providePlatformPlugin(@NonNull FlutterEngine flutterEngine) {
return BoostViewUtils.getPlatformPlugin(flutterEngine.getPlatformChannel());
}
/**
......
package com.idlefish.flutterboost.containers;
import com.idlefish.flutterboost.XPlatformPlugin;
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
class BoostViewUtils {
private static volatile XPlatformPlugin mInstance;
private BoostViewUtils() {
}
public static XPlatformPlugin getPlatformPlugin(PlatformChannel channel) {
if (mInstance == null) {
synchronized (BoostViewUtils.class) {
if (mInstance == null) {
mInstance = new XPlatformPlugin(channel);
}
}
}
return mInstance;
}
}
......@@ -230,7 +230,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer
// Null out the platformPlugin to avoid a possible retain cycle between the plugin, this Fragment,
// and this Fragment's Activity.
if (platformPlugin != null) {
platformPlugin.detachActivity();
platformPlugin.detachActivity(getContextActivity());
platformPlugin = null;
}
......
......@@ -14,13 +14,11 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import com.idlefish.flutterboost.FlutterBoost;
import com.idlefish.flutterboost.Utils;
import com.idlefish.flutterboost.XFlutterView;
import com.idlefish.flutterboost.XPlatformPlugin;
import io.flutter.embedding.android.*;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.plugin.platform.PlatformPlugin;
import java.util.HashMap;
import java.util.Map;
......@@ -469,8 +467,7 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm
@Nullable
@Override
public XPlatformPlugin providePlatformPlugin( @NonNull FlutterEngine flutterEngine) {
return new XPlatformPlugin(flutterEngine.getPlatformChannel());
return BoostViewUtils.getPlatformPlugin(flutterEngine.getPlatformChannel());
}
/**
......
......@@ -69,6 +69,7 @@
@property (nonatomic,assign) long long identifier;
@property (nonatomic, copy) NSString *flbNibName;
@property (nonatomic, strong) NSBundle *flbNibBundle;
@property (nonatomic, assign) BOOL deallocNotified;
@end
#pragma clang diagnostic push
......@@ -163,6 +164,7 @@ static NSUInteger kInstanceCounter = 0;
pageName:_name
params:_params
uniqueId:[self uniqueIDString]];
self.deallocNotified = NO;
}
[super willMoveToParentViewController:parent];
}
......@@ -171,23 +173,29 @@ static NSUInteger kInstanceCounter = 0;
if (!parent) {
//当VC被移出parent时,就通知flutter层销毁page
[self notifyWillDealloc];
self.deallocNotified = YES;
}
[super didMoveToParentViewController:parent];
}
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion {
__weak __typeof__(self) weakSelf = self;
[super dismissViewControllerAnimated:flag completion:^(){
__strong __typeof__(weakSelf) self = weakSelf;
if (completion) {
completion();
}
//当VC被dismiss时,就通知flutter层销毁page
[self notifyWillDealloc];
self.deallocNotified = YES;
}];
}
- (void)dealloc
{
if (!self.deallocNotified) {
[self notifyWillDealloc];
}
[NSNotificationCenter.defaultCenter removeObserver:self];
}
......
......@@ -39,9 +39,9 @@ enum ContainerLifeCycle {
}
typedef BoostContainerLifeCycleObserver = void Function(
ContainerLifeCycle state,
BoostContainerSettings settings,
);
ContainerLifeCycle state,
BoostContainerSettings settings,
);
class BoostContainer extends Navigator {
const BoostContainer({
......@@ -52,17 +52,17 @@ class BoostContainer extends Navigator {
RouteFactory onUnknownRoute,
List<NavigatorObserver> observers,
}) : super(
key: key,
initialRoute: initialRoute,
onGenerateRoute: onGenerateRoute,
onUnknownRoute: onUnknownRoute,
observers: observers,
);
key: key,
initialRoute: initialRoute,
onGenerateRoute: onGenerateRoute,
onUnknownRoute: onUnknownRoute,
observers: observers,
);
factory BoostContainer.copy(
Navigator navigator, [
BoostContainerSettings settings = const BoostContainerSettings(),
]) =>
Navigator navigator, [
BoostContainerSettings settings = const BoostContainerSettings(),
]) =>
BoostContainer(
key: GlobalKey<BoostContainerState>(),
settings: settings,
......@@ -73,9 +73,9 @@ class BoostContainer extends Navigator {
);
factory BoostContainer.obtain(
Navigator navigator,
BoostContainerSettings settings,
) =>
Navigator navigator,
BoostContainerSettings settings,
) =>
BoostContainer(
key: GlobalKey<BoostContainerState>(),
settings: settings,
......@@ -86,7 +86,11 @@ class BoostContainer extends Navigator {
params: settings.params,
uniqueId: settings.uniqueId,
animated: false,
settings: routeSettings,
settings: RouteSettings(
name: settings.name,
isInitialRoute: routeSettings.isInitialRoute,
arguments: routeSettings.arguments,
),
builder: settings.builder,
);
} else {
......@@ -110,18 +114,20 @@ class BoostContainer extends Navigator {
static BoostContainerState tryOf(BuildContext context) {
final BoostContainerState container =
context.findAncestorStateOfType<BoostContainerState>();
context.findAncestorStateOfType<BoostContainerState>();
return container;
}
static BoostContainerState of(BuildContext context) {
final BoostContainerState container =
context.findAncestorStateOfType<BoostContainerState>();
context.findAncestorStateOfType<BoostContainerState>();
assert(container != null, 'not in flutter boost');
return container;
}
String desc() => '{uniqueId=${settings.uniqueId},name=${settings.name}}';
RouteListFactory get initialRoutes => super.onGenerateInitialRoutes;
}
class BoostContainerState extends NavigatorState {
......@@ -161,6 +167,15 @@ class BoostContainerState extends NavigatorState {
void initState() {
super.initState();
backPressedHandler = () => maybePop();
final String initRoute = widget.initialRoute ?? Navigator.defaultRouteName;
if (initRoute != null && routerHistory.isEmpty) {
routerHistory.addAll(
widget.initialRoutes(
this,
widget.initialRoute ?? Navigator.defaultRouteName
)
);
}
}
@override
......@@ -177,22 +192,28 @@ class BoostContainerState extends NavigatorState {
@override
Future<bool> maybePop<T extends Object>([T result]) async {
if(routerHistory.isEmpty) {
pop(result);
return true;
}
final Route<T> route = routerHistory.last as Route<T>;
final RoutePopDisposition disposition = await route.willPop();
if (mounted) {
switch (disposition) {
case RoutePopDisposition.pop:
pop(result);
return true;
break;
case RoutePopDisposition.doNotPop:
return false;
break;
case RoutePopDisposition.bubble:
pop(result);
return true;
break;
}
switch (disposition) {
case RoutePopDisposition.pop:
pop(result);
return true;
break;
case RoutePopDisposition.doNotPop:
return false;
break;
case RoutePopDisposition.bubble:
pop(result);
return true;
break;
}
}
return false;
}
......@@ -238,10 +259,10 @@ class BoostContainerState extends NavigatorState {
VoidCallback addLifeCycleObserver(BoostContainerLifeCycleObserver observer) {
return FlutterBoost.singleton.addBoostContainerLifeCycleObserver(
(
ContainerLifeCycle state,
BoostContainerSettings settings,
) {
(
ContainerLifeCycle state,
BoostContainerSettings settings,
) {
if (settings.uniqueId == uniqueId) {
observer(state, settings);
}
......
......@@ -62,4 +62,9 @@ class BoostPageRoute<T> extends MaterialPageRoute<T> {
return null;
}
}
@override
Future<RoutePopDisposition> willPop() {
return Future<RoutePopDisposition>.value(RoutePopDisposition.pop);
}
}
......@@ -69,7 +69,6 @@ class ContainerManagerState extends State<BoostContainerManager> {
final List<BoostContainer> _offstage = <BoostContainer>[];
List<_ContainerOverlayEntry> _leastEntries;
BoostContainer _onstage;
bool _foreground = true;
......@@ -158,15 +157,20 @@ class ContainerManagerState extends State<BoostContainerManager> {
}
}
final List<BoostContainer> containers = <BoostContainer>[];
containers.addAll(_offstage);
final List<Widget> containers = <Widget>[];
containers.addAll(_offstage.map<Widget>(
(BoostContainer container) => HeroControllerScope(
controller: null,
child: container
)
));
assert(_onstage != null, 'Should have a least one BoostContainer');
containers.add(_onstage);
_leastEntries = containers
.map<_ContainerOverlayEntry>(
(BoostContainer container) => _ContainerOverlayEntry(container))
(Widget container) => _ContainerOverlayEntry(container))
.toList(growable: false);
overlayState.insertAll(_leastEntries);
......@@ -336,7 +340,7 @@ class ContainerManagerState extends State<BoostContainerManager> {
}
class _ContainerOverlayEntry extends OverlayEntry {
_ContainerOverlayEntry(BoostContainer container)
_ContainerOverlayEntry(Widget container)
: super(
builder: (BuildContext ctx) => container,
opaque: true,
......
......@@ -38,7 +38,7 @@ class ObserversHolder {
void removeObserver<T>(T observer) =>
_observers[T.toString()]?.remove(observer);
void cleanObservers<T>(T observer) => _observers[T.toString()]?.clear();
void cleanObservers<T>() => _observers[T.toString()]?.clear();
Set<T> observersOf<T>() => _observers[T.toString()] as Set<T> ?? <T>{};
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment