Skip to content

Commit

Permalink
Jzecm wal local sync 958 (#1010)
Browse files Browse the repository at this point in the history
<!-- This is an auto-generated comment: release notes by OSS
Entelligence.AI -->
### Summary by Entelligence.AI

- New Feature: Introduced a "Local Sync" feature under the experimental
section, allowing users to manage local synchronization of their
memories.
- New Feature: Added a new UI widget, `LocalSyncWidget`, to display and
control the local sync status.
- Refactor: Updated the `SharedPreferencesUtil` with a new property
`localSyncEnabled` for tracking local sync status.
- Refactor: Modified the `SocketServicePool` class's `start()` method
for improved internet connection status monitoring.
- New Feature: Added a utility function `convertToHHMMSS` for converting
seconds into HH:MM:SS format.
<!-- end of auto-generated comment: release notes by OSS Entelligence.AI
-->
  • Loading branch information
beastoin authored Oct 9, 2024
2 parents 74cda7e + 2ae9037 commit 574d2b7
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 40 deletions.
10 changes: 8 additions & 2 deletions app/lib/backend/preferences.dart
Original file line number Diff line number Diff line change
Expand Up @@ -395,13 +395,19 @@ class SharedPreferencesUtil {

bool get locationPermissionRequested => getBool('locationPermissionRequested') ?? false;

// WAL

set wals(List<Wal> wals) {
final List<String> value = wals.map((e) => jsonEncode(e.toJson())).toList();
saveStringList('v3/wals', value);
saveStringList('wals', value);
}

List<Wal> get wals {
final List<String> value = getStringList('v3/wals') ?? [];
final List<String> value = getStringList('wals') ?? [];
return Wal.fromJsonList(value.map((e) => jsonDecode(e)).toList());
}

set localSyncEnabled(bool value) => saveBool('localSyncEnabled', value);

bool get localSyncEnabled => getBool('localSyncEnabled') ?? false;
}
30 changes: 3 additions & 27 deletions app/lib/pages/memories/page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:friend_private/backend/schema/memory.dart';
import 'package:friend_private/pages/capture/widgets/widgets.dart';
import 'package:friend_private/pages/memories/sync_page.dart';
import 'package:friend_private/pages/memories/widgets/date_list_item.dart';
import 'package:friend_private/pages/memories/widgets/local_sync.dart';
import 'package:friend_private/pages/memories/widgets/processing_capture.dart';
import 'package:friend_private/providers/memory_provider.dart';
import 'package:friend_private/utils/other/temp.dart';
Expand Down Expand Up @@ -67,7 +68,7 @@ class _MemoriesPageState extends State<MemoriesPage> with AutomaticKeepAliveClie

@override
Widget build(BuildContext context) {
print('building memories page');
debugPrint('building memories page');
super.build(context);
return Consumer<MemoryProvider>(builder: (context, memoryProvider, child) {
return RefreshIndicator(
Expand All @@ -78,34 +79,9 @@ class _MemoriesPageState extends State<MemoriesPage> with AutomaticKeepAliveClie
},
child: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: memoryProvider.missingWalsInSeconds > 120
? GestureDetector(
onTap: () {
routeToPage(context, const SyncPage());
},
child: Container(
width: double.maxFinite,
decoration: BoxDecoration(
color: Colors.grey.shade900,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
margin: const EdgeInsets.fromLTRB(16, 18, 16, 0),
padding: const EdgeInsets.all(16),
child: ListTile(
leading: const Icon(Icons.record_voice_over_rounded),
title: Text(
'You have ${secondsToHumanReadable(memoryProvider.missingWalsInSeconds)} of conversation locally, sync now?',
style: const TextStyle(color: Colors.white, fontSize: 16),
),
),
),
)
: const SizedBox.shrink(),
),
const SliverToBoxAdapter(child: SizedBox(height: 26)),
const SliverToBoxAdapter(child: SpeechProfileCardWidget()),
const SliverToBoxAdapter(child: UpdateFirmwareCardWidget()),
const SliverToBoxAdapter(child: LocalSyncWidget()),
const SliverToBoxAdapter(child: MemoryCaptureWidget()),
getProcessingMemoriesWidget(memoryProvider.processingMemories),
if (memoryProvider.groupedMemories.isEmpty && !memoryProvider.isLoadingMemories)
Expand Down
118 changes: 118 additions & 0 deletions app/lib/pages/memories/widgets/local_sync.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import 'dart:async';
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:friend_private/pages/memories/page.dart';
import 'package:friend_private/pages/memories/sync_page.dart';
import 'package:friend_private/providers/capture_provider.dart';
import 'package:friend_private/providers/developer_mode_provider.dart';
import 'package:friend_private/providers/memory_provider.dart';
import 'package:friend_private/utils/other/string_utils.dart';
import 'package:friend_private/utils/other/temp.dart';
import 'package:provider/provider.dart';

class LocalSyncWidget extends StatefulWidget {
const LocalSyncWidget({super.key});

@override
State<LocalSyncWidget> createState() => _LocalSyncWidgetState();
}

enum LocalSyncStatus {
disabled,
inProgress,
flush, // flushed to disk
}

class _LocalSyncWidgetState extends State<LocalSyncWidget> {
LocalSyncStatus? _status;
Timer? _missSecondsInEstTimer;
int _missSeconds = 0;

@override
void dispose() {
_missSecondsInEstTimer?.cancel();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Consumer2<MemoryProvider, CaptureProvider>(builder: (context, provider, captureProvider, child) {
if (provider.missingWalsInSeconds > 120) {
_status = LocalSyncStatus.flush;
_missSeconds = max(_missSeconds, provider.missingWalsInSeconds); // est. good for ux
} else if (!captureProvider.isWalSupported) {
_status = LocalSyncStatus.disabled;
_missSecondsInEstTimer?.cancel();
} else if ((!captureProvider.transcriptServiceReady && captureProvider.recordingDeviceServiceReady) ||
provider.missingWalsInSeconds > 0) {
var previousStatus = _status;
_status = LocalSyncStatus.inProgress;

// Change state to in progress
if (previousStatus != LocalSyncStatus.inProgress) {
_missSecondsInEstTimer?.cancel();
_missSeconds = provider.missingWalsInSeconds;
_missSecondsInEstTimer = Timer.periodic(const Duration(seconds: 1), (t) {
setState(() {
_missSeconds++;
});
});
}
}

// in progress
if (_status == LocalSyncStatus.inProgress) {
return Container(
decoration: BoxDecoration(
color: Colors.grey.shade900,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
margin: const EdgeInsets.fromLTRB(16, 16, 16, 16),
padding: const EdgeInsets.all(16),
child: Text(
'${convertToHHMMSS(_missSeconds)} of conversation locally',
style: const TextStyle(color: Colors.white, fontSize: 16),
textAlign: TextAlign.center,
),
);
}

// ready to sync
if (_status == LocalSyncStatus.flush) {
return GestureDetector(
onTap: () {
routeToPage(context, const SyncPage());
},
child: Container(
decoration: BoxDecoration(
color: Colors.grey.shade900,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
margin: const EdgeInsets.fromLTRB(16, 16, 16, 16),
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Row(
children: [
const Icon(Icons.download_rounded),
const SizedBox(width: 16),
Text(
'${secondsToHumanReadable(_missSeconds)} available. Sync now?',
style: const TextStyle(color: Colors.white, fontSize: 16),
),
],
),
),
],
),
),
);
}

return const SizedBox.shrink();
});
}
}
22 changes: 22 additions & 0 deletions app/lib/pages/settings/developer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,28 @@ class __DeveloperSettingsPageState extends State<_DeveloperSettingsPage> {
decoration: _getTextFieldDecoration('Endpoint URL'),
style: const TextStyle(color: Colors.white),
),
const SizedBox(height: 16),
Divider(color: Colors.grey.shade500),
const SizedBox(height: 32),
const Text(
'Experimental',
style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.w600),
),
const SizedBox(height: 8),
Text(
'Try the latest experimental features from Omi Team.',
style: TextStyle(color: Colors.grey.shade200, fontSize: 14),
),
const SizedBox(height: 16.0),
CheckboxListTile(
contentPadding: const EdgeInsets.all(0),
title: const Text(
'Local Sync',
style: TextStyle(color: Colors.white, fontSize: 16),
),
value: provider.localSyncEnabled,
onChanged: provider.onLocalSyncEnabledChanged,
),
const SizedBox(height: 64),
],
),
Expand Down
42 changes: 35 additions & 7 deletions app/lib/providers/capture_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import 'package:friend_private/providers/message_provider.dart';
import 'package:friend_private/services/devices.dart';
import 'package:friend_private/services/notifications.dart';
import 'package:friend_private/services/services.dart';
import 'package:friend_private/services/sockets/pure_socket.dart';
import 'package:friend_private/services/sockets/sdcard_socket.dart';
import 'package:friend_private/services/sockets/transcription_connection.dart';
import 'package:friend_private/services/wals.dart';
Expand All @@ -25,6 +26,7 @@ import 'package:friend_private/utils/enums.dart';
import 'package:friend_private/utils/logger.dart';
import 'package:friend_private/utils/memories/integrations.dart';
import 'package:friend_private/utils/memories/process.dart';
import 'package:internet_connection_checker_plus/internet_connection_checker_plus.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:uuid/uuid.dart';

