Skip to content

Commit

Permalink
clients: add UI notification batching and config to client
Browse files Browse the repository at this point in the history
This unifies notification batching logic on the client package instead
of individual client apps.

Also, it adds more granular configuration for notifications on bruig:
users can configure notification flags for PMs, GCMs and mentions inside
GCMs.

brclient is updated to use the same mechanism for emitting bell
commands.
  • Loading branch information
miki authored and miki-totefu committed Sep 24, 2024
1 parent 9911bc9 commit f250b8f
Show file tree
Hide file tree
Showing 21 changed files with 784 additions and 461 deletions.
75 changes: 46 additions & 29 deletions brclient/appstate.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,6 @@ type appState struct {

mimeMap atomic.Pointer[map[string]string]

// cmd to run when receiving messages. First element is bin, other are
// args.
bellCmd []string

unwelcomeError atomic.Pointer[error]

inboundMsgsMtx sync.Mutex
Expand Down Expand Up @@ -333,6 +329,31 @@ func (as *appState) run() error {
}()
}

// Setup UI notifications (requires having the nick after client runs).
go func() {
select {
case <-as.ctx.Done():
return
case <-as.c.AddressBookLoaded():
}

nick := as.c.LocalNick()
mentionRegexp, err := regexp.Compile("@" + nick)
if err != nil {
as.diagMsg("Unable to create mention regexp: %v", err)
return
}

as.c.NotificationManager().UpdateUIConfig(client.UINotificationsConfig{
PMs: true,
GCMMentions: true,
MaxLength: 255,
EmitInterval: 30 * time.Second,
MentionRegexp: mentionRegexp,
CancelEmissionChannel: as.ctx.Done(),
})
}()

as.wg.Wait()
if as.cmdHistoryFile != nil {
as.cmdHistoryFile.Close()
Expand Down Expand Up @@ -1355,29 +1376,6 @@ func (as *appState) handleRcvdText(s string, nick string) string {
// Cannonicalize line endings.
s = strescape.CannonicalizeNL(s)

if len(as.bellCmd) > 0 && as.bellCmd[0] == "*BEEP*" {
os.Stdout.Write([]byte("\a"))
} else if len(as.bellCmd) > 0 {
go func() {
cmd := append([]string{}, as.bellCmd...)
msg := s[:min(len(s), 100)] // truncate msg passed to cmd.

// Replace $src and $msg in command line args.
for i := 1; i < len(cmd); i++ {
cmd[i] = strings.Replace(cmd[i], "$src", nick, -1)
cmd[i] = strings.Replace(cmd[i], "$msg", msg, -1)
}

c := exec.Command(cmd[0], cmd[1:]...)
c.Stdout = nil
c.Stderr = nil
err := c.Start()
if err != nil {
as.diagMsg("Unable to run bellcmd: %v", err)
}
}()
}

return s
}

Expand Down Expand Up @@ -3720,7 +3718,7 @@ func newAppState(sendMsg func(tea.Msg), lndLogLines *sloglinesbuffer.Buffer,
return nil, err
}

// Parse bell command.
// Parse bell command and add UI notification handler when it exists.
var bellCmd []string
if args.BellCmd != "" {
r := regexp.MustCompile(`[^\s"]+|"([^"]*)"`)
Expand All @@ -3735,6 +3733,26 @@ func newAppState(sendMsg func(tea.Msg), lndLogLines *sloglinesbuffer.Buffer,
}
}
}
if len(bellCmd) > 0 {
ntfns.Register(client.OnUINotification(func(n client.UINotification) {
cmd := append([]string{}, bellCmd...)

// Replace $src and $msg in command line args.
for i := 1; i < len(cmd); i++ {
cmd[i] = strings.Replace(cmd[i], "$src", n.FromNick, -1)
cmd[i] = strings.Replace(cmd[i], "$msg", n.Text, -1)
}

c := exec.Command(cmd[0], cmd[1:]...)
c.Stdout = nil
c.Stderr = nil
err := c.Start()
if err != nil {
as.diagMsg("Unable to run bellcmd: %v", err)
}
}))

}

