Fix Crash Bug in Android (ShareFile and Reconnect) + Force prettyDateString to use .toLocal() time + Formatting #794

Merged
sarah merged 9 commits from post-stable-fixes into trunk 2024-01-03 22:10:09 +00:00
18 changed files with 101 additions and 35 deletions

View File

@ -1 +1 @@
2023-09-26-13-15-v0.0.10
2024-01-03-20-52-v0.0.10-4-g6c0b2e2

View File

@ -331,8 +331,9 @@ class MainActivity: FlutterActivity() {
val conversation: Int = call.argument("conversation") ?: 0
val indexI: Int = call.argument("index") ?: 0
val count: Int = call.argument("count") ?: 1
val ucount : Int = maxOf(1, count) // don't allow negative counts
result.success(Cwtch.getMessages(profile, conversation.toLong(), indexI.toLong(), count.toLong()))
result.success(Cwtch.getMessages(profile, conversation.toLong(), indexI.toLong(), ucount.toLong()))
return
}
"SendMessage" -> {

View File

@ -156,4 +156,6 @@ abstract class Cwtch {
void DeleteServerInfo(String profile, String handle);
void PublishServerUpdate(String onion);
Future<void> ConfigureConnections(String onion, bool listen, bool peers, bool servers);
bool IsLoaded();
}

View File

@ -268,6 +268,8 @@ class CwtchFfi implements Cwtch {
// Called on object being disposed to (presumably on app close) to close the isolate that's listening to libcwtch-go events
@override
void dispose() {
EnvironmentConfig.debugLog("tearing down cwtch FFI isolate");
library.close();
cwtchIsolate.kill(priority: Isolate.immediate);
}
@ -1121,4 +1123,11 @@ class CwtchFfi implements Cwtch {
PublishServerUpdate(utf8profile, utf8profile.length);
malloc.free(utf8profile);
}
@override
bool IsLoaded() {
bool check = library.providesSymbol("c_UpdateSettings");
EnvironmentConfig.debugLog("Checking that the FFI Interface is Correctly Loaded... $check");
return check;
}
}

View File

@ -469,4 +469,9 @@ class CwtchGomobile implements Cwtch {
void PublishServerUpdate(String profile) {
cwtchPlatform.invokeMethod("PublishServerUpdate", {"ProfileOnion": profile});
}
@override
bool IsLoaded() {
return true;
}
}

View File

@ -173,6 +173,10 @@ class FlwtchState extends State<Flwtch> with WindowListener {
getServerListStateProvider(),
],
builder: (context, widget) {
// in test mode...rebuild everything every second...if cwtch isn't loaded...
if (EnvironmentConfig.TEST_MODE && cwtch.IsLoaded() == false) {
Timer t = new Timer.periodic(Duration(seconds: 1), (Timer t) => setState(() {}));
}
return Consumer2<Settings, AppState>(
builder: (context, settings, appState, child) => MaterialApp(
key: Key('app'),
@ -190,7 +194,7 @@ class FlwtchState extends State<Flwtch> with WindowListener {
title: 'Cwtch',
showSemanticsDebugger: settings.useSemanticDebugger,
theme: mkThemeData(settings),
home: (!appState.cwtchInit || appState.modalState != ModalState.none) ? SplashView() : ProfileMgrView(),
home: (!appState.cwtchInit || appState.modalState != ModalState.none) || !cwtch.IsLoaded() ? SplashView() : ProfileMgrView(),
),
);
},

View File

@ -75,6 +75,8 @@ class ContactInfoState extends ChangeNotifier {
DateTime _lastRetryTime = DateTime.now();
DateTime loaded = DateTime.now();
List<ContactEvent> contactEvents = List.empty(growable: true);
ContactInfoState(
this.profileOnion,
this.identifier,
@ -198,6 +200,7 @@ class ContactInfoState extends ChangeNotifier {
set status(String newVal) {
this._status = newVal;
this.contactEvents.add(ContactEvent("Update Peer Status Received: $newVal"));
notifyListeners();
}
@ -486,3 +489,11 @@ class ContactInfoState extends ChangeNotifier {
}
}
}
class ContactEvent {
String summary;
late DateTime timestamp;
ContactEvent(this.summary) {
this.timestamp = DateTime.now();
}
}

View File

@ -98,7 +98,7 @@ class ContactListState extends ChangeNotifier {
notifyListeners();
//} </todo>
}
void updateLastMessageReceivedTime(int forIdentifier, DateTime newMessageTime) {
var contact = getContact(forIdentifier);
if (contact == null) return;

View File

@ -30,22 +30,27 @@ class LocalIndexMessage {
this.messageId = messageId;
this.cacheOnly = cacheOnly;
this.isLoading = isLoading;
if (isLoading) {
loader = Completer<void>();
loaded = loader.future;
loader = Completer<void>();
loaded = loader.future;
if (!isLoading) {
loader.complete(); // complete this
}
}
void finishLoad(int messageId) {
this.messageId = messageId;
isLoading = false;
loader.complete(true);
if (!loader.isCompleted) {
isLoading = false;
loader.complete(true);
}
}
void failLoad() {
this.messageId = null;
isLoading = false;
loader.complete(true);
if (!loader.isCompleted) {
isLoading = false;
loader.complete(true);
}
}
Future<void> waitForLoad() {
@ -95,7 +100,7 @@ class MessageCache extends ChangeNotifier {
this._storageMessageCount = newval;
}
// On android reconnect, if backend supplied message count > UI message count, add the differnce to the front of the index
// On android reconnect, if backend supplied message count > UI message count, add the difference to the front of the index
void addFrontIndexGap(int count) {
this._indexUnsynced = count;
}

View File

@ -1,4 +1,5 @@
import 'dart:convert';
import 'dart:math';
import 'package:cwtch/config.dart';
import 'package:cwtch/models/remoteserver.dart';
@ -248,8 +249,22 @@ class ProfileInfoState extends ChangeNotifier {
if (profileContact != null) {
profileContact.status = contact["status"];
var newCount = contact["numMessages"];
var newCount = contact["numMessages"] as int;
if (newCount != profileContact.totalMessages) {
if (newCount < profileContact.totalMessages) {
// on Android, when sharing a file the UI may be briefly unloaded for the
// OS to display the file management/selection screen. Afterwards a
// call to ReconnectCwtchForeground will be made which will refresh all values (including count of numMessages)
// **at the same time** the foreground will increment .totalMessages and send a new message to the backend.
// This will result in a negative number of messages being calculated here, and an incorrect totalMessage count.
// This bug is exacerbated in debug mode, and when multiple files are sent in succession. Both cases result in multiple ReconnectCwtchForeground
// events that have the potential to conflict with currentMessageCounts.
// Note that *if* a new message came in at the same time, we would be unable to distinguish this case - as such this is specific instance of a more general problem
// TODO: A true-fix to this bug is to implement a syncing step in the foreground where totalMessages and inFlightMessages can be distinguished
// This requires a change to the backend to confirm submission of an inFlightMessage, which will be implemented in #664
EnvironmentConfig.debugLog("Conflicting message counts: $newCount ${profileContact.totalMessages}");
newCount = max(newCount, profileContact.totalMessages);
}
profileContact.messageCache.addFrontIndexGap(newCount - profileContact.totalMessages);
}
profileContact.totalMessages = newCount;

View File

@ -39,5 +39,5 @@ String prettyDateString(BuildContext context, DateTime date) {
// }
return AppLocalizations.of(context)!.now;
}
return DateFormat.yMd(Platform.localeName).add_jm().format(date);
return DateFormat.yMd(Platform.localeName).add_jm().format(date.toLocal());
}

View File

@ -24,7 +24,7 @@ LoadAssetThemes() async {
themes = await loadYamlThemes();
}
OpaqueThemeType getTheme(String themeId, String mode) {
OpaqueThemeType getTheme(String themeId, String mode) {
if (themeId == "") {
themeId = cwtch_theme;
}

View File

@ -8,8 +8,6 @@ import 'package:flutter/services.dart';
import 'package:yaml/yaml.dart';
import 'package:path/path.dart' as path;
Future<Map<String, Map<String, OpaqueThemeType>>> loadYamlThemes() async {
final manifestJson = await rootBundle.loadString('AssetManifest.json');
final themesList = json.decode(manifestJson).keys.where((String key) => key.startsWith('assets/themes'));
@ -17,7 +15,7 @@ Future<Map<String, Map<String, OpaqueThemeType>>> loadYamlThemes() async {
Map<String, Map<String, OpaqueThemeType>> themes = Map();
for (String themefile in themesList) {
if (themefile.substring(themefile.length-4) != ".yml") {
if (themefile.substring(themefile.length - 4) != ".yml") {
continue;
}
@ -77,16 +75,16 @@ class YmlTheme extends OpaqueThemeType {
Color? getColor(String name) {
var val = yml["themes"][mode]["theme"][name];
if (! (val is int)) {
if (!(val is int)) {
val = yml["themes"][mode]["theme"][val] ?? val;
}
if (! (val is int)) {
if (!(val is int)) {
val = yml["themes"][mode]?["colors"][val] ?? val;
}
if (! (val is int)) {
if (!(val is int)) {
val = yml["colors"]?[val];
}
if (! (val is int)) {
if (!(val is int)) {
return null;
}
return Color(0xFF000000 + val as int);
@ -95,7 +93,7 @@ class YmlTheme extends OpaqueThemeType {
String? getImage(String name) {
var val = yml["themes"][mode]["theme"]?[name];
if (val != null) {
return path.join("assets", "themes", yml["themes"]["name"], val);
return path.join("assets", "themes", yml["themes"]["name"], val);
}
return null;
}
@ -137,4 +135,4 @@ class YmlTheme extends OpaqueThemeType {
// Images
get chatImage => getImage("chatImage") ?? fallbackTheme.chatImage;
}
}

View File

@ -83,7 +83,7 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
}
Widget _buildSettingsList() {
return Consumer<Settings>(builder: (context, settings, child) {
return Consumer<Settings>(builder: (ccontext, settings, child) {
return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
var appIcon = Icon(Icons.info, color: settings.current().mainTextColor);
return Scrollbar(

View File

@ -125,6 +125,7 @@ class _MessageViewState extends State<MessageView> {
}
// reset the disconnect button to allow for immediate connection...
Provider.of<ContactInfoState>(context, listen: false).lastRetryTime = DateTime.now().subtract(Duration(minutes: 2));
Provider.of<ContactInfoState>(context, listen: false).contactEvents.add(ContactEvent("Disconnect from Peer"));
}));
}

View File

@ -1,5 +1,6 @@
import 'dart:convert';
import 'dart:ui';
import 'package:cwtch/config.dart';
import 'package:cwtch/cwtch_icons_icons.dart';
import 'package:cwtch/models/appstate.dart';
import 'package:cwtch/models/contact.dart';
@ -84,6 +85,13 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
}
}
List<Widget> evWidgets = Provider.of<ContactInfoState>(context, listen: false).contactEvents.map((ev) {
return ListTile(
title: Text(ev.summary, style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle)),
subtitle: Text(ev.timestamp.toLocal().toIso8601String(), style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle)),
);
}).toList();
return Scrollbar(
trackVisibility: true,
controller: peerSettingsScrollController,
@ -319,8 +327,14 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
style: settings.scaleFonts(defaultTextButtonStyle.copyWith(decoration: TextDecoration.underline)),
),
))
])
])
]),
]),
Visibility(
visible: EnvironmentConfig.BUILD_VER == dev_version,
maintainSize: false,
child: Column(
children: evWidgets,
))
])))));
});
});