Expand All @@ -42,9 +44,20 @@ class CaptureProvider extends ChangeNotifier

ServerMemory? get inProgressMemory => _inProgressMemory;

bool _walFeatureEnabled = false;
IWalService get _walService => ServiceManager.instance().wal;
IDeviceService get _deviceService => ServiceManager.instance().device;
bool _isWalSupported = false;
bool get isWalSupported => _isWalSupported;

StreamSubscription<InternetStatus>? _internetStatusListener;
InternetStatus? _internetStatus;
get internetStatus => _internetStatus;

CaptureProvider() {
_internetStatusListener = PureCore().internetConnection.onStatusChange.listen((InternetStatus status) {
onInternetSatusChanged(status);
});
}

void updateProviderInstances(MemoryProvider? mp, MessageProvider? p) {
memoryProvider = mp;
Expand Down Expand Up @@ -72,7 +85,7 @@ class CaptureProvider extends ChangeNotifier

bool _transcriptServiceReady = false;

bool get transcriptServiceReady => _transcriptServiceReady;
bool get transcriptServiceReady => _transcriptServiceReady && _internetStatus == InternetStatus.connected;

bool get recordingDeviceServiceReady => _recordingDevice != null || recordingState == RecordingState.record;

Expand Down Expand Up @@ -160,11 +173,14 @@ class CaptureProvider extends ChangeNotifier

// support: opus codec, 1m from the first device connectes
var deviceFirstConnectedAt = _deviceService.getFirstConnectedAt();
var isWalEnabled = codec == BleAudioCodec.opus &&
var checkWalSupported = codec == BleAudioCodec.opus &&
(deviceFirstConnectedAt != null &&
deviceFirstConnectedAt.isBefore(DateTime.now().subtract(const Duration(seconds: 60)))) &&
_walFeatureEnabled;
if (isWalEnabled) {
deviceFirstConnectedAt.isBefore(DateTime.now().subtract(const Duration(seconds: 15)))) &&
SharedPreferencesUtil().localSyncEnabled;
if (checkWalSupported != _isWalSupported) {
setIsWalSupported(checkWalSupported);
}
if (_isWalSupported) {
_walService.onByteStream(value);
}

Expand All @@ -174,7 +190,7 @@ class CaptureProvider extends ChangeNotifier
_socket?.send(trimmedValue);

// synced
if (isWalEnabled) {
if (_isWalSupported) {
_walService.onBytesSync(value);
}
}
Expand Down Expand Up @@ -295,6 +311,7 @@ class CaptureProvider extends ChangeNotifier
_bleBytesStream?.cancel();
_socket?.unsubscribe(this);
_keepAliveTimer?.cancel();
_internetStatusListener?.cancel();
super.dispose();
}

