Skip to content

Commit eed1b63

Browse files
committed
Publish fixes
1 parent 05fc834 commit eed1b63

30 files changed

Lines changed: 1374 additions & 388 deletions

android/app/src/main/kotlin/com/bluebubbles/messaging/services/rustpush/APNService.kt

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,46 @@ class APNService : Service(), MsgReceiver {
4646
var pushState: NativePushState? = null
4747
private var started = false
4848
private val binder = APNBinder()
49-
private var ready = false;
50-
private var waitingHandleCb = ArrayList<(handle: ULong) -> Unit>();
51-
49+
private var ready = false
50+
private val waitingHandleCb = ArrayList<(handle: ULong) -> Unit>()
51+
private val waitingStartedCb = ArrayList<() -> Unit>()
5252
private val job = SupervisorJob()
5353
val scope = CoroutineScope(Dispatchers.IO + job)
5454

55-
5655
fun ready() {
5756
Log.i("launching agent", "ready")
5857
synchronized(waitingHandleCb) {
59-
ready = true;
58+
ready = true
6059
for (cb in waitingHandleCb) {
6160
cb(pushState?.getState() ?: 0UL)
6261
}
62+
waitingHandleCb.clear()
63+
}
64+
}
65+
66+
fun whenStarted(cb: () -> Unit) {
67+
var runNow = false
68+
synchronized(waitingStartedCb) {
69+
if (started) {
70+
runNow = true
71+
} else {
72+
waitingStartedCb.add(cb)
73+
}
74+
}
75+
if (runNow) {
76+
cb()
77+
}
78+
}
79+
80+
private fun markStarted() {
81+
val callbacks = ArrayList<() -> Unit>()
82+
synchronized(waitingStartedCb) {
83+
if (started) return
84+
started = true
85+
callbacks.addAll(waitingStartedCb)
86+
waitingStartedCb.clear()
6387
}
88+
callbacks.forEach { it() }
6489
}
6590

6691
override fun twofaEvent(success: Boolean) {
@@ -258,7 +283,7 @@ class APNService : Service(), MsgReceiver {
258283
notifyForeground()
259284
}
260285
launchAgent()
261-
started = true
286+
markStarted()
262287
}
263288
return super.onStartCommand(intent, flags, startId)
264289
}
@@ -271,9 +296,6 @@ class APNService : Service(), MsgReceiver {
271296

272297
override fun onBind(intent: Intent): IBinder {
273298
Log.i("trybindsfsf", "bound")
274-
if (!started) {
275-
throw Exception("APNService not started!")
276-
}
277299
return binder
278300
}
279301

@@ -285,19 +307,25 @@ class APNService : Service(), MsgReceiver {
285307
class APNClient(val context: Context) {
286308
private lateinit var mService: APNService
287309
private var mBound: Boolean = false
310+
private var mBinding: Boolean = false
288311
private var mCallback: ((service: APNService) -> Unit)? = null
289312

290313
private val connection = object : ServiceConnection {
291314
override fun onServiceConnected(className: ComponentName, service: IBinder) {
292315
// We've bound to LocalService, cast the IBinder and get LocalService instance.
293316
val binder = service as APNService.APNBinder
294317
mService = binder.getService()
295-
mBound = true
296-
mCallback?.let { it(mService) }
318+
mService.whenStarted {
319+
if (mBound) return@whenStarted
320+
mBound = true
321+
mBinding = false
322+
mCallback?.let { it(mService) }
323+
}
297324
}
298325

299326
override fun onServiceDisconnected(arg0: ComponentName) {
300327
mBound = false
328+
mBinding = false
301329
}
302330
}
303331

@@ -307,15 +335,37 @@ class APNClient(val context: Context) {
307335

308336
fun bind(cb: (service: APNService) -> Unit) {
309337
mCallback = cb
338+
if (mBound) {
339+
mService.whenStarted {
340+
mCallback?.let { it(mService) }
341+
}
342+
return
343+
}
344+
if (mBinding) return
345+
346+
val serviceIntent = Intent(context, APNService::class.java)
347+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
348+
context.startForegroundService(serviceIntent)
349+
} else {
350+
context.startService(serviceIntent)
351+
}
352+
310353
Intent(context, APNService::class.java).also { intent ->
311354
Log.i("trybindsfsf", "trying to bind")
355+
mBinding = true
312356
val result = context.bindService(intent, connection, 0)
313-
Log.i("trybindresult", result.toString());
357+
if (!result) {
358+
mBinding = false
359+
}
360+
Log.i("trybindresult", result.toString())
314361
}
315362
}
316363

317364
fun destroy() {
318-
context.unbindService(connection)
365+
if (mBound || mBinding) {
366+
context.unbindService(connection)
367+
}
319368
mBound = false
369+
mBinding = false
320370
}
321-
}
371+
}

lib/app/layouts/chat_creator/chat_creator.dart

Lines changed: 135 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ class ChatCreatorState extends OptimizedState<ChatCreator> {
7373
ConversationViewController? oldController;
7474
Timer? _debounce;
7575
Completer<void>? createCompleter;
76+
int selectedSuggestionIndex = -1;
77+
int previousSuggestionIndex = -1;
78+
final Map<int, GlobalKey> suggestionKeys = {};
7679

7780
bool canCreateGroupChats = backend.canCreateGroupChats();
7881

@@ -134,6 +137,12 @@ class ChatCreatorState extends OptimizedState<ChatCreator> {
134137
if (addressController.text.isNotEmpty) {
135138
filteredChats.sort((a, b) => a.participants.length.compareTo(b.participants.length));
136139
}
140+
final totalSuggestions = _totalSuggestions;
141+
if (addressController.text.isEmpty || totalSuggestions == 0) {
142+
selectedSuggestionIndex = -1;
143+
} else if (selectedSuggestionIndex < 0 || selectedSuggestionIndex >= totalSuggestions) {
144+
selectedSuggestionIndex = 0;
145+
}
137146
});
138147
});
139148
});
@@ -216,6 +225,69 @@ class ChatCreatorState extends OptimizedState<ChatCreator> {
216225
findExistingChat();
217226
}
218227

