diff --git a/example/images/logo-dark.svg b/example/images/logo-dark.svg new file mode 100644 index 0000000..adfea0e --- /dev/null +++ b/example/images/logo-dark.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/example/lib/pages/connect.dart b/example/lib/pages/connect.dart index 8483e9e..d336e37 100644 --- a/example/lib/pages/connect.dart +++ b/example/lib/pages/connect.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:livekit_client/livekit_client.dart'; +import 'package:livekit_example/widgets/text_field.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../exts.dart'; @@ -105,74 +107,110 @@ class _ConnectPageState extends State { @override Widget build(BuildContext context) => Scaffold( - appBar: AppBar( - title: const Text('Connect to LiveKit'), - ), - body: Center( + body: Container( + alignment: Alignment.center, child: Container( padding: const EdgeInsets.symmetric( - vertical: 20, horizontal: 20, ), - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - borderRadius: BorderRadius.circular(8), - border: - Border.all(color: Theme.of(context).colorScheme.secondary), - ), - constraints: const BoxConstraints( - maxWidth: 320, - ), + constraints: const BoxConstraints(maxWidth: 400), child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - TextField( - enableSuggestions: false, - autocorrect: false, - controller: _uriCtrl, - decoration: const InputDecoration(labelText: 'URL'), - ), - TextField( - enableSuggestions: false, - autocorrect: false, - controller: _tokenCtrl, - decoration: const InputDecoration(labelText: 'Token'), - ), - Padding( - padding: const EdgeInsets.only(top: 20), - child: CheckboxListTile( - controlAffinity: ListTileControlAffinity.leading, - onChanged: (value) => _setSimulcast(value), - title: const Text('Use Simulcast'), - value: _simulcast, - ), - ), - Padding( - padding: const EdgeInsets.only(top: 20), - child: ElevatedButton( - onPressed: _busy ? null : () => _connect(context), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (_busy) - const Padding( - padding: EdgeInsets.only(right: 10), - child: SizedBox( - height: 15, - width: 15, - child: CircularProgressIndicator( - color: Colors.white, - strokeWidth: 2, - ), - ), - ), - const Text('Connect'), - ], + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 70), + child: SvgPicture.asset( + 'images/logo-dark.svg', ), ), + + Padding( + padding: const EdgeInsets.only(bottom: 30), + child: LKTextField( + label: 'Server URL', + ctrl: _uriCtrl, + ), + ), + Padding( + padding: const EdgeInsets.only(bottom: 50), + child: LKTextField( + label: 'Token', + ctrl: _tokenCtrl, + ), + ), + Container( + alignment: Alignment.center, + child: ElevatedButton( + onPressed: _busy ? null : () => _connect(context), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (_busy) + const Padding( + padding: EdgeInsets.only(right: 10), + child: SizedBox( + height: 15, + width: 15, + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2, + ), + ), + ), + const Text('CONNECT'), + ], + ), + ), + ), + + // Container( + // padding: const EdgeInsets.symmetric( + // vertical: 20, + // horizontal: 20, + // ), + // decoration: BoxDecoration( + // color: Theme.of(context).cardColor, + // borderRadius: BorderRadius.circular(8), + // // border: Border.all( + // // color: Theme.of(context).colorScheme.secondary), + // ), + // constraints: const BoxConstraints( + // maxWidth: 320, + // ), + // child: Column( + // mainAxisSize: MainAxisSize.min, + // children: [ + // TextField( + // enableSuggestions: false, + // autocorrect: false, + // controller: _uriCtrl, + // decoration: const InputDecoration(labelText: 'URL'), + // ), + // TextField( + // enableSuggestions: false, + // autocorrect: false, + // controller: _tokenCtrl, + // decoration: const InputDecoration(labelText: 'Token'), + // ), + // Padding( + // padding: const EdgeInsets.only(top: 20), + // child: CheckboxListTile( + // controlAffinity: ListTileControlAffinity.leading, + // onChanged: (value) => _setSimulcast(value), + // title: const Text('Use Simulcast'), + // value: _simulcast, + // ), + // ), + // + // ], + // ), + // ), + ] + // .map((e) => Padding( + // padding: const EdgeInsets.only(bottom: 50), child: e)) + // .toList(), ), - ], - ), ), ), ); diff --git a/example/lib/pages/room.dart b/example/lib/pages/room.dart index 6560d32..f5340ae 100644 --- a/example/lib/pages/room.dart +++ b/example/lib/pages/room.dart @@ -142,10 +142,9 @@ class _RoomPageState extends State { child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: math.max(0, participants.length - 1), - itemBuilder: (BuildContext context, int index) => Container( + itemBuilder: (BuildContext context, int index) => SizedBox( width: 100, height: 100, - padding: const EdgeInsets.all(2), child: ParticipantWidget(participants[index + 1], quality: VideoQuality.LOW), ), diff --git a/example/lib/theme.dart b/example/lib/theme.dart index 3b34a81..730d2bb 100644 --- a/example/lib/theme.dart +++ b/example/lib/theme.dart @@ -7,17 +7,23 @@ import 'package:google_fonts/google_fonts.dart'; // https://github.com/flutter/flutter/issues/55092 // https://github.com/flutter/flutter/issues/39113 // + +extension LKColors on Colors { + static const lkBlue = Color(0xFF5A8BFF); + static const lkDarkBlue = Color(0xFF00153C); +} + class LiveKitTheme { // final bgColor = Colors.black; final textColor = Colors.white; - final cardColor = const Color(0xFF00163c); - final accentColor = const Color(0xFF2d6aef); + final cardColor = LKColors.lkDarkBlue; + final accentColor = LKColors.lkBlue; ThemeData buildThemeData(BuildContext ctx) => ThemeData( backgroundColor: bgColor, // accentColor: accentColor, - colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.blue), + // colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.blue), appBarTheme: AppBarTheme( backgroundColor: cardColor, ), @@ -29,6 +35,14 @@ class LiveKitTheme { ), elevatedButtonTheme: ElevatedButtonThemeData( style: ButtonStyle( + textStyle: + MaterialStateProperty.all(GoogleFonts.montserrat( + fontSize: 15, + )), + padding: MaterialStateProperty.all( + const EdgeInsets.symmetric(vertical: 20, horizontal: 25)), + shape: MaterialStateProperty.all( + RoundedRectangleBorder(borderRadius: BorderRadius.circular(8))), foregroundColor: MaterialStateProperty.all(Colors.white), // backgroundColor: MaterialStateProperty.all(accentColor), backgroundColor: MaterialStateProperty.resolveWith((states) { @@ -43,8 +57,13 @@ class LiveKitTheme { checkColor: MaterialStateProperty.all(Colors.white), fillColor: MaterialStateProperty.all(accentColor), ), - dialogBackgroundColor: cardColor, - textTheme: GoogleFonts.latoTextTheme( + dialogTheme: DialogTheme( + backgroundColor: cardColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + textTheme: GoogleFonts.montserratTextTheme( Theme.of(ctx).textTheme, ).apply( displayColor: textColor, @@ -53,15 +72,14 @@ class LiveKitTheme { ), hintColor: Colors.red, inputDecorationTheme: InputDecorationTheme( - labelStyle: TextStyle( - color: textColor.withOpacity(.5), + labelStyle: const TextStyle( + color: LKColors.lkBlue, ), - enabledBorder: UnderlineInputBorder( - borderSide: BorderSide(color: textColor.withOpacity(0.1)), - ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide(color: accentColor), + hintStyle: TextStyle( + color: LKColors.lkBlue.withOpacity(.5), ), + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, ), ); } diff --git a/example/lib/widgets/controls.dart b/example/lib/widgets/controls.dart index 93fec83..1afa36d 100644 --- a/example/lib/widgets/controls.dart +++ b/example/lib/widgets/controls.dart @@ -151,56 +151,74 @@ class _ControlsWidgetState extends State { final videoPub = participant.videoTracks.firstOrNull; final videoEnabled = videoPub != null && !videoPub.muted; - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - IconButton( - onPressed: _unpublishAll, - icon: const Icon(EvaIcons.closeCircleOutline), - ), - if (canMute) + return Padding( + padding: const EdgeInsets.symmetric( + vertical: 15, + horizontal: 15, + ), + child: Wrap( + alignment: WrapAlignment.center, + spacing: 5, + runSpacing: 5, + children: [ IconButton( - onPressed: _muteAudio, - icon: const Icon(EvaIcons.mic), - ) - else - IconButton( - onPressed: _unmuteAudio, - icon: const Icon(EvaIcons.micOff), + onPressed: _unpublishAll, + icon: const Icon(EvaIcons.closeCircleOutline), + tooltip: 'Unpublish all', ), - if (videoEnabled) + if (canMute) + IconButton( + onPressed: _muteAudio, + icon: const Icon(EvaIcons.mic), + tooltip: 'mute audio', + ) + else + IconButton( + onPressed: _unmuteAudio, + icon: const Icon(EvaIcons.micOff), + tooltip: 'un-mute audio', + ), + if (videoEnabled) + IconButton( + onPressed: _muteVideo, + icon: const Icon(EvaIcons.video), + tooltip: 'mute video', + ) + else + IconButton( + onPressed: _unmuteVideo, + icon: const Icon(EvaIcons.videoOff), + tooltip: 'un-mute video', + ), IconButton( - onPressed: _muteVideo, - icon: const Icon(EvaIcons.video), - ) - else - IconButton( - onPressed: _unmuteVideo, - icon: const Icon(EvaIcons.videoOff), + icon: Icon(position == CameraPosition.back + ? EvaIcons.camera + : EvaIcons.person), + onPressed: () => _toggleCamera(), + tooltip: 'toggle camera', ), - IconButton( - icon: Icon(position == CameraPosition.back - ? EvaIcons.camera - : EvaIcons.person), - onPressed: () => _toggleCamera(), - ), - IconButton( - icon: const Icon(EvaIcons.monitor), - onPressed: () => _shareScreen(), - ), - IconButton( - onPressed: _onTapDisconnect, - icon: const Icon(EvaIcons.closeCircle), - ), - IconButton( - onPressed: _onTapSendData, - icon: const Icon(EvaIcons.paperPlane), - ), - IconButton( - onPressed: _onTapReconnect, - icon: const Icon(EvaIcons.refresh), - ), - ], + IconButton( + icon: const Icon(EvaIcons.monitor), + onPressed: () => _shareScreen(), + tooltip: 'share screen (experimental)', + ), + IconButton( + onPressed: _onTapDisconnect, + icon: const Icon(EvaIcons.closeCircle), + tooltip: 'disconnect', + ), + IconButton( + onPressed: _onTapSendData, + icon: const Icon(EvaIcons.paperPlane), + tooltip: 'send demo data', + ), + IconButton( + onPressed: _onTapReconnect, + icon: const Icon(EvaIcons.refresh), + tooltip: 're-connect', + ), + ], + ), ); } } diff --git a/example/lib/widgets/no_video.dart b/example/lib/widgets/no_video.dart index 0a4c371..c428794 100644 --- a/example/lib/widgets/no_video.dart +++ b/example/lib/widgets/no_video.dart @@ -2,6 +2,8 @@ import 'package:eva_icons_flutter/eva_icons_flutter.dart'; import 'package:flutter/material.dart'; import 'dart:math' as math; +import 'package:livekit_example/theme.dart'; + class NoVideoWidget extends StatelessWidget { // const NoVideoWidget({Key? key}) : super(key: key); @@ -12,7 +14,7 @@ class NoVideoWidget extends StatelessWidget { child: LayoutBuilder( builder: (ctx, constraints) => Icon( EvaIcons.videoOffOutline, - color: Theme.of(ctx).colorScheme.secondary, + color: LKColors.lkBlue, size: math.min(constraints.maxHeight, constraints.maxWidth) * 0.3, ), ), diff --git a/example/lib/widgets/participant.dart b/example/lib/widgets/participant.dart index b6b8ab5..3b3abd9 100644 --- a/example/lib/widgets/participant.dart +++ b/example/lib/widgets/participant.dart @@ -3,6 +3,7 @@ import 'package:eva_icons_flutter/eva_icons_flutter.dart'; import 'package:flutter/material.dart'; import 'package:flutter_webrtc/flutter_webrtc.dart'; import 'package:livekit_client/livekit_client.dart'; +import 'package:livekit_example/theme.dart'; import 'no_video.dart'; import 'participant_info.dart'; @@ -64,7 +65,17 @@ class _ParticipantWidgetState extends State { @override Widget build(BuildContext ctx) => Container( - color: Theme.of(ctx).cardColor, + foregroundDecoration: BoxDecoration( + border: widget.participant.isSpeaking + ? Border.all( + width: 5, + color: LKColors.lkBlue, + ) + : null, + ), + decoration: BoxDecoration( + color: Theme.of(ctx).cardColor, + ), child: Stack( children: [ // Video diff --git a/example/lib/widgets/text_field.dart b/example/lib/widgets/text_field.dart new file mode 100644 index 0000000..b0479cd --- /dev/null +++ b/example/lib/widgets/text_field.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; + +class LKTextField extends StatelessWidget { + final String label; + final TextEditingController? ctrl; + const LKTextField({ + required this.label, + this.ctrl, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Text( + label, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + Container( + padding: const EdgeInsets.symmetric( + vertical: 15, + horizontal: 15, + ), + decoration: BoxDecoration( + border: Border.all( + width: 1, + color: Colors.white.withOpacity(.3), + ), + borderRadius: BorderRadius.circular(8), + ), + child: TextField( + controller: ctrl, + decoration: const InputDecoration.collapsed( + hintText: '', + ), + keyboardType: TextInputType.url, + ), + ), + ], + ); +} diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 7af7d96..35d8ad6 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - flutter_webrtc (0.2.2): + - flutter_webrtc (0.2.3): - FlutterMacOS - WebRTC-SDK (= 92.4515.10) - FlutterMacOS (1.0.0) @@ -30,7 +30,7 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos SPEC CHECKSUMS: - flutter_webrtc: 43d8f66a0782f8a79fad49115845b55060725b2a + flutter_webrtc: 8d69a6819b422e5caf1c7b2844a05bc6a62b437b FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424 path_provider_macos: 160cab0d5461f0c0e02995469a98f24bdb9a3f1f shared_preferences_macos: 480ce071d0666e37cef23fe6c702293a3d21799e diff --git a/example/pubspec.lock b/example/pubspec.lock index 2dd34ab..9bcba69 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -97,6 +97,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.4" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + url: "https://pub.dartlang.org" + source: hosted + version: "0.23.0+1" flutter_test: dependency: "direct dev" description: flutter @@ -191,6 +198,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0" + path_drawing: + dependency: transitive + description: + name: path_drawing + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.1+1" + path_parsing: + dependency: transitive + description: + name: path_parsing + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.1" path_provider: dependency: transitive description: @@ -226,6 +247,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.3" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "4.4.0" platform: dependency: transitive description: @@ -392,6 +420,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.0" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "5.3.1" sdks: dart: ">=2.14.0 <3.0.0" flutter: ">=2.5.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 04a4ed2..e03b2b1 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -13,9 +13,11 @@ dependencies: provider: ^5.0.0 logging: ^1.0.1 + shared_preferences: ^2.0.7 + google_fonts: ^2.1.0 eva_icons_flutter: ^3.0.0 - shared_preferences: ^2.0.7 + flutter_svg: ^0.23.0+1 livekit_client: path: ../ @@ -27,39 +29,6 @@ dev_dependencies: # The following section is specific to Flutter. flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, 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 from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages + assets: + - images/logo-dark.svg