From 06ca99c7f979ade8258938857e69a0cea1f04134 Mon Sep 17 00:00:00 2001 From: ksenia312 Date: Tue, 6 Feb 2024 23:25:36 +0100 Subject: [PATCH] feat(example_full, lib, android): optimisation improving, current device data --- .../nearby_service/NearbyServiceManager.kt | 3 +- example_full/android/app/build.gradle | 2 +- .../android/app/src/main/AndroidManifest.xml | 11 ++ example_full/lib/domain/app_service.dart | 2 +- example_full/lib/presentation/app.dart | 90 +++++++------ .../presentation/view/communication_view.dart | 68 ++++++++-- .../lib/presentation/view/connected_view.dart | 119 +++++++++--------- .../presentation/view/permissions_view.dart | 20 ++- .../view/streaming_peers_view.dart | 53 ++++---- example_full/lib/utils/files_saver.dart | 13 ++ .../model/communication_channel_state.dart | 12 +- 11 files changed, 251 insertions(+), 142 deletions(-) diff --git a/android/src/main/kotlin/com/xenikii/nearby_service/NearbyServiceManager.kt b/android/src/main/kotlin/com/xenikii/nearby_service/NearbyServiceManager.kt index 8728cd6..7c6a185 100644 --- a/android/src/main/kotlin/com/xenikii/nearby_service/NearbyServiceManager.kt +++ b/android/src/main/kotlin/com/xenikii/nearby_service/NearbyServiceManager.kt @@ -97,8 +97,9 @@ class NearbyServiceManager(private var context: Context) { } } catch (e: SecurityException) { if (!permissionsHandler.checkPermissions()) { - Logger.e("No permission to call 'discover'") + Logger.e("No permission to call 'getCurrentDevice'") permissionsHandler.requestPermissions() + result.success(null) } } } diff --git a/example_full/android/app/build.gradle b/example_full/android/app/build.gradle index 52f245e..a2b5b68 100644 --- a/example_full/android/app/build.gradle +++ b/example_full/android/app/build.gradle @@ -45,7 +45,7 @@ android { applicationId "com.example.nearby_service_example_full" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdkVersion flutter.minSdkVersion + minSdkVersion 24 targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/example_full/android/app/src/main/AndroidManifest.xml b/example_full/android/app/src/main/AndroidManifest.xml index 428cdc6..c131d74 100644 --- a/example_full/android/app/src/main/AndroidManifest.xml +++ b/example_full/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,15 @@ + + + + + + + + + + (builder: (context, service, _) { - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const Padding( - padding: EdgeInsets.fromLTRB(16, 12, 16, 10), - child: InfoPanel(), - ), - Flexible( - child: MediaQuery.removePadding( - context: context, - removeLeft: true, - child: Stepper( - controlsBuilder: (context, _) => const SizedBox.shrink(), - currentStep: service.state.step, - steps: [ - ...AppState.steps.map((e) { - final builder = AppStepViewBuilder(state: e); - final isActive = e == service.state; - return Step( - state: isActive - ? StepState.indexed - : e.step < service.state.step - ? StepState.complete - : StepState.disabled, - title: builder.buildTitle(), - subtitle: builder.buildSubtitle(), - content: builder.buildContent(), - isActive: isActive, - ); - }), - ], - ), - ), - ), - ], - ); - }), + body: const _AppBody(), ), ), ); } } + +class _AppBody extends StatelessWidget { + const _AppBody(); + + @override + Widget build(BuildContext context) { + return Selector( + selector: (context, service) => service.state, + builder: (context, state, _) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Padding( + padding: EdgeInsets.fromLTRB(16, 12, 16, 10), + child: InfoPanel(), + ), + Flexible( + child: MediaQuery.removePadding( + context: context, + removeLeft: true, + child: Stepper( + controlsBuilder: (context, _) => const SizedBox.shrink(), + currentStep: state.step, + steps: [ + ...AppState.steps.map( + (e) { + final builder = AppStepViewBuilder(state: e); + final isActive = e == state; + return Step( + state: isActive + ? StepState.indexed + : e.step < state.step + ? StepState.complete + : StepState.disabled, + title: builder.buildTitle(), + subtitle: builder.buildSubtitle(), + content: builder.buildContent(), + isActive: isActive, + ); + }, + ), + ], + ), + ), + ), + ], + ); + }, + ); + } +} diff --git a/example_full/lib/presentation/view/communication_view.dart b/example_full/lib/presentation/view/communication_view.dart index 09f075d..4ae71bc 100644 --- a/example_full/lib/presentation/view/communication_view.dart +++ b/example_full/lib/presentation/view/communication_view.dart @@ -2,9 +2,11 @@ import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; +import 'package:nearby_service/nearby_service.dart'; import 'package:nearby_service_example_full/domain/app_service.dart'; import 'package:nearby_service_example_full/presentation/app.dart'; import 'package:nearby_service_example_full/uikit/uikit.dart'; +import 'package:nearby_service_example_full/utils/files_saver.dart'; import 'package:provider/provider.dart'; import '../components/device_preview.dart'; @@ -22,13 +24,13 @@ class _CommunicationViewState extends State { @override Widget build(BuildContext context) { - return Consumer( - builder: (context, service, _) { - final device = service.connectedDevice; + return Selector( + selector: (context, service) => service.connectedDevice, + builder: (context, device, _) { if (device == null) { return Center( child: ActionButton( - onTap: service.stopListeningAll, + onTap: context.read().stopListeningAll, title: 'Restart', ), ); @@ -69,7 +71,9 @@ class _CommunicationViewState extends State { Flexible( child: ActionButton( title: 'Send', - onTap: () => service.sendTextRequest(message), + onTap: () => context.read().sendTextRequest( + message, + ), ), ), ], @@ -100,7 +104,7 @@ class _CommunicationViewState extends State { Flexible( child: ActionButton( title: 'Send', - onTap: () => service.sendFilesRequest([ + onTap: () => context.read().sendFilesRequest([ ...files .map((e) => e.path) .where((element) => element != null) @@ -122,11 +126,57 @@ class _CommunicationViewState extends State { physics: const NeverScrollableScrollPhysics(), children: [ ...files.where((element) => element.path != null).map( - (e) => Image.file( + (e) { + return Container( + decoration: BoxDecoration( + border: Border.all(color: kGreenColor), + borderRadius: BorderRadius.circular(12), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(11), + child: FilesSaver.isImage(e.extension) + ? Image.file( + File(e.path!), + fit: BoxFit.cover, + ) + : Container( + alignment: Alignment.center, + padding: const EdgeInsets.all(8.0), + child: Text( + e.name, + style: const TextStyle( + fontSize: 8, + fontWeight: FontWeight.bold, + color: kGreenColor, + ), + ), + ), + ), + ); + if (FilesSaver.isImage(e.extension)) { + return Image.file( File(e.path!), fit: BoxFit.cover, - ), - ), + ); + } else { + return Container( + decoration: BoxDecoration( + border: Border.all(color: kBlueColor), + borderRadius: BorderRadius.circular(16)), + padding: const EdgeInsets.all(8.0), + alignment: Alignment.center, + child: Text( + e.name, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: kBlueColor, + ), + ), + ); + } + }, + ), ], ), ), diff --git a/example_full/lib/presentation/view/connected_view.dart b/example_full/lib/presentation/view/connected_view.dart index f3eefde..a764649 100644 --- a/example_full/lib/presentation/view/connected_view.dart +++ b/example_full/lib/presentation/view/connected_view.dart @@ -13,63 +13,68 @@ class ConnectedView extends StatelessWidget { @override Widget build(BuildContext context) { - return Consumer( - builder: (context, service, _) { - final device = service.connectedDevice; - return device != null - ? Center( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (!device.status.isConnected) - const Center( - child: Padding( - padding: EdgeInsets.all(8.0), - child: Text('Connection lost'), - ), - ) - else if (!device.status.isConnected) - Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text('Connection lost'), - ActionButton( - onTap: () { - service.connect(device); - }, - title: 'Reconnect', - ), - ], - ) - else - DevicePreview(device: device, largeView: true), - const SizedBox(height: 10), - if (service.communicationChannelState != - CommunicationChannelState.loading) - ActionButton( - title: 'Start communicate', - onTap: () => service.startCommunicationChannel( - listener: (event) => MessagesListener.call( - context, - event, - ), - onFilesSaved: (files) => FilesListener.call( - context, - files, - ), - ), - ) - else - Text( - 'Connecting socket.. ' - '${service.isAndroidGroupOwner != null ? service.isAndroidGroupOwner! ? "Waiting a client for connect" : "Waiting a server for connect" : "Waiting a connection"}', - ) - ], - ), - ) - : const SizedBox(); - }, + final device = context.select( + (service) => service.connectedDevice, ); + final isAndroidGroupOwner = context.select( + (service) => service.isAndroidGroupOwner, + ); + final communicationChannelState = + context.select( + (service) => service.communicationChannelState, + ); + return device != null + ? Center( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (!device.status.isConnected) + const Center( + child: Padding( + padding: EdgeInsets.all(8.0), + child: Text('Connection lost'), + ), + ) + else if (!device.status.isConnected) + Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text('Connection lost'), + ActionButton( + onTap: () { + context.read().connect(device); + }, + title: 'Reconnect', + ), + ], + ) + else + DevicePreview(device: device, largeView: true), + const SizedBox(height: 10), + if (!communicationChannelState.isLoading) + ActionButton( + title: 'Start communicate', + onTap: () => + context.read().startCommunicationChannel( + listener: (event) => MessagesListener.call( + context, + event, + ), + onFilesSaved: (files) => FilesListener.call( + context, + files, + ), + ), + ) + else + Text( + 'Connecting socket.. ' + '${isAndroidGroupOwner != null ? isAndroidGroupOwner ? "Waiting a client for connect" : "Waiting a server for connect" : "Waiting a connection"}', + ) + ], + ), + ) + : const SizedBox(); } } diff --git a/example_full/lib/presentation/view/permissions_view.dart b/example_full/lib/presentation/view/permissions_view.dart index d0aa40e..5efaec6 100644 --- a/example_full/lib/presentation/view/permissions_view.dart +++ b/example_full/lib/presentation/view/permissions_view.dart @@ -8,16 +8,14 @@ class PermissionsView extends StatelessWidget { @override Widget build(BuildContext context) { - return Consumer(builder: (context, service, _) { - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - ActionButton( - onTap: service.requestPermissions, - title: 'Request permissions', - ), - ], - ); - }); + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ActionButton( + onTap: context.read().requestPermissions, + title: 'Request permissions', + ), + ], + ); } } diff --git a/example_full/lib/presentation/view/streaming_peers_view.dart b/example_full/lib/presentation/view/streaming_peers_view.dart index ca5c11c..f871c27 100644 --- a/example_full/lib/presentation/view/streaming_peers_view.dart +++ b/example_full/lib/presentation/view/streaming_peers_view.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:nearby_service/nearby_service.dart'; import 'package:nearby_service_example_full/domain/app_service.dart'; import 'package:nearby_service_example_full/uikit/uikit.dart'; import 'package:provider/provider.dart'; @@ -32,29 +33,35 @@ class _PeersBody extends StatelessWidget { @override Widget build(BuildContext context) { - return Consumer( - builder: (context, service, _) { - return (service.peers != null && service.peers!.isNotEmpty) - ? Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - ...service.peers!.map( - (e) { - return Container( - padding: const EdgeInsets.only(bottom: 8.0), - child: DevicePreview(device: e), - ); - }, - ), - ], - ) - : Text( - Platform.isAndroid || service.isIOSBrowser - ? 'No one here (' - : "Wait until someone invites you!", - textAlign: TextAlign.center, - ); + return Selector( + selector: (context, service) => service.isIOSBrowser, + builder: (context, isIOSBrowser, _) { + return Selector?>( + selector: (context, service) => service.peers, + builder: (context, peers, _) { + return (peers != null && peers.isNotEmpty) + ? Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ...peers.map( + (e) { + return Container( + padding: const EdgeInsets.only(bottom: 8.0), + child: DevicePreview(device: e), + ); + }, + ), + ], + ) + : Text( + Platform.isAndroid || isIOSBrowser + ? 'No one here (' + : "Wait until someone invites you!", + textAlign: TextAlign.center, + ); + }, + ); }, ); } diff --git a/example_full/lib/utils/files_saver.dart b/example_full/lib/utils/files_saver.dart index 4b75be0..22ecb2d 100644 --- a/example_full/lib/utils/files_saver.dart +++ b/example_full/lib/utils/files_saver.dart @@ -6,6 +6,19 @@ import 'package:path_provider/path_provider.dart'; class FilesSaver { FilesSaver._(); + static bool isImage(String? extension) { + return _imageExtensions.contains(extension?.toLowerCase()); + } + + static const _imageExtensions = [ + 'jpg', + 'jpeg', + 'png', + 'gif', + 'webp', + 'bmp', + ]; + static Future> savePack( ReceivedNearbyFilesPack pack) async { final files = []; diff --git a/lib/src/model/communication_channel_state.dart b/lib/src/model/communication_channel_state.dart index 7bfd5a5..b80303a 100644 --- a/lib/src/model/communication_channel_state.dart +++ b/lib/src/model/communication_channel_state.dart @@ -2,4 +2,14 @@ /// The status of the communication channel for data exchange. /// Use it to determine if you can send data over the communication channel or not. /// -enum CommunicationChannelState { notConnected, loading, connected } +enum CommunicationChannelState { + notConnected, + loading, + connected; + + bool get isNotConnected => this == CommunicationChannelState.notConnected; + + bool get isLoading => this == CommunicationChannelState.loading; + + bool get isConnected => this == CommunicationChannelState.connected; +}