Expand Down Expand Up @@ -468,6 +485,17 @@ class CaptureProvider extends ChangeNotifier
notifyListeners();
}

void onInternetSatusChanged(InternetStatus status) {
debugPrint("[SocketService] Internet connection changed $status");
_internetStatus = status;
notifyListeners();
}

void setIsWalSupported(bool value) {
_isWalSupported = value;
notifyListeners();
}

/*
*
*
Expand Down
14 changes: 14 additions & 0 deletions app/lib/providers/developer_mode_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,24 @@ class DeveloperModeProvider extends BaseProvider {
bool loadingExportMemories = false;
bool loadingImportMemories = false;

bool localSyncEnabled = false;

void initialize() {
gcpCredentialsController.text = SharedPreferencesUtil().gcpCredentials;
gcpBucketNameController.text = SharedPreferencesUtil().gcpBucketName;
webhookOnMemoryCreated.text = SharedPreferencesUtil().webhookOnMemoryCreated;
webhookOnTranscriptReceived.text = SharedPreferencesUtil().webhookOnTranscriptReceived;
localSyncEnabled = SharedPreferencesUtil().localSyncEnabled;

notifyListeners();
}

void saveSettings() async {
if (savingSettingsLoading) return;
savingSettingsLoading = true;
notifyListeners();
final prefs = SharedPreferencesUtil();

if (gcpCredentialsController.text.isNotEmpty && gcpBucketNameController.text.isNotEmpty) {
try {
await authenticateGCP(base64: gcpCredentialsController.text.trim());
Expand All @@ -50,6 +56,9 @@ class DeveloperModeProvider extends BaseProvider {
prefs.webhookOnMemoryCreated = webhookOnMemoryCreated.text.trim();
prefs.webhookOnTranscriptReceived = webhookOnTranscriptReceived.text.trim();

// Experimental
prefs.localSyncEnabled = localSyncEnabled;

MixpanelManager().settingsSaved(
hasGCPCredentials: prefs.gcpCredentials.isNotEmpty,
hasGCPBucketName: prefs.gcpBucketName.isNotEmpty,
Expand All @@ -62,4 +71,9 @@ class DeveloperModeProvider extends BaseProvider {
'Settings saved!',
);
}

void onLocalSyncEnabledChanged(var value) {
localSyncEnabled = value;
notifyListeners();
}
}
4 changes: 1 addition & 3 deletions app/lib/services/sockets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ class SocketServicePool extends ISocketService {
TranscriptSegmentSocketService? _socket;

@override
void start() {
// TODO: implement start
}
void start() {}

@override
void stop() async {
Expand Down
13 changes: 12 additions & 1 deletion app/lib/utils/other/string_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,15 @@ String extractJson(String input) {
return input.substring(startIndex, endIndex + 1);
}
return '';
}
}

String convertToHHMMSS(int seconds) {
int hours = seconds ~/ 3600;
int minutes = (seconds % 3600) ~/ 60;
int remainingSeconds = seconds % 60;

String twoDigits(int n) => n.toString().padLeft(2, '0');

return '${twoDigits(hours)}:${twoDigits(minutes)}:${twoDigits(remainingSeconds)}';
}

0 comments on commit 574d2b7

Please sign in to comment.