Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WAL Sync UI #993

Merged
merged 4 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/lib/backend/http/api/memories.dart
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ Future<List<ServerMemory>> sendStorageToBackend(File file, String sdCardDateTime
}
}

Future<bool> syncLocalFiles(List<File> files) async {
Future<(Map<String, dynamic>?, bool)> syncLocalFiles(List<File> files) async {
var request = http.MultipartRequest(
'POST',
Uri.parse('${Env.apiBaseUrl}v1/sync-local-files'),
Comment on lines +255 to 258
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

The return type of syncLocalFiles has been changed from Future<bool> to Future<(Map<String, dynamic>?, bool)>. This change is significant as it affects the external interface of this function. Any code that calls this function will need to be updated to handle the new return type. Ensure that all call sites have been updated accordingly.

- Future<bool> syncLocalFiles(List<File> files) async {
+ Future<(Map<String, dynamic>?, bool)> syncLocalFiles(List<File> files) async {

Expand All @@ -268,7 +268,7 @@ Future<bool> syncLocalFiles(List<File> files) async {

if (response.statusCode == 200) {
debugPrint('syncLocalFile Response body: ${jsonDecode(response.body)}');
return true;
return (jsonDecode(response.body) as Map<String, dynamic>, true);
} else {
debugPrint('Failed to upload sample. Status code: ${response.statusCode}');
throw Exception('Failed to upload sample. Status code: ${response.statusCode}');
Comment on lines +271 to 274
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

The return value of the syncLocalFiles function has been changed from a boolean to a tuple containing a Map<String, dynamic>? and a boolean. This change is significant as it affects the external interface of this function. Any code that calls this function will need to be updated to handle the new return type. Ensure that all call sites have been updated accordingly.

- return true;
+ return (jsonDecode(response.body) as Map<String, dynamic>, true);

Expand Down
72 changes: 56 additions & 16 deletions app/lib/pages/memories/page.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,47 @@
import 'package:flutter/material.dart';
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/processing_capture.dart';
import 'package:friend_private/providers/memory_provider.dart';
import 'package:friend_private/utils/other/temp.dart';
import 'package:provider/provider.dart';
import 'package:visibility_detector/visibility_detector.dart';

import 'widgets/empty_memories.dart';
import 'widgets/memory_list_item.dart';

String secondsToHumanReadable(int seconds) {
if (seconds < 60) {
return '$seconds secs';
} else if (seconds < 3600) {
var minutes = (seconds / 60).floor();
var remainingSeconds = seconds % 60;
if (remainingSeconds == 0) {
return '$minutes mins';
} else {
return '$minutes mins $remainingSeconds secs';
}
} else if (seconds < 86400) {
var hours = (seconds / 3600).floor();
var remainingMinutes = (seconds % 3600 / 60).floor();
if (remainingMinutes == 0) {
return '$hours hours';
} else {
return '$hours hours $remainingMinutes mins';
}
} else {
var days = (seconds / 86400).floor();
var remainingHours = (seconds % 86400 / 3600).floor();
if (remainingHours == 0) {
return '$days days';
} else {
return '$days days $remainingHours hours';
}
}
}
Comment on lines +15 to +43
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

The secondsToHumanReadable function is a utility function that converts seconds into a human-readable format. This function could be moved to a separate utility file for better modularity and reusability. It doesn't directly relate to the MemoriesPage widget and could potentially be used elsewhere in your application.

- String secondsToHumanReadable(int seconds) {
-   if (seconds < 60) {
-     return '$seconds secs';
-   } else if (seconds < 3600) {
-     var minutes = (seconds / 60).floor();
-     var remainingSeconds = seconds % 60;
-     if (remainingSeconds == 0) {
-       return '$minutes mins';
-     } else {
-       return '$minutes mins $remainingSeconds secs';
-     }
-   } else if (seconds < 86400) {
-     var hours = (seconds / 3600).floor();
-     var remainingMinutes = (seconds % 3600 / 60).floor();
-     if (remainingMinutes == 0) {
-       return '$hours hours';
-     } else {
-       return '$hours hours $remainingMinutes mins';
-     }
-   } else {
-     var days = (seconds / 86400).floor();
-     var remainingHours = (seconds % 86400 / 3600).floor();
-     if (remainingHours == 0) {
-       return '$days days';
-     } else {
-       return '$days days $remainingHours hours';
-     }
-   }
- }


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

Expand Down Expand Up @@ -46,25 +78,32 @@ class _MemoriesPageState extends State<MemoriesPage> with AutomaticKeepAliveClie
},
child: CustomScrollView(
slivers: [
// TODO: FIXME
SliverToBoxAdapter(
child: memoryProvider.missingWals.isNotEmpty
? Padding(
padding: const EdgeInsets.all(32.0),
child: Center(
child: GestureDetector(
onTap: () {
memoryProvider.syncWals();
},
child: memoryProvider.walsSyncedProgress == 0.0
? Text(
"You have ${memoryProvider.missingWals.map((val) => val.seconds).reduce((a, b) => a + b)}s stereo localy, sync now?")
: Text("${(memoryProvider.walsSyncedProgress * 100).toInt()}%"),
child: memoryProvider.missingWals.isNotEmpty
? 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.missingWals.map((val) => val.seconds).reduce((a, b) => a + b))} of conversation locally, sync now?',
style: const TextStyle(color: Colors.white, fontSize: 16),
),
),
)
: SizedBox.shrink()),
const SliverToBoxAdapter(child: SizedBox(height: 32)),
),
)
: const SizedBox.shrink(),
),
const SliverToBoxAdapter(child: SizedBox(height: 26)),
const SliverToBoxAdapter(child: SpeechProfileCardWidget()),
const SliverToBoxAdapter(child: UpdateFirmwareCardWidget()),
const SliverToBoxAdapter(child: MemoryCaptureWidget()),
Expand Down Expand Up @@ -95,6 +134,7 @@ class _MemoriesPageState extends State<MemoriesPage> with AutomaticKeepAliveClie
childCount: memoryProvider.groupedMemories.length + 1,
(context, index) {
if (index == memoryProvider.groupedMemories.length) {
print('loading more memories');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

Avoid using print statements in production code. If you need to log information, consider using a logging package that can provide different levels of logging (info, warning, error, etc.) and can be toggled on or off in different environments (development, production).

- print('loading more memories');
+ // Use a logging package for logging
+ log.info('loading more memories');

if (memoryProvider.isLoadingMemories) {
return const Center(
child: Padding(
Expand Down
142 changes: 142 additions & 0 deletions app/lib/pages/memories/sync_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import 'package:flutter/material.dart';
import 'package:friend_private/pages/memories/widgets/sync_animation.dart';
import 'package:friend_private/providers/memory_provider.dart';
import 'package:friend_private/utils/other/temp.dart';
import 'package:gradient_borders/box_borders/gradient_box_border.dart';
import 'package:provider/provider.dart';

import 'synced_memories_page.dart';

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

@override
State<SyncPage> createState() => _SyncPageState();
}

class _SyncPageState extends State<SyncPage> {
bool _isAnimating = false;

void _toggleAnimation() {
setState(() {
_isAnimating = !_isAnimating;
});
}

@override
Widget build(BuildContext context) {
return PopScope(
onPopInvoked: (didPop) {
Provider.of<MemoryProvider>(context, listen: false).clearSyncResult();
},
Comment on lines +29 to +31
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

The PopScope widget is not a standard Flutter widget. It seems like a custom widget, but without the implementation details, it's hard to review its usage here. Please ensure that it correctly handles the pop events and invokes the clearSyncResult() method as expected.

child: Scaffold(
appBar: AppBar(
title: const Text('Sync Memories'),
backgroundColor: Theme.of(context).colorScheme.primary,
),
backgroundColor: Theme.of(context).colorScheme.primary,
body: Consumer<MemoryProvider>(
builder: (context, memoryProvider, child) {
return Center(
child: Column(
children: [
const SizedBox(height: 80),
child!,
const SizedBox(height: 80),
memoryProvider.isSyncing || memoryProvider.syncCompleted
? const SizedBox()
: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 0),
decoration: BoxDecoration(
border: const GradientBoxBorder(
gradient: LinearGradient(colors: [
Color.fromARGB(127, 208, 208, 208),
Color.fromARGB(127, 188, 99, 121),
Color.fromARGB(127, 86, 101, 182),
Color.fromARGB(127, 126, 190, 236)
]),
width: 2,
),
borderRadius: BorderRadius.circular(12),
),
child: TextButton(
onPressed: () async {
_toggleAnimation();
await memoryProvider.syncWals();
_toggleAnimation();
},
Comment on lines +64 to +67
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

The _toggleAnimation function is called before and after the syncWals function. If an error occurs during the execution of syncWals, the animation will continue indefinitely because _toggleAnimation won't be called the second time. Consider using a try-finally block to ensure that the animation is stopped even if an error occurs.

- _toggleAnimation();
- await memoryProvider.syncWals();
- _toggleAnimation();
+ _toggleAnimation();
+ try {
+   await memoryProvider.syncWals();
+ } finally {
+   _toggleAnimation();
+ }

child: const Text(
'Sync Now',
style: TextStyle(color: Colors.white, fontSize: 16),
),
),
),
const SizedBox(
height: 10,
),
memoryProvider.isSyncing
? Text(
'${memoryProvider.walsSyncedProgress}% synced',
style: const TextStyle(color: Colors.white, fontSize: 16),
)
: const SizedBox.shrink(),
const SizedBox(
height: 10,
),
memoryProvider.syncCompleted && memoryProvider.syncResult != null
? Column(
children: [
const Text(
'Memories Synced Successfully 🎉',
style: TextStyle(color: Colors.white, fontSize: 16),
),
const SizedBox(
height: 18,
),
(memoryProvider.syncResult!['new_memories'].isNotEmpty ||
memoryProvider.syncResult!['updated_memories'].isNotEmpty)
? Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 0),
decoration: BoxDecoration(
border: const GradientBoxBorder(
gradient: LinearGradient(colors: [
Color.fromARGB(127, 208, 208, 208),
Color.fromARGB(127, 188, 99, 121),
Color.fromARGB(127, 86, 101, 182),
Color.fromARGB(127, 126, 190, 236)
]),
width: 2,
),
borderRadius: BorderRadius.circular(12),
),
child: TextButton(
onPressed: () {
routeToPage(context, const SyncedMemoriesPage());
},
child: const Text(
'View Synced Memories',
style: TextStyle(color: Colors.white, fontSize: 16),
),
),
)
: const SizedBox.shrink(),
Comment on lines +96 to +122
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image description Entelligence.AI

There's no null safety check for memoryProvider.syncResult!['new_memories'] and memoryProvider.syncResult!['updated_memories']. This could lead to a runtime error if these keys are not present in the map. Consider adding null safety checks or default values for these keys.

- (memoryProvider.syncResult!['new_memories'].isNotEmpty ||
- memoryProvider.syncResult!['updated_memories'].isNotEmpty)
+ (memoryProvider.syncResult!['new_memories']?.isNotEmpty ?? false ||
+ memoryProvider.syncResult!['updated_memories']?.isNotEmpty ?? false)

],
)
: const SizedBox.shrink(),
],
),
);
},
child: RepaintBoundary(
child: SyncAnimation(
isAnimating: _isAnimating,
onStart: () {},
onStop: () {},
dotsPerRing: 12,
),
),
),
),
);
}
}
60 changes: 60 additions & 0 deletions app/lib/pages/memories/synced_memories_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:friend_private/providers/memory_provider.dart';
import 'package:provider/provider.dart';

class SyncedMemoriesPage extends StatelessWidget {
final Map<String, dynamic>? res;
const SyncedMemoriesPage({super.key, this.res});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Synced Memories'),
backgroundColor: Theme.of(context).colorScheme.primary,
),
backgroundColor: Theme.of(context).colorScheme.primary,
body: Consumer<MemoryProvider>(
builder: (context, memoryProvider, child) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
"Updated Memories",
style: TextStyle(color: Colors.white),
),
ListView.separated(
shrinkWrap: true,
itemBuilder: (ctx, i) {
return Container();
},
separatorBuilder: (ctx, i) {
return const SizedBox(
height: 10,
);
},
itemCount: memoryProvider.syncResult!['updated_memories'].length,
),
const Text(
"New Memories",
style: TextStyle(color: Colors.white),
),
ListView.separated(
shrinkWrap: true,
itemBuilder: (ctx, i) {
return Container();
},
separatorBuilder: (ctx, i) {
return const SizedBox(
height: 10,
);
},
itemCount: memoryProvider.syncResult!['new_memories'].length,
),
],
);
},
),
);
}
}
Loading
Loading