View File

@ -75,6 +75,7 @@ class _MessageListState extends State<MessageList> {
.AttemptReconnection(Provider.of<ProfileInfoState>(context, listen: false).onion, Provider.of<ContactInfoState>(context, listen: false).onion);
}
Provider.of<ContactInfoState>(context, listen: false).lastRetryTime = DateTime.now();
Provider.of<ContactInfoState>(context, listen: false).contactEvents.add(ContactEvent("Actively Retried Connection"));
setState(() {
// force update of this view...otherwise the button won't be removed fast enough...
});
@ -116,8 +117,8 @@ class _MessageListState extends State<MessageList> {
// Only show broken heart is the contact is offline...
decoration: BoxDecoration(
image: Provider.of<ContactInfoState>(outerContext).isOnline()
? (Provider.of<Settings>(context).theme.chatImage != null) ?
DecorationImage(
? (Provider.of<Settings>(context).theme.chatImage != null)
? DecorationImage(
repeat: ImageRepeat.repeat,
image: AssetImage(Provider.of<Settings>(context).theme.chatImage),
colorFilter: ColorFilter.mode(Provider.of<Settings>(context).theme.hilightElementColor.withOpacity(0.15), BlendMode.srcIn))

View File

@ -5,11 +5,11 @@ sed "s|featurePaths: REPLACED_BY_SCRIPT|featurePaths: <String>[$paths]|" integra
flutter pub run build_runner clean
flutter pub run build_runner build --delete-conflicting-outputs
PATH=$PATH:$PWD/linux/Tor
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"$PWD/linux/":"$PWD/linux/Tor/"
PATH=$PATH LD_LIBRARY_PATH=$LD_LIBRARY_PATH LOG_FILE=test.log CWTCH_HOME=$PWD/integration_test/env/temp/ flutter test -d linux --dart-define TEST_MODE=true integration_test/gherkin_suite_test.dart
#node index2.js
#if [ "$HEADLESS" = "false" ]; then
PATH=$PATH:"$PWD/linux/Tor"
LD_LIBRARY_PATH="$PWD/linux/":"$PWD/linux/Tor/":$LD_LIBRARY_PATH
env PATH=$PATH LD_LIBRARY_PATH=$LD_LIBRARY_PATH LOG_FILE=test.log CWTCH_HOME=$PWD/integration_test/env/temp/ flutter test -d linux --dart-define TEST_MODE=true integration_test/gherkin_suite_test.dart
# node index2.js
# if [ "$HEADLESS" = "false" ]; then
# xdg-open integration_test/gherkin/reports/cucumber_report.html
#fi
# fi