diff --git a/app/lib/backend/http/api/messages.dart b/app/lib/backend/http/api/messages.dart index 5f567174d..874880497 100644 --- a/app/lib/backend/http/api/messages.dart +++ b/app/lib/backend/http/api/messages.dart @@ -40,7 +40,7 @@ Future> getMessagesServer({ Future> clearChatServer({String? pluginId}) async { if (pluginId == 'no_selected') pluginId = null; var response = await makeApiCall( - url: '${Env.apiBaseUrl}v1/messages?plugin_id=$pluginId', + url: '${Env.apiBaseUrl}v1/messages?plugin_id=${pluginId ?? ''}', headers: {}, method: 'DELETE', body: '', diff --git a/app/lib/pages/chat/page.dart b/app/lib/pages/chat/page.dart index e82531443..23687d4da 100644 --- a/app/lib/pages/chat/page.dart +++ b/app/lib/pages/chat/page.dart @@ -121,48 +121,49 @@ class ChatPageState extends State with AutomaticKeepAliveClientMixin { ), ), ) - : AnimatedMiniBanner( - showAppBar: _showDeleteOption, - height: 80, - child: Container( - width: double.infinity, - height: 40, - color: Theme.of(context).primaryColor, - child: Row( - children: [ - const SizedBox(width: 20), - const Spacer(), - InkWell( - onTap: () async { - showDialog( - context: context, - builder: (ctx) { - return getDialog(context, () { - Navigator.of(context).pop(); - }, () { - setState(() { - _showDeleteOption = false; - }); - context.read().clearChat(); - Navigator.of(context).pop(); - }, "Clear Chat?", - "Are you sure you want to clear the chat? This action cannot be undone."); - }, - ); - }, - child: const Padding( - padding: EdgeInsets.all(8.0), - child: Text( - "Clear Chat \u{1F5D1}", - style: TextStyle(color: Colors.white, fontSize: 14), - ), - ), - ), - const SizedBox(width: 20), - ], - ), - ), - ), + : null, + // AnimatedMiniBanner( + // showAppBar: _showDeleteOption, + // height: 80, + // child: Container( + // width: double.infinity, + // height: 40, + // color: Theme.of(context).primaryColor, + // child: Row( + // children: [ + // const SizedBox(width: 20), + // const Spacer(), + // InkWell( + // onTap: () async { + // showDialog( + // context: context, + // builder: (ctx) { + // return getDialog(context, () { + // Navigator.of(context).pop(); + // }, () { + // setState(() { + // _showDeleteOption = false; + // }); + // context.read().clearChat(); + // Navigator.of(context).pop(); + // }, "Clear Chat?", + // "Are you sure you want to clear the chat? This action cannot be undone."); + // }, + // ); + // }, + // child: const Padding( + // padding: EdgeInsets.all(8.0), + // child: Text( + // "Clear Chat \u{1F5D1}", + // style: TextStyle(color: Colors.white, fontSize: 14), + // ), + // ), + // ), + // const SizedBox(width: 20), + // ], + // ), + // ), + // ), body: Stack( children: [ Align( diff --git a/app/lib/pages/home/widgets/chat_apps_dropdown_widget.dart b/app/lib/pages/home/widgets/chat_apps_dropdown_widget.dart index 88acd2a36..bdd5145e0 100644 --- a/app/lib/pages/home/widgets/chat_apps_dropdown_widget.dart +++ b/app/lib/pages/home/widgets/chat_apps_dropdown_widget.dart @@ -1,4 +1,5 @@ import 'package:cached_network_image/cached_network_image.dart'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:friend_private/backend/schema/app.dart'; import 'package:friend_private/gen/assets.gen.dart'; @@ -6,6 +7,7 @@ import 'package:friend_private/providers/app_provider.dart'; import 'package:friend_private/providers/home_provider.dart'; import 'package:friend_private/providers/message_provider.dart'; import 'package:friend_private/utils/analytics/mixpanel.dart'; +import 'package:friend_private/widgets/dialog.dart'; import 'package:provider/provider.dart'; class ChatAppsDropdownWidget extends StatelessWidget { @@ -28,163 +30,278 @@ class ChatAppsDropdownWidget extends StatelessWidget { return child!; }, child: Consumer(builder: (context, provider, child) { + var selectedApp = provider.apps.firstWhereOrNull((app) => app.id == provider.selectedChatAppId); return Padding( padding: const EdgeInsets.only(left: 0), - child: provider.apps.where((p) => p.enabled).isEmpty - ? GestureDetector( - onTap: () { - MixpanelManager().pageOpened('Chat Apps'); - // routeToPage(context, const AppsPage(filterChatOnly: true)); - context.read().setIndex(2); - controller?.animateToPage(2, duration: const Duration(milliseconds: 200), curve: Curves.easeInOut); - }, - child: const Row( - children: [ - Icon(size: 20, Icons.chat, color: Colors.white), - SizedBox(width: 10), - Text( - 'Enable Apps', - style: TextStyle(color: Colors.white, fontWeight: FontWeight.w500, fontSize: 16), - ), - ], + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: PopupMenuButton( + iconSize: 164, + icon: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + selectedApp != null ? _getAppAvatar(selectedApp) : _getOmiAvatar(), + const SizedBox(width: 8), + Container( + constraints: const BoxConstraints( + maxWidth: 100, + ), + child: Text( + selectedApp != null ? selectedApp.name : "Omi", + style: const TextStyle(color: Colors.white, fontSize: 16), + overflow: TextOverflow.fade, + ), ), - ) - : Container( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: DropdownButton( - menuMaxHeight: 350, - value: provider.getSelectedApp()?.id ?? "no_selected", - onChanged: (s) async { - if ((s == 'no_selected' && provider.apps.where((p) => p.enabled).isEmpty) || s == 'enable') { - // routeToPage(context, const AppsPage(filterChatOnly: true)); - MixpanelManager().pageOpened('Chat Apps'); - context.read().setIndex(2); - controller?.animateToPage(2, - duration: const Duration(milliseconds: 200), curve: Curves.easeInOut); - return; - } - if (s == null || s == provider.selectedChatAppId) return; - provider.setSelectedChatAppId(s); - await context.read().refreshMessages(dropdownSelected: true); - var app = provider.getSelectedApp(); - if (context.read().messages.isEmpty) { - context.read().sendInitialAppMessage(app); - } - }, - focusNode: focusNode, - // icon: Container(), - alignment: Alignment.center, - dropdownColor: Colors.black, - style: const TextStyle(color: Colors.white, fontSize: 16), - underline: Container(height: 0, color: Colors.transparent), - isExpanded: false, - itemHeight: 48, - padding: EdgeInsets.zero, - items: _getAppsDropdownItems(context, provider), + const SizedBox(width: 8), + const SizedBox( + width: 24, + child: Icon(Icons.keyboard_arrow_down, color: Colors.white60, size: 16), ), - ), + ], + ), + constraints: const BoxConstraints( + minWidth: 250.0, + maxWidth: 250.0, + maxHeight: 350.0, + ), + offset: + Offset((MediaQuery.sizeOf(context).width - 250) / 2 / MediaQuery.devicePixelRatioOf(context), 114), + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))), + onSelected: (String? val) async { + if (val == null || val == provider.selectedChatAppId) { + return; + } + + // clear chat + if (val == 'clear_chat') { + showDialog( + context: context, + builder: (ctx) { + return getDialog(context, () { + Navigator.of(context).pop(); + }, () { + context.read().clearChat(); + Navigator.of(context).pop(); + }, "Clear Chat?", "Are you sure you want to clear the chat? This action cannot be undone."); + }, + ); + return; + } + + // enable apps + if (val == 'enable') { + MixpanelManager().pageOpened('Chat Apps'); + context.read().setIndex(2); + controller?.animateToPage(2, duration: const Duration(milliseconds: 200), curve: Curves.easeInOut); + return; + } + + // select app by id + provider.setSelectedChatAppId(val); + await context.read().refreshMessages(dropdownSelected: true); + var app = provider.getSelectedApp(); + if (context.read().messages.isEmpty) { + context.read().sendInitialAppMessage(app); + } + }, + itemBuilder: (BuildContext context) { + return _getAppsDropdownItems(context, provider); + }, + color: Colors.grey.shade900, + ), + ), ); }), ); } + _getAppAvatar(App app) { + return CachedNetworkImage( + imageUrl: app.getImageUrl(), + imageBuilder: (context, imageProvider) { + return CircleAvatar( + backgroundColor: Colors.white, + radius: 12, + backgroundImage: imageProvider, + ); + }, + errorWidget: (context, url, error) { + return const CircleAvatar( + backgroundColor: Colors.white, + radius: 12, + child: Icon(Icons.error_outline_rounded), + ); + }, + progressIndicatorBuilder: (context, url, progress) => CircleAvatar( + backgroundColor: Colors.white, + radius: 12, + child: CircularProgressIndicator( + value: progress.progress, + valueColor: const AlwaysStoppedAnimation(Colors.white), + ), + ), + ); + } + + _getOmiAvatar() { + return Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(Assets.images.background.path), + fit: BoxFit.cover, + ), + borderRadius: const BorderRadius.all(Radius.circular(16.0)), + ), + height: 24, + width: 24, + child: Stack( + alignment: Alignment.center, + children: [ + Image.asset( + Assets.images.herologo.path, + height: 16, + width: 16, + ), + ], + ), + ); + } + _getAppsDropdownItems(BuildContext context, AppProvider provider) { + var selectedApp = provider.apps.firstWhereOrNull((app) => app.id == provider.selectedChatAppId); var items = [ - DropdownMenuItem( - value: 'no_selected', + const PopupMenuItem( + height: 40, + value: 'clear_chat', + child: Padding( + padding: EdgeInsets.only(left: 32), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Clear Chat', style: TextStyle(color: Colors.redAccent, fontSize: 16)), + SizedBox( + width: 24, + child: Icon(Icons.delete, color: Colors.redAccent, size: 16), + ), + ], + ), + ), + ) + ] + + [ + const PopupMenuItem( + height: 1, + child: Divider( + height: 1, + ), + ), + PopupMenuItem( + value: 'enable', + height: 40, child: Row( crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, children: [ - Container( - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage(Assets.images.background.path), - fit: BoxFit.cover, - ), - borderRadius: const BorderRadius.all(Radius.circular(16.0)), - ), - height: 24, + SizedBox( width: 24, - child: Stack( - alignment: Alignment.center, - children: [ - Image.asset( - Assets.images.herologo.path, - height: 16, - width: 16, - ), - ], + child: Icon(Icons.arrow_forward_ios, color: Colors.white, size: 16), + ), + const SizedBox(width: 8), + Expanded( + child: Container( + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Enable Apps', style: TextStyle(color: Colors.white, fontSize: 16)), + SizedBox( + width: 24, + child: Icon(Icons.apps, color: Colors.white60, size: 16), + ), + ], + ), ), ), + ], + ), + ) + ] + + [ + PopupMenuItem( + height: 1, + child: Divider( + height: 1, + ), + ), + PopupMenuItem( + height: 40, + value: 'no_selected', + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _getOmiAvatar(), const SizedBox(width: 10), - Text( - provider.apps.where((p) => p.enabled).isEmpty ? 'Enable Apps' : 'Omi', - style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w500, fontSize: 16), + Expanded( + child: Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Omi", + style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w500, fontSize: 16), + ), + selectedApp == null + ? const SizedBox( + width: 24, + child: Icon(Icons.check, color: Colors.white60, size: 16), + ) + : const SizedBox.shrink(), + ], + ), + ), ), - // const SizedBox(width: 40), - // Icon(Icons.arrow_drop_down, color: Colors.white, size: 24) ], ), ) ] + - provider.apps.where((p) => p.enabled && p.worksWithChat()).map>((App app) { - return DropdownMenuItem( + provider.apps.where((p) => p.enabled && p.worksWithChat()).map>((App app) { + return PopupMenuItem( + height: 40, value: app.id, child: Row( - crossAxisAlignment: CrossAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.start, children: [ - CachedNetworkImage( - imageUrl: app.getImageUrl(), - imageBuilder: (context, imageProvider) { - return CircleAvatar( - backgroundColor: Colors.white, - radius: 12, - backgroundImage: imageProvider, - ); - }, - errorWidget: (context, url, error) { - return const CircleAvatar( - backgroundColor: Colors.white, - radius: 12, - child: Icon(Icons.error_outline_rounded), - ); - }, - progressIndicatorBuilder: (context, url, progress) => CircleAvatar( - backgroundColor: Colors.white, - radius: 12, - child: CircularProgressIndicator( - value: progress.progress, - valueColor: const AlwaysStoppedAnimation(Colors.white), - ), + _getAppAvatar(app), + const SizedBox(width: 8), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + overflow: TextOverflow.fade, + app.name, + style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w500, fontSize: 16), + ), + ), + selectedApp?.id == app.id + ? const SizedBox( + width: 24, + child: Icon(Icons.check, color: Colors.white60, size: 16), + ) + : const SizedBox.shrink(), + ], ), ), - const SizedBox(width: 8), - Text( - app.name.length > 18 ? '${app.name.substring(0, 18)}...' : app.name + ' ' * (18 - app.name.length), - style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w500, fontSize: 16), - ) ], ), ); }).toList(); - if (provider.apps.where((p) => p.enabled).isNotEmpty) { - items.add(const DropdownMenuItem( - value: 'enable', - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - CircleAvatar( - backgroundColor: Colors.transparent, - maxRadius: 12, - child: Icon(Icons.star, color: Colors.purpleAccent), - ), - SizedBox(width: 8), - Text('Enable Apps ', style: TextStyle(color: Colors.white, fontWeight: FontWeight.w500, fontSize: 16)) - ], - ), - )); - } return items; } } diff --git a/backend/routers/chat.py b/backend/routers/chat.py index a2edeb9bf..5499aff0b 100644 --- a/backend/routers/chat.py +++ b/backend/routers/chat.py @@ -106,6 +106,8 @@ async def send_message_with_file( @router.delete('/v1/messages', tags=['chat'], response_model=Message) def clear_chat_messages(plugin_id: Optional[str] = None, uid: str = Depends(auth.get_current_user_uid)): + if plugin_id in ['null', '']: + plugin_id = None err = chat_db.clear_chat(uid, plugin_id=plugin_id) if err: raise HTTPException(status_code=500, detail='Failed to clear chat')