// Initialize client.
c, err := client.New(cfg)
Expand Down Expand Up @@ -3960,7 +3978,6 @@ func newAppState(sendMsg func(tea.Msg), lndLogLines *sloglinesbuffer.Buffer,
lnFundWalletChan: make(chan msgLNFundWalletReply),

winpin: args.WinPin,
bellCmd: bellCmd,
inviteFundsAccount: args.InviteFundsAccount,

collator: cfg.Collator,
Expand Down
22 changes: 20 additions & 2 deletions bruig/flutterui/bruig/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ void main(List<String> args) async {
// Ensure the platform bindings are initialized.
WidgetsFlutterBinding.ensureInitialized();

if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
windowManager.ensureInitialized();
}

// Create global models.
initGlobalLogModel();
initGlobalShutdownModel();
Expand Down Expand Up @@ -179,14 +183,16 @@ class _AppState extends State<App> with WindowListener {
@override
void initState() {
super.initState();
!isMobile ? windowManager.addListener(this) : null;
isMobile
? lifecycleListener =
AppLifecycleListener(onStateChange: onAppStateChanged)
: null;
handleNotifications();
initClient();
!isMobile ? windowManager.setPreventClose(true) : null;
if (!isMobile) {
windowManager.setPreventClose(true);
windowManager.addListener(this);
}
NotificationService().init();

widget.shutdown.addListener(shutdownChanged);
Expand All @@ -196,6 +202,7 @@ class _AppState extends State<App> with WindowListener {
void dispose() {
!isMobile ? windowManager.removeListener(this) : null;
widget.shutdown.removeListener(shutdownChanged);
!isMobile ? windowManager.addListener(this) : null;
super.dispose();
}

Expand Down Expand Up @@ -248,6 +255,16 @@ class _AppState extends State<App> with WindowListener {
clientStopped = widget.shutdown.clientStopped;
}

@override
void onWindowBlur() {
NotificationService().appInBackground = true;
}

@override
void onWindowFocus() {
NotificationService().appInBackground = false;
}

void initClient() async {
try {
var cfg = widget.cfg;
Expand Down Expand Up @@ -357,6 +374,7 @@ class _AppState extends State<App> with WindowListener {
await doWalletChecks(wasAlreadyRunning);
await client.fetchNetworkInfo();
await client.fetchMyAvatar();
NotificationService().updateUIConfig();
}

void handleNotifications() async {
Expand Down
25 changes: 14 additions & 11 deletions bruig/flutterui/bruig/lib/models/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import 'package:golib_plugin/definitions.dart';
import 'package:golib_plugin/golib_plugin.dart';
import 'package:intl/intl.dart';
import 'package:bruig/storage_manager.dart';
import 'package:bruig/notification_service.dart';
import 'package:provider/provider.dart';

const SCE_unknown = 0;
Expand Down Expand Up @@ -512,6 +511,13 @@ class ChatsListModel extends ChangeNotifier {
return idx == -1 ? null : _sorted[idx];
}

// firstByNick returns the first chatModel with the given uid (if one exists).
ChatModel? firstByUID(String id, {bool? isGC}) {
var idx = _sorted
.indexWhere((c) => c.id == id && (isGC == null || c.isGC == isGC));
return idx == -1 ? null : _sorted[idx];
}

// Sorting algo to attempt to retain order
static int _compareFunc(ChatModel a, ChatModel b) {
// If both are empty, sort by nick.
Expand Down Expand Up @@ -747,6 +753,13 @@ class ClientModel extends ChangeNotifier {
}
}

void setActiveByUID(String uid, {bool? isGC}) {
var c = activeChats.firstByUID(uid, isGC: isGC);
if (c != null) {
active = c;
}
}

Future<void> handleSubscriptions() async {
var newSubscriptions = await Golib.listSubscriptions();
for (var subscription in newSubscriptions) {
Expand Down Expand Up @@ -957,16 +970,6 @@ class ClientModel extends ChangeNotifier {
}
chat.append(ChatEventModel(evnt, source), false);

// Only do notifcications for GC messages or PMs
if (evnt is GCMsg || evnt is PM) {
if (source != null) {
if (!chat.active) {
NotificationService().showChatNotification(
evnt.msg, source.nick, chat.isGC, chat.nick);
}
}
}

// Make this the most recent chat.
activeChats._addActive(chat);
hiddenChats._remove(chat);
Expand Down
2 changes: 0 additions & 2 deletions bruig/flutterui/bruig/lib/models/feed.dart
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,6 @@ class FeedPostModel extends ChangeNotifier {
_comments.add(c);
}
}
NotificationService().showPostCommentNotification(post, c.nick, c.comment);
/*
var idx = _comments.indexWhere((e) => e.id == c.parentID);
Expand Down Expand Up @@ -338,7 +337,6 @@ class FeedModel extends ChangeNotifier {
_downloadingUserPosts.remove(newPost.summ.id);
}

NotificationService().showPostNotification(newPost.summ);
// Handle posts that replace a previously relayed post: the client removes
// the relayed post in favor of the one by the author, so remove such posts
// from the list.
Expand Down
Loading

0 comments on commit f250b8f

Please sign in to comment.