diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart index f4589364..7f16ab49 100644 --- a/lib/cwtch/cwtchNotifier.dart +++ b/lib/cwtch/cwtchNotifier.dart @@ -133,6 +133,8 @@ class CwtchNotifier { notificationManager.notify("New Message From Peer!"); if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != data["RemotePeer"]) { profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.unreadMessages++; + } else { + profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.newMarker++; } profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.totalMessages++; profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["RemotePeer"], DateTime.now()); @@ -181,6 +183,8 @@ class CwtchNotifier { //if not currently open if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != data["GroupID"]) { profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.unreadMessages++; + } else { + profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.newMarker++; } var timestampSent = DateTime.tryParse(data['TimestampSent'])!; diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index 2d088390..b3793cca 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -1,6 +1,7 @@ { "@@locale": "de", - "@@last_modified": "2021-11-10T18:47:30+01:00", + "@@last_modified": "2021-11-11T01:02:08+01:00", + "newMessagesLabel": "New Messages", "localeRU": "Russian", "copyServerKeys": "Copy keys", "verfiyResumeButton": "Verify\/resume", diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index e085360a..70f13992 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1,6 +1,7 @@ { "@@locale": "en", - "@@last_modified": "2021-11-10T18:47:30+01:00", + "@@last_modified": "2021-11-11T01:02:08+01:00", + "newMessagesLabel": "New Messages", "localeRU": "Russian", "copyServerKeys": "Copy keys", "verfiyResumeButton": "Verify\/resume", diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index 69515e76..ba0a54bc 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -1,6 +1,7 @@ { "@@locale": "es", - "@@last_modified": "2021-11-10T18:47:30+01:00", + "@@last_modified": "2021-11-11T01:02:08+01:00", + "newMessagesLabel": "New Messages", "localeRU": "Russian", "copyServerKeys": "Copy keys", "verfiyResumeButton": "Verify\/resume", diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 2df7d849..bb6e8810 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -1,6 +1,7 @@ { "@@locale": "fr", - "@@last_modified": "2021-11-10T18:47:30+01:00", + "@@last_modified": "2021-11-11T01:02:08+01:00", + "newMessagesLabel": "New Messages", "localeRU": "Russian", "copyServerKeys": "Copier les clés", "verfiyResumeButton": "Vérifier\/reprendre", diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index 4722cfa9..fce93d69 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -1,6 +1,7 @@ { "@@locale": "it", - "@@last_modified": "2021-11-10T18:47:30+01:00", + "@@last_modified": "2021-11-11T01:02:08+01:00", + "newMessagesLabel": "New Messages", "localeRU": "Russian", "copyServerKeys": "Copy keys", "verfiyResumeButton": "Verify\/resume", diff --git a/lib/l10n/intl_pl.arb b/lib/l10n/intl_pl.arb index 576b41db..aa2e5366 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -1,6 +1,7 @@ { "@@locale": "pl", - "@@last_modified": "2021-11-10T18:47:30+01:00", + "@@last_modified": "2021-11-11T01:02:08+01:00", + "newMessagesLabel": "New Messages", "localeRU": "Russian", "copyServerKeys": "Kopiuj klucze", "verfiyResumeButton": "Zweryfikuj\/wznów", diff --git a/lib/l10n/intl_pt.arb b/lib/l10n/intl_pt.arb index 169bdd72..137e8a2b 100644 --- a/lib/l10n/intl_pt.arb +++ b/lib/l10n/intl_pt.arb @@ -1,6 +1,7 @@ { "@@locale": "pt", - "@@last_modified": "2021-11-10T18:47:30+01:00", + "@@last_modified": "2021-11-11T01:02:08+01:00", + "newMessagesLabel": "New Messages", "localeRU": "Russian", "copyServerKeys": "Copy keys", "verfiyResumeButton": "Verify\/resume", diff --git a/lib/model.dart b/lib/model.dart index 9268ca0d..23780557 100644 --- a/lib/model.dart +++ b/lib/model.dart @@ -507,6 +507,8 @@ class ContactInfoState extends ChangeNotifier { late int _totalMessages = 0; late DateTime _lastMessageTime; late Map> keys; + int _newMarker = 0; + DateTime _newMarkerClearAt = DateTime.now(); // todo: a nicer way to model contacts, groups and other "entities" late bool _isGroup; @@ -587,10 +589,36 @@ class ContactInfoState extends ChangeNotifier { int get unreadMessages => this._unreadMessages; set unreadMessages(int newVal) { + // don't reset newMarker position when unreadMessages is being cleared + if (newVal > 0) { + this._newMarker = newVal; + } else { + this._newMarkerClearAt = DateTime.now().add(const Duration(minutes:2)); + } this._unreadMessages = newVal; notifyListeners(); } + int get newMarker { + if (DateTime.now().isAfter(this._newMarkerClearAt)) { + // perform heresy + this._newMarker = 0; + // no need to notifyListeners() because presumably this getter is + // being called from a renderer anyway + } + return this._newMarker; + } + // what's a getter that sometimes sets without a setter + // that sometimes doesn't set + set newMarker(int newVal) { + // only unreadMessages++ can set newMarker = 1; + // avoids drawing a marker when the convo is already open + if (newVal > 1) { + this._newMarker = newVal; + notifyListeners(); + } + } + int get totalMessages => this._totalMessages; set totalMessages(int newVal) { this._totalMessages = newVal; diff --git a/lib/views/contactsview.dart b/lib/views/contactsview.dart index 0ac5ae72..895b06ac 100644 --- a/lib/views/contactsview.dart +++ b/lib/views/contactsview.dart @@ -4,7 +4,6 @@ import 'package:cwtch/views/torstatusview.dart'; import 'package:cwtch/widgets/contactrow.dart'; import 'package:cwtch/widgets/profileimage.dart'; import 'package:cwtch/widgets/textfield.dart'; -import 'package:cwtch/widgets/tor_icon.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import '../main.dart'; diff --git a/lib/views/doublecolview.dart b/lib/views/doublecolview.dart index ebd7ffff..99d2aa9f 100644 --- a/lib/views/doublecolview.dart +++ b/lib/views/doublecolview.dart @@ -35,7 +35,7 @@ class _DoubleColumnViewState extends State { ChangeNotifierProvider.value(value: Provider.of(context)), ChangeNotifierProvider.value( value: flwtch.selectedConversation != null ? Provider.of(context).contactList.getContact(flwtch.selectedConversation!)! : ContactInfoState("", "")), - ], child: Container(child: MessageView())), + ], child: Container(key: Key(flwtch.selectedConversation??"never_this"), child: MessageView())), ), ], ); diff --git a/lib/views/messageview.dart b/lib/views/messageview.dart index 0c9ce028..f35460fb 100644 --- a/lib/views/messageview.dart +++ b/lib/views/messageview.dart @@ -40,15 +40,11 @@ class _MessageViewState extends State { @override void initState() { scrollListener.itemPositions.addListener(() { - if (scrollListener.itemPositions.value.length == 0) { - return; - } - var first = scrollListener.itemPositions.value.first.index; - var last = scrollListener.itemPositions.value.last.index; - // sometimes these go hi->lo and sometimes they go lo->hi because [who tf knows] - if ((first == 0 || last == 0) && Provider.of(context, listen: false).unreadMessagesBelow == true) { - Provider.of(context, listen: false).initialScrollIndex = 0; - Provider.of(context, listen: false).unreadMessagesBelow = false; + if (scrollListener.itemPositions.value.length != 0 && + Provider.of(context, listen: false).unreadMessagesBelow == true && + scrollListener.itemPositions.value.any((element) => element.index == 0)) { + Provider.of(context, listen: false).initialScrollIndex = 0; + Provider.of(context, listen: false).unreadMessagesBelow = false; } }); super.initState(); @@ -59,7 +55,7 @@ class _MessageViewState extends State { var appState = Provider.of(context, listen: false); // using "8" because "# of messages that fit on one screen" isnt trivial to calculate at this point - if (appState.initialScrollIndex > 8 && appState.unreadMessagesBelow == false) { + if (appState.initialScrollIndex > 4 && appState.unreadMessagesBelow == false) { WidgetsFlutterBinding.ensureInitialized().addPostFrameCallback((timeStamp) { appState.unreadMessagesBelow = true; }); @@ -111,6 +107,8 @@ class _MessageViewState extends State { ? FloatingActionButton( child: Icon(Icons.arrow_downward), onPressed: () { + Provider.of(context, listen: false).initialScrollIndex = 0; + Provider.of(context, listen: false).unreadMessagesBelow = false; scrollController.scrollTo(index: 0, duration: Duration(milliseconds: 600)); }) : null, @@ -216,6 +214,7 @@ class _MessageViewState extends State { focusNode.requestFocus(); Future.delayed(const Duration(milliseconds: 80), () { Provider.of(context, listen: false).totalMessages++; + Provider.of(context, listen: false).newMarker++; // Resort the contact list... Provider.of(context, listen: false).contactList.updateLastMessageTime(Provider.of(context, listen: false).onion, DateTime.now()); }); diff --git a/lib/widgets/messagelist.dart b/lib/widgets/messagelist.dart index f541a12d..33406b72 100644 --- a/lib/widgets/messagelist.dart +++ b/lib/widgets/messagelist.dart @@ -71,7 +71,7 @@ class _MessageListState extends State { ? ScrollablePositionedList.builder( itemPositionsListener: widget.scrollListener, itemScrollController: widget.scrollController, - initialScrollIndex: Provider.of(outerContext, listen: false).initialScrollIndex, + initialScrollIndex: initi > 4 ? initi - 4 : 0, itemCount: Provider.of(outerContext).totalMessages, reverse: true, // NOTE: There seems to be a bug in flutter that corrects the mouse wheel scroll, but not the drag direction... itemBuilder: (itemBuilderContext, index) { diff --git a/lib/widgets/messagerow.dart b/lib/widgets/messagerow.dart index efaa670f..d4789266 100644 --- a/lib/widgets/messagerow.dart +++ b/lib/widgets/messagerow.dart @@ -159,7 +159,7 @@ class MessageRowState extends State with SingleTickerProviderStateMi ]; } var size = MediaQuery.of(context).size; - return MouseRegion( + var mr = MouseRegion( // For desktop... onHover: (event) { setState(() { @@ -197,6 +197,31 @@ class MessageRowState extends State with SingleTickerProviderStateMi mainAxisAlignment: MainAxisAlignment.center, children: widgetRow, ))))); + var mark = Provider.of(context).newMarker; + if (mark > 0 && mark == Provider.of(context).totalMessages - Provider.of(context).messageIndex) { + return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Align(alignment:Alignment.center ,child:_bubbleNew()), mr]); + } else { + return mr; + } + } + + Widget _bubbleNew() { + return Container( + decoration: BoxDecoration( + color: Provider.of(context).theme.messageFromMeBackgroundColor(), + border: Border.all( + color: Provider.of(context).theme.messageFromMeBackgroundColor(), + width: 1), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(8), + topRight: Radius.circular(8), + bottomLeft: Radius.circular(8), + bottomRight: Radius.circular(8), + ), + ), + child: Padding( + padding: EdgeInsets.all(9.0), + child: Text(AppLocalizations.of(context)!.newMessagesLabel))); } void _runAnimation(Offset pixelsPerSecond, Size size) {