228+
GlobalKey _suggestionKey(int index) => suggestionKeys.putIfAbsent(index, () => GlobalKey());
229+
230+
void _scrollSelectedSuggestionIntoView() {
231+
if (selectedSuggestionIndex < 0) return;
232+
final movingUp = previousSuggestionIndex >= 0 && selectedSuggestionIndex < previousSuggestionIndex;
233+
WidgetsBinding.instance.addPostFrameCallback((_) {
234+
if (!mounted) return;
235+
final context = _suggestionKey(selectedSuggestionIndex).currentContext;
236+
if (context == null) return;
237+
Scrollable.ensureVisible(
238+
context,
239+
duration: const Duration(milliseconds: 150),
240+
alignment: movingUp ? 0 : 1,
241+
alignmentPolicy: ScrollPositionAlignmentPolicy.explicit,
242+
);
243+
});
244+
}
245+
246+
int get _totalSuggestions {
247+
int contactSuggestions = 0;
248+
for (final contact in filteredContacts) {
249+
contactSuggestions += getUniqueNumbers(contact.phones).length + getUniqueEmails(contact.emails).length;
250+
}
251+
return filteredChats.length + contactSuggestions;
252+
}
253+
254+
Future<bool> _selectSuggestionAt(int index) async {
255+
if (index < 0) return false;
256+
if (index < filteredChats.length) {
257+
addSelectedList(filteredChats[index].participants
258+
.where((e) => selectedContacts.firstWhereOrNull((c) => c.address == e.address) == null)
259+
.map((e) => SelectedContact(
260+
displayName: e.displayName,
261+
address: e.address,
262+
isIMessage: filteredChats[index].isIMessage,
263+
)));
264+
return true;
265+
}
266+
267+
int contactIndex = index - filteredChats.length;
268+
for (final contact in filteredContacts) {
269+
final phones = getUniqueNumbers(contact.phones);
270+
if (contactIndex < phones.length) {
271+
final address = phones[contactIndex];
272+
if (selectedContacts.firstWhereOrNull((c) => c.address == address) != null) return true;
273+
await addSelected(SelectedContact(displayName: contact.displayName, address: address));
274+
return true;
275+
}
276+
contactIndex -= phones.length;
277+
278+
final emails = getUniqueEmails(contact.emails);
279+
if (contactIndex < emails.length) {
280+
final address = emails[contactIndex];
281+
if (selectedContacts.firstWhereOrNull((c) => c.address == address) != null) return true;
282+
await addSelected(SelectedContact(displayName: contact.displayName, address: address));
283+
return true;
284+
}
285+
contactIndex -= emails.length;
286+
}
287+
288+
return false;
289+
}
290+
219291
Future<Chat?> findExistingChat({bool checkDeleted = false, bool update = true}) async {
220292
// no selected items, remove message view
221293
if (selectedContacts.isEmpty) {
@@ -318,6 +390,9 @@ class ChatCreatorState extends OptimizedState<ChatCreator> {
318390
}
319391

320392
Future<void> addressOnSubmitted() async {
393+
if (selectedSuggestionIndex >= 0 && await _selectSuggestionAt(selectedSuggestionIndex)) {
394+
return;
395+
}
321396
final text = addressController.text;
322397
if (text.isEmail || text.isPhoneNumber) {
323398
await addSelected(SelectedContact(
@@ -500,6 +575,32 @@ class ChatCreatorState extends OptimizedState<ChatCreator> {
500575
event.logicalKey == LogicalKeyboardKey.tab) {
501576
messageNode.requestFocus();
502577
return KeyEventResult.handled;
578+
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown && _totalSuggestions > 0) {
579+
if (selectedSuggestionIndex >= _totalSuggestions - 1) {
580+
messageNode.requestFocus();
581+
return KeyEventResult.handled;
582+
}
583+
setState(() {
584+
previousSuggestionIndex = selectedSuggestionIndex;
585+
selectedSuggestionIndex = min(
586+
selectedSuggestionIndex < 0 ? 0 : selectedSuggestionIndex + 1,
587+
_totalSuggestions - 1,
588+
);
589+
});
590+
_scrollSelectedSuggestionIntoView();
591+
return KeyEventResult.handled;
592+
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp && _totalSuggestions > 0) {
593+
setState(() {
594+
previousSuggestionIndex = selectedSuggestionIndex;
595+
selectedSuggestionIndex = max(selectedSuggestionIndex - 1, 0);
596+
});
597+
_scrollSelectedSuggestionIntoView();
598+
return KeyEventResult.handled;
599+
} else if ((event.logicalKey == LogicalKeyboardKey.enter ||
600+
event.logicalKey == LogicalKeyboardKey.select) &&
601+
!HardwareKeyboard.instance.isShiftPressed) {
602+
addressOnSubmitted();
603+
return KeyEventResult.handled;
503604
}
504605
}
505606
return KeyEventResult.ignored;
@@ -643,8 +744,10 @@ class ChatCreatorState extends OptimizedState<ChatCreator> {
643744
_title =
644745
chat.participants.length > 1 ? "Group Chat" : chat.participants[0].fakeName;
645746
}
747+
final isSelectedSuggestion = selectedSuggestionIndex == index;
646748
return Material(
647-
color: Colors.transparent,
749+
key: _suggestionKey(index),
750+
color: isSelectedSuggestion ? context.theme.colorScheme.outline.withOpacity(0.2) : Colors.transparent,
648751
child: InkWell(
649752
onTap: () {
650753
addSelectedList(chat.participants
@@ -678,44 +781,56 @@ class ChatCreatorState extends OptimizedState<ChatCreator> {
678781
SliverList(
679782
delegate: SliverChildBuilderDelegate(
680783
(context, index) {
681-
final contact = filteredContacts[index];
682-
contact.phones = getUniqueNumbers(contact.phones);
683-
contact.emails = getUniqueEmails(contact.emails);
684-
final hideInfo =
685-
ss.settings.redactedMode.value && ss.settings.hideContactInfo.value;
686-
return Column(
687-
key: ValueKey(contact.id),
688-
mainAxisSize: MainAxisSize.min,
689-
children: [
690-
...contact.phones.map((e) => Material(
691-
color: Colors.transparent,
784+
final contact = filteredContacts[index];
785+
final phones = getUniqueNumbers(contact.phones);
786+
final emails = getUniqueEmails(contact.emails);
787+
contact.phones = phones;
788+
contact.emails = emails;
789+
final hideInfo =
790+
ss.settings.redactedMode.value && ss.settings.hideContactInfo.value;
791+
int suggestionOffset = filteredChats.length;
792+
for (int i = 0; i < index; i++) {
793+
suggestionOffset += getUniqueNumbers(filteredContacts[i].phones).length + getUniqueEmails(filteredContacts[i].emails).length;
794+
}
795+
return Column(
796+
key: ValueKey(contact.id),
797+
mainAxisSize: MainAxisSize.min,
798+
children: [
799+
...phones.asMap().entries.map((entry) => Material(
800+
key: _suggestionKey(suggestionOffset + entry.key),
801+
color: selectedSuggestionIndex == suggestionOffset + entry.key
802+
? context.theme.colorScheme.outline.withOpacity(0.2)
803+
: Colors.transparent,
692804
child: InkWell(
693805
onTap: () {
694-
if (selectedContacts.firstWhereOrNull((c) => c.address == e) !=
806+
if (selectedContacts.firstWhereOrNull((c) => c.address == entry.value) !=
695807
null) return;
696808
addSelected(
697-
SelectedContact(displayName: contact.displayName, address: e));
809+
SelectedContact(displayName: contact.displayName, address: entry.value));
698810
},
699811
child: ChatCreatorTile(
700812
title: hideInfo ? "Contact" : contact.displayName,
701-
subtitle: hideInfo ? "" : e,
813+
subtitle: hideInfo ? "" : entry.value,
702814
contact: contact,
703815
format: true,
704816
),
705817
),
706818
)),
707-
...contact.emails.map((e) => Material(
708-
color: Colors.transparent,
819+
...emails.asMap().entries.map((entry) => Material(
820+
key: _suggestionKey(suggestionOffset + phones.length + entry.key),
821+
color: selectedSuggestionIndex == suggestionOffset + phones.length + entry.key
822+
? context.theme.colorScheme.outline.withOpacity(0.2)
823+
: Colors.transparent,
709824
child: InkWell(
710825
onTap: () {
711-
if (selectedContacts.firstWhereOrNull((c) => c.address == e) !=
826+
if (selectedContacts.firstWhereOrNull((c) => c.address == entry.value) !=
712827
null) return;
713828
addSelected(
714-
SelectedContact(displayName: contact.displayName, address: e));
829+
SelectedContact(displayName: contact.displayName, address: entry.value));
715830
},
716831
child: ChatCreatorTile(
717832
title: hideInfo ? "Contact" : contact.displayName,
718-
subtitle: hideInfo ? "" : e,
833+
subtitle: hideInfo ? "" : entry.value,
719834
contact: contact,
720835
),
721836
),

lib/app/layouts/conversation_list/pages/conversation_list.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class ConversationListController extends StatefulController {
3636
final ScrollController iosScrollController = ScrollController();
3737
final ScrollController materialScrollController = ScrollController();
3838
final ScrollController samsungScrollController = ScrollController();
39+
final FocusNode newMessageFocusNode = FocusNode(skipTraversal: true);
3940
final List<Chat> selectedChats = [];
4041
bool showMaterialFABText = true;
4142
double materialScrollStartPosition = 0;
@@ -69,6 +70,7 @@ class ConversationListController extends StatefulController {
6970
@override
7071
void dispose() {
7172
if (!kIsWeb) sub?.cancel();
73+
newMessageFocusNode.dispose();
7274
super.dispose();
7375
}
7476

0 commit comments

Comments
 (0)