Merge pull request 'Support Conversation Search, Upgrade Cwtch, Patch support for downloading new Cwtch library name formats' (#699) from search into trunk
continuous-integration/drone/push Build is pending
Details
continuous-integration/drone/push Build is pending
Details
Reviewed-on: #699 Reviewed-by: Dan Ballard <dan@openprivacy.ca>
This commit is contained in:
commit
1b35f8a32b
|
@ -1 +1 @@
|
|||
2023-07-13-19-54-v0.0.5-4-g2e7a9be
|
||||
2023-08-02-09-30-v0.0.5-13-g6fdcf5b
|
|
@ -517,6 +517,12 @@ class MainActivity: FlutterActivity() {
|
|||
result.success(Cwtch.importProfile(file, pass))
|
||||
return
|
||||
}
|
||||
"SearchConversations" -> {
|
||||
val profile: String = call.argument("ProfileOnion") ?: ""
|
||||
val pattern: String = call.argument("pattern") ?: ""
|
||||
result.success(Cwtch.searchConversations(profile, pattern))
|
||||
return
|
||||
}
|
||||
"ReconnectCwtchForeground" -> {
|
||||
Cwtch.reconnectCwtchForeground()
|
||||
}
|
||||
|
|
|
@ -4,4 +4,5 @@ VERSION=`cat LIBCWTCH-GO.version`
|
|||
echo $VERSION
|
||||
|
||||
curl --fail https://build.openprivacy.ca/files/libCwtch-autobindings-$VERSION/android/cwtch.aar --output android/cwtch/cwtch.aar
|
||||
curl --fail https://build.openprivacy.ca/files/libCwtch-autobindings-$VERSION/linux/libCwtch.so --output linux/libCwtch.so
|
||||
# FIXME...at some point we need to support different linux architectures...for now rely on existing expectations and rename x64 lib
|
||||
curl --fail https://build.openprivacy.ca/files/libCwtch-autobindings-$VERSION/linux/libCwtch.x64.so --output linux/libCwtch.so
|
|
@ -143,4 +143,7 @@ abstract class Cwtch {
|
|||
Future<String> TranslateMessage(String profile, int conversation, int message, String language);
|
||||
|
||||
bool IsBlodeuweddSupported();
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<String> SearchConversations(String profile, String pattern);
|
||||
}
|
||||
|
|
|
@ -324,6 +324,7 @@ class CwtchNotifier {
|
|||
torStatus.updateVersion(data["Data"]);
|
||||
break;
|
||||
case "UpdateServerInfo":
|
||||
EnvironmentConfig.debugLog("NewEvent UpdateServerInfo $type $data");
|
||||
profileCN.getProfile(data["ProfileOnion"])?.replaceServers(data["ServerList"]);
|
||||
break;
|
||||
case "TokenManagerInfo":
|
||||
|
@ -460,6 +461,12 @@ class CwtchNotifier {
|
|||
profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(handle)?.acnCircuit = data["Data"];
|
||||
}
|
||||
break;
|
||||
case "SearchResult":
|
||||
String searchID = data["SearchID"];
|
||||
var conversationIdentifier = int.parse(data["ConversationID"]);
|
||||
var messageIndex = int.parse(data["RowIndex"]);
|
||||
profileCN.getProfile(data["ProfileOnion"])?.handleSearchResult(searchID, conversationIdentifier, messageIndex);
|
||||
break;
|
||||
default:
|
||||
EnvironmentConfig.debugLog("unhandled event: $type");
|
||||
}
|
||||
|
|
|
@ -788,7 +788,7 @@ class CwtchFfi implements Cwtch {
|
|||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<String> GetMessageByID(String profile, int handle, int index) async {
|
||||
var getMessageC = library.lookup<NativeFunction<get_json_blob_from_str_int_int_function>>("c_GetMessageByID");
|
||||
var getMessageC = library.lookup<NativeFunction<get_json_blob_from_str_int_int_function>>("c_GetMessageById");
|
||||
// ignore: non_constant_identifier_names
|
||||
final GetMessage = getMessageC.asFunction<GetJsonBlobFromStrIntIntFn>();
|
||||
final utf8profile = profile.toNativeUtf8();
|
||||
|
@ -1027,4 +1027,20 @@ class CwtchFfi implements Cwtch {
|
|||
malloc.free(utf8profile);
|
||||
malloc.free(utf8onion);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> SearchConversations(String profile, String pattern) async {
|
||||
var searchConversationsC = library.lookup<NativeFunction<string_string_to_string_function>>("c_SearchConversations");
|
||||
// ignore: non_constant_identifier_names
|
||||
final SearchConversations = searchConversationsC.asFunction<StringFromStringStringFn>();
|
||||
final utf8profile = profile.toNativeUtf8();
|
||||
final utf8pattern = pattern.toNativeUtf8();
|
||||
EnvironmentConfig.debugLog("Searching for $profile $pattern");
|
||||
Pointer<Utf8> searchIDRaw = SearchConversations(utf8profile, utf8profile.length, utf8pattern, utf8pattern.length);
|
||||
String searchID = searchIDRaw.toDartString();
|
||||
_UnsafeFreePointerAnyUseOfThisFunctionMustBeDoubleApproved(searchIDRaw);
|
||||
malloc.free(utf8profile);
|
||||
malloc.free(utf8pattern);
|
||||
return searchID;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -415,4 +415,9 @@ class CwtchGomobile implements Cwtch {
|
|||
void AttemptReconnection(String profile, String onion) {
|
||||
cwtchPlatform.invokeMethod("PeerWithOnion", {"ProfileOnion": profile, "onion": onion});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> SearchConversations(String profile, String pattern) async {
|
||||
return await cwtchPlatform.invokeMethod("SearchConversations", {"ProfileOnion": profile, "pattern": pattern});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ class AppState extends ChangeNotifier {
|
|||
String appError = "";
|
||||
String? _selectedProfile;
|
||||
int? _selectedConversation;
|
||||
int? _selectedSearchMessage;
|
||||
int _initialScrollIndex = 0;
|
||||
bool _unreadMessagesBelow = false;
|
||||
bool _disableFilePicker = false;
|
||||
|
@ -54,6 +55,12 @@ class AppState extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
int? get selectedSearchMessage => _selectedSearchMessage;
|
||||
set selectedSearchMessage(int? newVal) {
|
||||
this._selectedSearchMessage = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
bool get disableFilePicker => _disableFilePicker;
|
||||
set disableFilePicker(bool newVal) {
|
||||
this._disableFilePicker = newVal;
|
||||
|
|
|
@ -68,6 +68,7 @@ class ContactInfoState extends ChangeNotifier {
|
|||
MessageDraft _messageDraft = MessageDraft.empty();
|
||||
|
||||
var _hoveredIndex = -1;
|
||||
var _pendingScroll = -1;
|
||||
|
||||
ContactInfoState(
|
||||
this.profileOnion,
|
||||
|
@ -438,6 +439,12 @@ class ContactInfoState extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
int get pendingScroll => _pendingScroll;
|
||||
set pendingScroll(int newVal) {
|
||||
this._pendingScroll = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
String statusString(BuildContext context) {
|
||||
switch (this.availabilityStatus) {
|
||||
case ProfileStatusMenu.available:
|
||||
|
|
|
@ -160,6 +160,7 @@ class ById implements CacheHandler {
|
|||
if (messageInfo == null) {
|
||||
return Future.value(null);
|
||||
}
|
||||
EnvironmentConfig.debugLog("fetching $profileOnion $conversationIdentifier $id ${messageInfo.wrapper}");
|
||||
cache.addUnindexed(messageInfo);
|
||||
return Future.value(messageInfo);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
|||
|
||||
import 'package:cwtch/config.dart';
|
||||
import 'package:cwtch/models/remoteserver.dart';
|
||||
import 'package:cwtch/models/search.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
|
||||
|
@ -10,6 +11,7 @@ import '../views/contactsview.dart';
|
|||
import 'contact.dart';
|
||||
import 'contactlist.dart';
|
||||
import 'filedownloadprogress.dart';
|
||||
import 'message.dart';
|
||||
import 'messagecache.dart';
|
||||
import 'profileservers.dart';
|
||||
|
||||
|
@ -91,6 +93,23 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
// Code for managing the state of the profile-wide search feature...
|
||||
String activeSearchID = "";
|
||||
List<SearchResult> activeSearchResults = List.empty(growable: true);
|
||||
|
||||
void newSearch(String activeSearchID) {
|
||||
this.activeSearchID = activeSearchID;
|
||||
this.activeSearchResults.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void handleSearchResult(String searchID, int conversationIdentifier, int messageIndex) {
|
||||
if (searchID == activeSearchID) {
|
||||
activeSearchResults.add(SearchResult(searchID: searchID, conversationIdentifier: conversationIdentifier, messageIndex: messageIndex));
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Parse out the server list json into our server info state struct...
|
||||
void replaceServers(String serversJson) {
|
||||
if (serversJson != "" && serversJson != "null") {
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
class SearchResult {
|
||||
String searchID;
|
||||
int conversationIdentifier;
|
||||
int messageIndex;
|
||||
SearchResult({required this.searchID, required this.conversationIdentifier, required this.messageIndex});
|
||||
}
|
|
@ -7,6 +7,7 @@ import 'package:cwtch/models/contact.dart';
|
|||
import 'package:cwtch/models/contactlist.dart';
|
||||
import 'package:cwtch/models/profile.dart';
|
||||
import 'package:cwtch/models/profilelist.dart';
|
||||
import 'package:cwtch/models/search.dart';
|
||||
import 'package:cwtch/views/profileserversview.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cwtch/widgets/contactrow.dart';
|
||||
|
@ -15,7 +16,9 @@ import 'package:cwtch/widgets/textfield.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
import '../config.dart';
|
||||
import '../main.dart';
|
||||
import '../models/message.dart';
|
||||
import '../settings.dart';
|
||||
import 'addcontactview.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
@ -35,10 +38,23 @@ class ContactsView extends StatefulWidget {
|
|||
}
|
||||
|
||||
// selectConversation can be called from anywhere to set the active conversation
|
||||
void selectConversation(BuildContext context, int handle) {
|
||||
void selectConversation(BuildContext context, int handle, int? messageIndex) {
|
||||
int? index = null;
|
||||
|
||||
if (messageIndex != null) {
|
||||
// this message is loaded
|
||||
Provider.of<AppState>(context, listen: false).selectedSearchMessage = messageIndex;
|
||||
Provider.of<AppState>(context, listen: false).initialScrollIndex = messageIndex!;
|
||||
EnvironmentConfig.debugLog("Looked up index $messageIndex");
|
||||
}
|
||||
|
||||
if (handle == Provider.of<AppState>(context, listen: false).selectedConversation) {
|
||||
if (messageIndex != null) {
|
||||
Provider.of<ContactInfoState>(context, listen: false).messageScrollController.scrollTo(index: messageIndex, duration: Duration(milliseconds: 100));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// requery instead of using contactinfostate directly because sometimes listview gets confused about data that resorts
|
||||
var unread = Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(handle)!.unreadMessages;
|
||||
var previouslySelected = Provider.of<AppState>(context, listen: false).selectedConversation;
|
||||
|
@ -48,9 +64,12 @@ void selectConversation(BuildContext context, int handle) {
|
|||
Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(handle)!.selected();
|
||||
|
||||
// triggers update in Double/TripleColumnView
|
||||
Provider.of<AppState>(context, listen: false).initialScrollIndex = unread;
|
||||
Provider.of<AppState>(context, listen: false).selectedConversation = handle;
|
||||
Provider.of<ContactInfoState>(context, listen: false).hoveredIndex = -1;
|
||||
Provider.of<AppState>(context, listen: false).selectedConversation = handle;
|
||||
if (index != null) {
|
||||
Provider.of<AppState>(context, listen: false).initialScrollIndex = unread;
|
||||
}
|
||||
|
||||
// if in singlepane mode, push to the stack
|
||||
var isLandscape = Provider.of<AppState>(context, listen: false).isLandscape(context);
|
||||
if (Provider.of<Settings>(context, listen: false).uiColumns(isLandscape).length == 1) _pushMessageView(context, handle);
|
||||
|
@ -287,6 +306,10 @@ class _ContactsViewState extends State<ContactsView> {
|
|||
controller: ctrlrFilter,
|
||||
hintText: AppLocalizations.of(context)!.search,
|
||||
onChanged: (newVal) {
|
||||
String profileHandle = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SearchConversations(profileHandle, newVal).then((value) {
|
||||
Provider.of<ProfileInfoState>(context, listen: false).newSearch(value);
|
||||
});
|
||||
Provider.of<ContactListState>(context, listen: false).filter = newVal;
|
||||
},
|
||||
);
|
||||
|
@ -294,7 +317,7 @@ class _ContactsViewState extends State<ContactsView> {
|
|||
}
|
||||
|
||||
Widget _buildContactList() {
|
||||
final tiles = Provider.of<ContactListState>(context).filteredList().map((ContactInfoState contact) {
|
||||
var tilesSearchResult = Provider.of<ContactListState>(context).filteredList().map((ContactInfoState contact) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider.value(value: contact),
|
||||
|
@ -310,15 +333,28 @@ class _ContactsViewState extends State<ContactsView> {
|
|||
initialScroll = 0;
|
||||
}
|
||||
|
||||
if (showSearchBar) {
|
||||
List<SearchResult> searchResults = Provider.of<ProfileInfoState>(context).activeSearchResults;
|
||||
tilesSearchResult = searchResults.map((SearchResult searchResult) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context).contactList.getContact(searchResult.conversationIdentifier)),
|
||||
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context).serverList),
|
||||
],
|
||||
builder: (context, child) => ContactRow(messageIndex: searchResult.messageIndex),
|
||||
);
|
||||
});
|
||||
} else {}
|
||||
|
||||
var contactList = ScrollablePositionedList.separated(
|
||||
itemScrollController: Provider.of<ProfileInfoState>(context).contactListScrollController,
|
||||
itemCount: Provider.of<ContactListState>(context).numFiltered,
|
||||
itemCount: tilesSearchResult.length,
|
||||
initialScrollIndex: initialScroll,
|
||||
shrinkWrap: true,
|
||||
physics: BouncingScrollPhysics(),
|
||||
semanticChildCount: Provider.of<ContactListState>(context).numFiltered,
|
||||
semanticChildCount: tilesSearchResult.length,
|
||||
itemBuilder: (context, index) {
|
||||
return tiles.elementAt(index);
|
||||
return tilesSearchResult.elementAt(index);
|
||||
},
|
||||
separatorBuilder: (BuildContext context, int index) {
|
||||
return Divider(height: 1);
|
||||
|
|
|
@ -10,21 +10,34 @@ import 'package:cwtch/widgets/profileimage.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import '../main.dart';
|
||||
import '../models/message.dart';
|
||||
import '../settings.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class ContactRow extends StatefulWidget {
|
||||
int? messageIndex;
|
||||
|
||||
ContactRow({this.messageIndex});
|
||||
@override
|
||||
_ContactRowState createState() => _ContactRowState();
|
||||
}
|
||||
|
||||
class _ContactRowState extends State<ContactRow> {
|
||||
bool isHover = false;
|
||||
Message? cachedMessage = null;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var contact = Provider.of<ContactInfoState>(context);
|
||||
|
||||
if (widget.messageIndex != null && this.cachedMessage == null) {
|
||||
messageHandler(context, Provider.of<ProfileInfoState>(context, listen: false).onion, contact.identifier, ByIndex(widget.messageIndex!)).then((value) {
|
||||
setState(() {
|
||||
this.cachedMessage = value;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Only groups have a sync status
|
||||
Widget? syncStatus;
|
||||
if (contact.isGroup) {
|
||||
|
@ -37,11 +50,19 @@ class _ContactRowState extends State<ContactRow> {
|
|||
));
|
||||
}
|
||||
|
||||
bool selected = Provider.of<AppState>(context).selectedConversation == contact.identifier;
|
||||
if (selected && widget.messageIndex != null) {
|
||||
if (selected && widget.messageIndex == Provider.of<AppState>(context).selectedSearchMessage) {
|
||||
selected = true;
|
||||
} else {
|
||||
selected = false;
|
||||
}
|
||||
}
|
||||
return InkWell(
|
||||
enableFeedback: true,
|
||||
splashFactory: InkSplash.splashFactory,
|
||||
child: Ink(
|
||||
color: Provider.of<AppState>(context).selectedConversation == contact.identifier ? Provider.of<Settings>(context).theme.backgroundHilightElementColor : Colors.transparent,
|
||||
color: selected ? Provider.of<Settings>(context).theme.backgroundHilightElementColor : Colors.transparent,
|
||||
child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(6.0), //border size
|
||||
|
@ -85,6 +106,17 @@ class _ContactRowState extends State<ContactRow> {
|
|||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(color: contact.isBlocked ? Provider.of<Settings>(context).theme.portraitBlockedTextColor : Provider.of<Settings>(context).theme.mainTextColor)),
|
||||
),
|
||||
// we need to ignore the child widget in this context, otherwise gesture events will flow down...
|
||||
IgnorePointer(
|
||||
ignoring: true,
|
||||
child: Visibility(
|
||||
visible: this.cachedMessage != null,
|
||||
maintainSize: false,
|
||||
maintainInteractivity: false,
|
||||
maintainSemantics: false,
|
||||
maintainState: false,
|
||||
child: this.cachedMessage == null ? CircularProgressIndicator() : this.cachedMessage!.getPreviewWidget(context),
|
||||
)),
|
||||
Container(
|
||||
padding: EdgeInsets.all(0),
|
||||
child: contact.isInvitation == true
|
||||
|
@ -124,7 +156,7 @@ class _ContactRowState extends State<ContactRow> {
|
|||
icon: Icon(Icons.block, color: Provider.of<Settings>(context).theme.mainTextColor),
|
||||
onPressed: () {},
|
||||
)
|
||||
: Text(dateToNiceString(contact.lastMessageTime))),
|
||||
: Text(dateToNiceString(widget.messageIndex == null ? contact.lastMessageTime : (this.cachedMessage?.getMetadata().timestamp ?? DateTime.now())))),
|
||||
),
|
||||
],
|
||||
))),
|
||||
|
@ -149,7 +181,7 @@ class _ContactRowState extends State<ContactRow> {
|
|||
])),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
selectConversation(context, contact.identifier);
|
||||
selectConversation(context, contact.identifier, widget.messageIndex);
|
||||
});
|
||||
},
|
||||
onHover: (hover) {
|
||||
|
|
|
@ -342,7 +342,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
|
|||
if (id == null) {
|
||||
// Can't happen
|
||||
} else {
|
||||
selectConversation(context, id);
|
||||
selectConversation(context, id, null);
|
||||
var contactIndex = Provider.of<ProfileInfoState>(context, listen: false).contactList.filteredList().indexWhere((element) => element.identifier == id);
|
||||
Provider.of<ProfileInfoState>(context, listen: false).contactListScrollController.jumpTo(index: contactIndex);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue