Skip to content

Commit

Permalink
Add check for external keyboard on iOS(iPad). Replaced bool status …
Browse files Browse the repository at this point in the history
…with `KeyboardVisibilityStatus` which extended with iOS external keyboard
  • Loading branch information
muknta committed Mar 21, 2024
1 parent 42ddef5 commit a987232
Show file tree
Hide file tree
Showing 18 changed files with 142 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

#import "FlutterKeyboardVisibilityPlugin.h"
#import <GameController/GameController.h>

@interface FlutterKeyboardVisibilityPlugin() <FlutterStreamHandler>

Expand All @@ -17,11 +18,33 @@ @interface FlutterKeyboardVisibilityPlugin() <FlutterStreamHandler>
@end


@interface KeyboardDetector : NSObject

+ (BOOL)isExternalKeyboardConnected;

@end

@implementation KeyboardDetector

+ (BOOL)isExternalKeyboardConnected {
// Check if GameController framework is available
if (@available(iOS 14.0, *)) {
GCKeyboard *coalescedKeyboard = [GCKeyboard coalescedKeyboard];
return (coalescedKeyboard != nil);
} else {
return NO; // GameController framework is not available before iOS 14
}
}

@end


@implementation FlutterKeyboardVisibilityPlugin


+(void) registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
FlutterEventChannel *stream = [FlutterEventChannel eventChannelWithName:@"flutter_keyboard_visibility" binaryMessenger:[registrar messenger]];

FlutterKeyboardVisibilityPlugin *instance = [[FlutterKeyboardVisibilityPlugin alloc] init];
[stream setStreamHandler:instance];
}
Expand All @@ -46,7 +69,12 @@ - (void)didShow
if (!self.isVisible) {
self.isVisible = YES;
if (self.flutterEventListening) {
self.flutterEventSink([NSNumber numberWithInt:1]);
BOOL isExternalKeyboardConnected = [KeyboardDetector isExternalKeyboardConnected];
if (isExternalKeyboardConnected) {
self.flutterEventSink([NSNumber numberWithInt:2]);
} else {
self.flutterEventSink([NSNumber numberWithInt:1]);
}
}
}
}
Expand All @@ -57,7 +85,12 @@ - (void)willShow
if (!self.isVisible) {
self.isVisible = YES;
if (self.flutterEventListening) {
self.flutterEventSink([NSNumber numberWithInt:1]);
BOOL isExternalKeyboardConnected = [KeyboardDetector isExternalKeyboardConnected];
if (isExternalKeyboardConnected) {
self.flutterEventSink([NSNumber numberWithInt:2]);
} else {
self.flutterEventSink([NSNumber numberWithInt:1]);
}
}
}
}
Expand All @@ -79,7 +112,12 @@ -(FlutterError*)onListenWithArguments:(id)arguments eventSink:(FlutterEventSink)

// if keyboard is visible at startup, let our subscriber know
if (self.isVisible) {
self.flutterEventSink([NSNumber numberWithInt:1]);
BOOL isExternalKeyboardConnected = [KeyboardDetector isExternalKeyboardConnected];
if (isExternalKeyboardConnected) {
self.flutterEventSink([NSNumber numberWithInt:2]);
} else {
self.flutterEventSink([NSNumber numberWithInt:1]);
}
}

return nil;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export 'package:flutter_keyboard_visibility/src/keyboard_visibility_test_util.da
export 'package:flutter_keyboard_visibility/src/ui/keyboard_dismiss_on_tap.dart';
export 'package:flutter_keyboard_visibility/src/ui/keyboard_visibility_builder.dart';
export 'package:flutter_keyboard_visibility/src/ui/keyboard_visibility_provider.dart';
export 'package:flutter_keyboard_visibility_platform_interface/flutter_keyboard_visibility_platform_interface.dart';
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter_keyboard_visibility/src/keyboard_visibility_handler.dart';
import 'package:flutter_keyboard_visibility_platform_interface/flutter_keyboard_visibility_platform_interface.dart';

/// Provides direct information about keyboard visibility and allows you
/// to subscribe to changes.
Expand All @@ -16,7 +17,7 @@ class KeyboardVisibilityController {
static final KeyboardVisibilityController _instance =
KeyboardVisibilityController._();

Stream<bool> get onChange => KeyboardVisibilityHandler.onChange;
Stream<KeyboardVisibilityStatus> get onChange => KeyboardVisibilityHandler.onChange;

bool get isVisible => KeyboardVisibilityHandler.isVisible;
KeyboardVisibilityStatus get isVisible => KeyboardVisibilityHandler.isVisible;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,40 @@ class KeyboardVisibilityHandler {
static FlutterKeyboardVisibilityPlatform get _platform =>
FlutterKeyboardVisibilityPlatform.instance;

static bool _isInitialized = false;
static final _onChangeController = StreamController<bool>();
static KeyboardVisibilityStatus _isInitialized = KeyboardVisibilityStatus.notVisible;
static final _onChangeController = StreamController<KeyboardVisibilityStatus>();
static final _onChange = _onChangeController.stream.asBroadcastStream();

/// Emits true every time the keyboard is shown, and false every time the
/// keyboard is dismissed.
static Stream<bool> get onChange {
static Stream<KeyboardVisibilityStatus> get onChange {
// If _testIsVisible set, don't try to create the EventChannel
if (!_isInitialized && _testIsVisible == null) {
if (_isInitialized == KeyboardVisibilityStatus.notVisible && _testIsVisible == null) {
_platform.onChange.listen(_updateValue);
_isInitialized = true;
_isInitialized = KeyboardVisibilityStatus.visible;
}
return _onChange;
}

/// Returns true if the keyboard is currently visible, false if not.
static bool get isVisible => _testIsVisible ?? _isVisible;
static bool _isVisible = false;
static KeyboardVisibilityStatus get isVisible => _testIsVisible ?? _isVisible;
static KeyboardVisibilityStatus _isVisible = KeyboardVisibilityStatus.notVisible;

/// Fake representation of whether or not the keyboard is visible
/// for testing purposes. When this value is non-null, it will be
/// reported exclusively by the `isVisible` getter.
static bool? _testIsVisible;
static KeyboardVisibilityStatus? _testIsVisible;

/// Forces `KeyboardVisibilityHandler` to report `isKeyboardVisible`
/// for testing purposes.
///
/// `KeyboardVisibilityHandler` will continue reporting `isKeyboardVisible`
/// until the value is changed again with this method.
static void setVisibilityForTesting(bool isKeyboardVisible) {
static void setVisibilityForTesting(KeyboardVisibilityStatus isKeyboardVisible) {
_updateValue(isKeyboardVisible);
}

static void _updateValue(bool newValue) {
static void _updateValue(KeyboardVisibilityStatus newValue) {
_testIsVisible = newValue;

// Don't report the same value multiple times
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_keyboard_visibility/src/keyboard_visibility_handler.dart';
import 'package:flutter_keyboard_visibility_platform_interface/flutter_keyboard_visibility_platform_interface.dart';

@visibleForTesting
class KeyboardVisibilityTesting {
Expand All @@ -9,7 +10,7 @@ class KeyboardVisibilityTesting {
/// `KeyboardVisibility` will continue reporting `isKeyboardVisible`
/// until the value is changed again with this method.
@visibleForTesting
static void setVisibilityForTesting(bool isKeyboardVisible) {
static void setVisibilityForTesting(KeyboardVisibilityStatus isKeyboardVisible) {
KeyboardVisibilityHandler.setVisibilityForTesting(isKeyboardVisible);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_keyboard_visibility/src/keyboard_visibility_controller.dart';
import 'package:flutter_keyboard_visibility_platform_interface/flutter_keyboard_visibility_platform_interface.dart';

/// A convenience builder that exposes if the native keyboard is visible.
class KeyboardVisibilityBuilder extends StatelessWidget {
Expand All @@ -18,18 +19,18 @@ class KeyboardVisibilityBuilder extends StatelessWidget {
}) : super(key: key);

/// A builder method that exposes if the native keyboard is visible.
final Widget Function(BuildContext, bool isKeyboardVisible) builder;
final Widget Function(BuildContext, KeyboardVisibilityStatus isKeyboardVisible) builder;

@override
Widget build(BuildContext context) {
return StreamBuilder<bool>(
return StreamBuilder<KeyboardVisibilityStatus>(
stream: _controller.onChange,
initialData: _controller.isVisible,
builder: (context, snapshot) {
if (snapshot.data != null) {
return builder(context, snapshot.data!);
} else {
return builder(context, false);
return builder(context, KeyboardVisibilityStatus.notVisible);
}
},
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:flutter_keyboard_visibility/src/keyboard_visibility_controller.dart';
import 'package:flutter_keyboard_visibility_platform_interface/flutter_keyboard_visibility_platform_interface.dart';

/// Widget that reports to its descendants whether or not
/// the keyboard is currently visible.
Expand All @@ -16,7 +17,7 @@ import 'package:flutter_keyboard_visibility/src/keyboard_visibility_controller.d
/// return KeyboardVisibilityProvider(
/// child: Builder(
/// builder: (BuildContext context) {
/// final bool isKeyboardVisible = KeyboardVisibilityProvider.isKeyboardVisible(context);
/// final KeyboardVisibilityStatus isKeyboardVisible = KeyboardVisibilityProvider.isKeyboardVisible(context);
///
/// return Text('Keyboard is visible: $isKeyboardVisible');
/// },
Expand Down Expand Up @@ -48,7 +49,7 @@ class KeyboardVisibilityProvider extends StatefulWidget {
/// This method also establishes an `InheritedWidget` dependency
/// with the given `context`, and therefore the given `context`
/// will automatically rebuild if the keyboard visibility changes.
static bool isKeyboardVisible(BuildContext context) {
static KeyboardVisibilityStatus isKeyboardVisible(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<
_KeyboardVisibilityInheritedWidget>()!
Expand All @@ -63,7 +64,7 @@ class KeyboardVisibilityProvider extends StatefulWidget {
class _KeyboardVisibilityProviderState
extends State<KeyboardVisibilityProvider> {
late StreamSubscription _subscription;
bool _isKeyboardVisible = false;
KeyboardVisibilityStatus _isKeyboardVisible = KeyboardVisibilityStatus.notVisible;

@override
void initState() {
Expand All @@ -73,7 +74,7 @@ class _KeyboardVisibilityProviderState
widget._controller.onChange.listen(_onKeyboardVisibilityChange);
}

void _onKeyboardVisibilityChange(bool isKeyboardVisible) {
void _onKeyboardVisibilityChange(KeyboardVisibilityStatus isKeyboardVisible) {
setState(() {
_isKeyboardVisible = isKeyboardVisible;
});
Expand Down Expand Up @@ -103,7 +104,7 @@ class _KeyboardVisibilityInheritedWidget extends InheritedWidget {
required Widget child,
}) : super(key: key, child: child);

final bool isKeyboardVisible;
final KeyboardVisibilityStatus isKeyboardVisible;

@override
bool updateShouldNotify(_KeyboardVisibilityInheritedWidget oldWidget) {
Expand Down
17 changes: 12 additions & 5 deletions flutter_keyboard_visibility/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,24 @@ version: 6.0.0
homepage: https://github.com/MisterJimson/flutter_keyboard_visibility
repository: https://github.com/MisterJimson/flutter_keyboard_visibility

publish_to: 'none' # TODO: remove on fork merge

environment:
sdk: '>=2.12.0 <4.0.0'
flutter: ">=1.20.0"

dependencies:
meta: ">=1.0.0 <2.0.0"
flutter_keyboard_visibility_platform_interface: ^2.0.0
flutter_keyboard_visibility_linux: ^1.0.0
flutter_keyboard_visibility_macos: ^1.0.0
flutter_keyboard_visibility_web: ^2.0.0
flutter_keyboard_visibility_windows: ^1.0.0
flutter_keyboard_visibility_platform_interface:
path: ../flutter_keyboard_visibility_platform_interface
flutter_keyboard_visibility_linux:
path: ../flutter_keyboard_visibility_linux
flutter_keyboard_visibility_macos:
path: ../flutter_keyboard_visibility_macos
flutter_keyboard_visibility_web:
path: ../flutter_keyboard_visibility_web
flutter_keyboard_visibility_windows:
path: ../flutter_keyboard_visibility_windows
flutter:
sdk: flutter

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ class FlutterKeyboardVisibilityPluginLinux
}

/// Emits changes to keyboard visibility from the platform. Linux is not
/// implemented yet so false is returned.
/// implemented yet so `notVisible` is returned.
@override
Stream<bool> get onChange async* {
yield false;
Stream<KeyboardVisibilityStatus> get onChange async* {
yield KeyboardVisibilityStatus.notVisible;
}
}
5 changes: 4 additions & 1 deletion flutter_keyboard_visibility_linux/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ version: 1.0.0
homepage: https://github.com/MisterJimson/flutter_keyboard_visibility
repository: https://github.com/MisterJimson/flutter_keyboard_visibility

publish_to: 'none' # TODO: remove on fork merge

environment:
sdk: '>=2.12.0 <4.0.0'
flutter: '>=1.20.0'
Expand All @@ -16,7 +18,8 @@ flutter:
dartPluginClass: FlutterKeyboardVisibilityPluginLinux

dependencies:
flutter_keyboard_visibility_platform_interface: ^2.0.0
flutter_keyboard_visibility_platform_interface:
path: ../flutter_keyboard_visibility_platform_interface
flutter:
sdk: flutter

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ class FlutterKeyboardVisibilityPluginMacos
}

/// Emits changes to keyboard visibility from the platform. MacOS is not
/// implemented yet so false is returned.
/// implemented yet so `notVisible` is returned.
@override
Stream<bool> get onChange async* {
yield false;
Stream<KeyboardVisibilityStatus> get onChange async* {
yield KeyboardVisibilityStatus.notVisible;
}
}
5 changes: 4 additions & 1 deletion flutter_keyboard_visibility_macos/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ version: 1.0.0
homepage: https://github.com/MisterJimson/flutter_keyboard_visibility
repository: https://github.com/MisterJimson/flutter_keyboard_visibility

publish_to: 'none' # TODO: remove on fork merge

environment:
sdk: '>=2.12.0 <4.0.0'
flutter: ">=1.20.0"
Expand All @@ -16,7 +18,8 @@ flutter:
dartPluginClass: FlutterKeyboardVisibilityPluginMacos

dependencies:
flutter_keyboard_visibility_platform_interface: ^2.0.0
flutter_keyboard_visibility_platform_interface:
path: ../flutter_keyboard_visibility_platform_interface
flutter:
sdk: flutter

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,26 @@ import 'dart:async';
import 'package:flutter_keyboard_visibility_platform_interface/src/method_channel_flutter_keyboard_visibility.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';

enum KeyboardVisibilityStatus {
visible,

/// Supported only on iOS 14+.
///
/// Observed only in iPad for now.
/// It shows a little menu at the bottom if External Keyboard is connected and in focus.
iosExternalKeyboardVisible,
notVisible,
}

extension KeyboardVisibilityStatusExtension on KeyboardVisibilityStatus {
bool get isVisible =>
this == KeyboardVisibilityStatus.visible ||
this == KeyboardVisibilityStatus.iosExternalKeyboardVisible;
bool get isNotVisible => this == KeyboardVisibilityStatus.notVisible;
bool get isIosExternalKeyboardVisible =>
this == KeyboardVisibilityStatus.iosExternalKeyboardVisible;
}

/// The platform interface for the flutter_keyboard_visibility plugin
abstract class FlutterKeyboardVisibilityPlatform extends PlatformInterface {
/// The platform interface for the flutter_keyboard_visibility plugin
Expand All @@ -26,7 +46,7 @@ abstract class FlutterKeyboardVisibilityPlatform extends PlatformInterface {
}

/// Emits changes to keyboard visibility from the platform
Stream<bool> get onChange {
Stream<KeyboardVisibilityStatus> get onChange {
throw UnimplementedError('get onChange has not been implemented.');
}
}
Loading

0 comments on commit a987232

Please sign in to comment.