added annotations; improvements & bugfixes

This commit is contained in:
Myzel394 2022-08-20 22:12:10 +02:00
parent 9f37648762
commit 5a8a0352c1
16 changed files with 349 additions and 124 deletions

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:quid_faciam_hodie/constants/values.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
@ -9,12 +10,17 @@ extension ShowSnackBar on BuildContext {
static ScaffoldFeatureController<SnackBar, SnackBarClosedReason>?
pendingSnackBar;
ScaffoldFeatureController<SnackBar, SnackBarClosedReason> showSnackBar({
ScaffoldFeatureController<SnackBar, SnackBarClosedReason>? showSnackBar({
required final String message,
final Color backgroundColor = Colors.white,
final Duration duration = const Duration(seconds: 4),
final BuildContext? context,
}) {
if (!isMaterial(context ?? this)) {
// Not implemented yet
return null;
}
pendingSnackBar?.close();
pendingSnackBar = null;

View File

@ -12,6 +12,7 @@ class Memory {
final String filePath;
final bool isPublic;
final String userID;
final String annotation;
final MemoryLocation? location;
const Memory({
@ -20,6 +21,7 @@ class Memory {
required this.filePath,
required this.isPublic,
required this.userID,
required this.annotation,
this.location,
});
@ -29,6 +31,7 @@ class Memory {
filePath: jsonData['location'],
isPublic: jsonData['is_public'],
userID: jsonData['user_id'],
annotation: jsonData['annotation'],
location: MemoryLocation.parse(jsonData),
);

View File

@ -4,6 +4,7 @@
"generalError": "There was an error",
"generalCancelButtonLabel": "Cancel",
"generalContinueButtonLabel": "Continue",
"generalSaveButtonLabel": "Save",
"generalUnderstoodButtonLabel": "OK",
"generalLoadingLabel": "Loading...",
@ -52,6 +53,10 @@
"mainScreenHelpSheetTakePhotoExplanation": "Tap on the shutter button once to take a photo.",
"mainScreenHelpSheetTakeVideoExplanation": "Hold down the shutter button to start recording a video. Leave the button to stop recording.",
"mainScreenAnnotationDialogTitle": "Add an annotation",
"mainScreenAnnotationDialogExplanation": "You can add an annotation to your memory",
"mainScreenAnnotationDialogAnnotationFieldLabel": "Annotation",
"recordingOverlayIsRecording": "Recording",
@ -151,6 +156,7 @@
"settingsScreenDeleteAccountConfirmLabel": "Delete Account now",
"settingsScreenGeneralSectionTitle": "General",
"settingsScreenGeneralSectionQualityLabel": "Quality",
"settingsScreenGeneralSectionAskForMemoryAnnotationsLabel": "Ask for memory annotations",
"settingsScreenResetHelpSheetsLabel": "Reset Help Sheets",
"settingsScreenResetHelpSheetsResetSuccessfully": "Help Sheets reset successfully.",

View File

@ -36,7 +36,8 @@ class FileManager {
static uploadFile(
final User user,
final File file, {
LocationData? locationData,
final LocationData? locationData,
final Future<String?>? annotationGetterFuture,
}) async {
await GlobalValuesManager.waitForInitialization();
@ -65,6 +66,15 @@ class FileManager {
data['location_heading'] = locationData.heading!;
}
if (annotationGetterFuture != null) {
final annotation = await annotationGetterFuture;
if (annotation != null) {
// User has specified annotation
data['annotation'] = annotation;
}
}
final memoryResponse =
await supabase.from('memories').insert(data).execute();

View File

@ -1,6 +1,7 @@
import 'dart:convert';
import 'package:camera/camera.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:quid_faciam_hodie/constants/storage_keys.dart';
@ -9,14 +10,20 @@ const secure = FlutterSecureStorage();
class Settings extends ChangeNotifier {
ResolutionPreset _resolution = ResolutionPreset.max;
bool _askForMemoryAnnotations = false;
Settings({final ResolutionPreset resolution = ResolutionPreset.max})
: _resolution = resolution;
Settings({
final ResolutionPreset? resolution,
final bool? askForMemoryAnnotations,
}) : _resolution = resolution ?? ResolutionPreset.max,
_askForMemoryAnnotations = askForMemoryAnnotations ?? true;
ResolutionPreset get resolution => _resolution;
bool get askForMemoryAnnotations => _askForMemoryAnnotations;
Map<String, dynamic> toJSONData() => {
'resolution': _resolution.toString(),
'askForMemoryAnnotations': _askForMemoryAnnotations ? 'true' : 'false',
};
Future<void> save() async {
@ -36,11 +43,23 @@ class Settings extends ChangeNotifier {
}
final data = jsonDecode(rawData);
final resolution = ResolutionPreset.values.firstWhere(
final resolution = ResolutionPreset.values.firstWhereOrNull(
(preset) => preset.toString() == data['resolution'],
);
final askForMemoryAnnotations = () {
switch (data['askForMemoryAnnotations']) {
case 'true':
return true;
case 'false':
return false;
default:
return null;
}
}();
return Settings(
resolution: resolution,
askForMemoryAnnotations: askForMemoryAnnotations,
);
}
@ -49,4 +68,10 @@ class Settings extends ChangeNotifier {
notifyListeners();
save();
}
void setAskForMemoryAnnotations(final bool askForMemoryAnnotations) {
_askForMemoryAnnotations = askForMemoryAnnotations;
notifyListeners();
save();
}
}

View File

@ -82,10 +82,9 @@ class _LoginScreenState extends AuthState<LoginScreen> with Loadable {
await _signUp();
} catch (error) {
if (mounted) {
if (isMaterial(context))
context.showLongErrorSnackBar(
message: localizations.loginScreenLoginFailed,
);
context.showLongErrorSnackBar(
message: localizations.loginScreenLoginFailed,
);
passwordController.clear();
}

View File

@ -17,6 +17,7 @@ import 'package:quid_faciam_hodie/constants/values.dart';
import 'package:quid_faciam_hodie/extensions/snackbar.dart';
import 'package:quid_faciam_hodie/managers/file_manager.dart';
import 'package:quid_faciam_hodie/managers/global_values_manager.dart';
import 'package:quid_faciam_hodie/screens/main_screen/annotation_dialog.dart';
import 'package:quid_faciam_hodie/screens/main_screen/camera_help_content.dart';
import 'package:quid_faciam_hodie/screens/main_screen/settings_button_overlay.dart';
import 'package:quid_faciam_hodie/utils/auth_required.dart';
@ -188,6 +189,62 @@ class _MainScreenState extends AuthRequiredState<MainScreen> with Loadable {
});
}
Future<String?> _createAskAnnotationDialog() => showPlatformDialog(
barrierDismissible: true,
context: context,
builder: (dialogContext) => const AnnotationDialog(),
);
void _lockCamera() => setState(() {
lockCamera = true;
});
void _releaseCamera() => setState(() {
lockCamera = false;
});
void _showUploadingPhotoAnimation(final File file) => setState(() {
uploadingPhotoAnimation = file.readAsBytesSync();
});
void _releaseUploadingPhotoAnimation() => setState(() {
uploadingPhotoAnimation = null;
});
Future<String?> getAnnotation() async {
final settings = GlobalValuesManager.settings!;
if (settings.askForMemoryAnnotations) {
return _createAskAnnotationDialog();
} else {
return '';
}
}
Future<void> setFlashModeBeforeApplyingAction() async {
if (isTorchEnabled) {
await controller!.setFlashMode(FlashMode.torch);
} else {
await controller!.setFlashMode(FlashMode.off);
}
}
Future<LocationData?> getLocation([
final File? fileToTag,
]) async {
if (!(await Permission.location.isGranted)) {
return null;
}
final locationData = await Location().getLocation();
if (fileToTag != null && Platform.isAndroid) {
await tagLocationToImage(fileToTag, locationData);
}
return locationData;
}
Future<void> takePhoto() async {
final localizations = AppLocalizations.of(context)!;
@ -195,109 +252,94 @@ class _MainScreenState extends AuthRequiredState<MainScreen> with Loadable {
return;
}
setState(() {
lockCamera = true;
});
_lockCamera();
try {
if (isMaterial(context))
context.showPendingSnackBar(
message: localizations.mainScreenTakePhotoActionTakingPhoto,
);
context.showPendingSnackBar(
message: localizations.mainScreenTakePhotoActionTakingPhoto,
);
if (isTorchEnabled) {
await controller!.setFlashMode(FlashMode.torch);
} else {
await controller!.setFlashMode(FlashMode.off);
}
await setFlashModeBeforeApplyingAction();
final file = File((await controller!.takePicture()).path);
setState(() {
uploadingPhotoAnimation = file.readAsBytesSync();
});
final annotationGetterFuture = getAnnotation();
final locationData = await getLocation(file);
if (isMaterial(context))
context.showPendingSnackBar(
message: localizations.mainScreenTakePhotoActionUploadingPhoto,
);
_showUploadingPhotoAnimation(file);
LocationData? locationData;
if (await Permission.location.isGranted) {
locationData = await Location().getLocation();
if (Platform.isAndroid) {
await tagLocationToImage(file, locationData);
}
}
context.showPendingSnackBar(
message: localizations.mainScreenTakePhotoActionUploadingPhoto,
);
try {
await FileManager.uploadFile(_user, file, locationData: locationData);
await FileManager.uploadFile(
_user,
file,
locationData: locationData,
annotationGetterFuture: annotationGetterFuture,
);
} catch (error) {
if (isMaterial(context))
context.showErrorSnackBar(message: error.toString());
context.showErrorSnackBar(message: error.toString());
return;
}
if (isMaterial(context))
context.showSuccessSnackBar(
message: localizations.mainScreenUploadSuccess,
);
context.showSuccessSnackBar(
message: localizations.mainScreenUploadSuccess,
);
} finally {
setState(() {
lockCamera = false;
uploadingPhotoAnimation = null;
});
_releaseCamera();
_releaseUploadingPhotoAnimation();
}
}
Future<void> takeVideo() async {
final localizations = AppLocalizations.of(context)!;
setState(() {
isRecording = false;
});
if (!controller!.value.isRecordingVideo) {
// Recording has already been stopped
return;
}
setState(() {
lockCamera = true;
isRecording = false;
});
_lockCamera();
try {
if (isMaterial(context))
context.showPendingSnackBar(
message: localizations.mainScreenTakeVideoActionSaveVideo,
);
context.showPendingSnackBar(
message: localizations.mainScreenTakeVideoActionSaveVideo,
);
final file = File((await controller!.stopVideoRecording()).path);
if (isMaterial(context))
context.showPendingSnackBar(
message: localizations.mainScreenTakeVideoActionUploadingVideo,
);
final annotationGetterFuture = getAnnotation();
final locationData = await getLocation();
context.showPendingSnackBar(
message: localizations.mainScreenTakeVideoActionUploadingVideo,
);
try {
await FileManager.uploadFile(_user, file);
await FileManager.uploadFile(
_user,
file,
annotationGetterFuture: annotationGetterFuture,
locationData: locationData,
);
} catch (error) {
if (isMaterial(context)) {
context.showErrorSnackBar(message: error.toString());
}
context.showErrorSnackBar(message: error.toString());
return;
}
if (isMaterial(context))
context.showSuccessSnackBar(
message: localizations.mainScreenUploadSuccess,
);
context.showSuccessSnackBar(
message: localizations.mainScreenUploadSuccess,
);
} finally {
setState(() {
lockCamera = false;
});
_releaseCamera();
}
}

View File

@ -0,0 +1,74 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:quid_faciam_hodie/constants/spacing.dart';
import 'package:quid_faciam_hodie/utils/theme.dart';
class AnnotationDialog extends StatefulWidget {
const AnnotationDialog({Key? key}) : super(key: key);
@override
State<AnnotationDialog> createState() => _AnnotationDialogState();
}
class _AnnotationDialogState extends State<AnnotationDialog> {
final TextEditingController controller = TextEditingController();
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final localizations = AppLocalizations.of(context)!;
return PlatformAlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(MEDIUM_SPACE),
child: Column(
children: <Widget>[
Text(
localizations.mainScreenAnnotationDialogTitle,
style: getTitleTextStyle(context),
),
const SizedBox(height: MEDIUM_SPACE),
Text(
localizations.mainScreenAnnotationDialogExplanation,
style: getBodyTextTextStyle(context),
),
const SizedBox(height: MEDIUM_SPACE),
TextField(
controller: controller,
autofocus: true,
decoration: InputDecoration(
labelText: localizations
.mainScreenAnnotationDialogAnnotationFieldLabel,
),
onSubmitted: (value) {
Navigator.of(context).pop(value);
},
),
],
),
),
],
),
actions: <Widget>[
PlatformDialogAction(
child: Text(localizations.generalCancelButtonLabel),
onPressed: () => Navigator.pop(context),
),
PlatformDialogAction(
child: Text(localizations.generalSaveButtonLabel),
onPressed: () => Navigator.pop(context, controller.text.trim()),
),
],
);
}
}

View File

@ -83,7 +83,7 @@ class _SettingsScreenState extends AuthRequiredState<SettingsScreen>
);
}
Widget getPicker() {
Widget getQualityPicker() {
final settings = GlobalValuesManager.settings!;
final resolutionTextMapping = getResolutionTextMapping(context);
final items = ResolutionPreset.values
@ -125,6 +125,7 @@ class _SettingsScreenState extends AuthRequiredState<SettingsScreen>
@override
Widget build(BuildContext context) {
final settings = GlobalValuesManager.settings!;
final localizations = AppLocalizations.of(context)!;
return PlatformScaffold(
@ -203,7 +204,15 @@ class _SettingsScreenState extends AuthRequiredState<SettingsScreen>
localizations
.settingsScreenGeneralSectionQualityLabel,
),
title: getPicker(),
title: getQualityPicker(),
),
SettingsTile.switchTile(
initialValue: settings.askForMemoryAnnotations,
onToggle: settings.setAskForMemoryAnnotations,
title: Text(
localizations
.settingsScreenGeneralSectionAskForMemoryAnnotationsLabel,
),
),
SettingsTile(
leading: Icon(context.platformIcons.help),
@ -213,12 +222,10 @@ class _SettingsScreenState extends AuthRequiredState<SettingsScreen>
onPressed: (_) async {
await UserHelpSheetsManager.deleteAll();
if (isMaterial(context)) {
context.showSuccessSnackBar(
message: localizations
.settingsScreenResetHelpSheetsResetSuccessfully,
);
}
context.showSuccessSnackBar(
message: localizations
.settingsScreenResetHelpSheetsResetSuccessfully,
);
},
)
],

View File

@ -65,13 +65,11 @@ class _MemorySheetState extends State<MemorySheet> with Loadable {
Navigator.pop(context);
if (isMaterial(context))
context.showSuccessSnackBar(
message: localizations.memorySheetSavedToGallery,
);
context.showSuccessSnackBar(
message: localizations.memorySheetSavedToGallery,
);
} catch (error) {
if (isMaterial(context))
context.showErrorSnackBar(message: localizations.generalError);
context.showErrorSnackBar(message: localizations.generalError);
}
}
@ -94,10 +92,9 @@ class _MemorySheetState extends State<MemorySheet> with Loadable {
Navigator.pop(context);
if (isNowPublic) {
if (isMaterial(context))
context.showSuccessSnackBar(
message: localizations.memorySheetMemoryUpdatedToPublic,
);
context.showSuccessSnackBar(
message: localizations.memorySheetMemoryUpdatedToPublic,
);
} else {
if (isMaterial(context))
context.showSuccessSnackBar(
@ -105,8 +102,7 @@ class _MemorySheetState extends State<MemorySheet> with Loadable {
);
}
} catch (error) {
if (isMaterial(context))
context.showErrorSnackBar(message: localizations.generalError);
context.showErrorSnackBar(message: localizations.generalError);
}
}

View File

@ -1,12 +1,15 @@
import 'dart:async';
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:provider/provider.dart';
import 'package:quid_faciam_hodie/constants/spacing.dart';
import 'package:quid_faciam_hodie/enums.dart';
import 'package:quid_faciam_hodie/foreign_types/memory.dart';
import 'package:quid_faciam_hodie/models/timeline.dart';
import 'package:quid_faciam_hodie/widgets/raw_memory_display.dart';
import 'package:video_player/video_player.dart';
@ -37,6 +40,7 @@ class MemoryView extends StatefulWidget {
class _MemoryViewState extends State<MemoryView> {
MemoryFetchStatus status = MemoryFetchStatus.downloading;
Uint8List? data;
Timer? _nextMemoryTimer;
@override
void initState() {
@ -45,7 +49,16 @@ class _MemoryViewState extends State<MemoryView> {
loadMemoryFile();
}
@override
void dispose() {
_nextMemoryTimer?.cancel();
super.dispose();
}
Future<void> loadMemoryFile() async {
final timeline = context.read<TimelineModel>();
setState(() {
status = MemoryFetchStatus.downloading;
});
@ -75,6 +88,12 @@ class _MemoryViewState extends State<MemoryView> {
setState(() {
status = MemoryFetchStatus.error;
});
_nextMemoryTimer = Timer(
const Duration(seconds: 1),
timeline.nextMemory,
);
return;
}
}

View File

@ -51,44 +51,70 @@ class TimelineOverlay extends StatelessWidget {
),
),
Positioned(
right: SMALL_SPACE,
left: 0,
right: 0,
bottom: SMALL_SPACE * 2,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: SMALL_SPACE),
padding: const EdgeInsets.symmetric(horizontal: MEDIUM_SPACE),
child: AnimatedOpacity(
duration: const Duration(milliseconds: 500),
curve: Curves.linearToEaseOut,
opacity: timeline.showOverlay ? 1.0 : 0.0,
child: Row(
mainAxisAlignment:
(timeline.currentMemory.annotation.isNotEmpty)
? MainAxisAlignment.spaceBetween
: MainAxisAlignment.end,
children: <Widget>[
AnimatedOpacity(
opacity: timeline.currentMemory.isPublic ? 1.0 : 0.0,
duration: const Duration(milliseconds: 500),
curve: Curves.linearToEaseOut,
child: Icon(
Icons.public,
size: platformThemeData(
if (timeline.currentMemory.annotation.isNotEmpty)
Text(
timeline.currentMemory.annotation,
style: platformThemeData(
context,
material: (data) => data.textTheme.bodyLarge!.fontSize,
cupertino: (data) => data.textTheme.textStyle.fontSize,
material: (data) => data.textTheme.titleSmall!.copyWith(
color: Colors.white,
),
cupertino: (data) =>
data.textTheme.navTitleTextStyle.copyWith(
color: Colors.white,
),
),
color: Colors.white,
),
Row(
children: <Widget>[
AnimatedOpacity(
opacity: timeline.currentMemory.isPublic ? 1.0 : 0.0,
duration: const Duration(milliseconds: 500),
curve: Curves.linearToEaseOut,
child: Icon(
Icons.public,
size: platformThemeData(
context,
material: (data) =>
data.textTheme.bodyLarge!.fontSize,
cupertino: (data) =>
data.textTheme.textStyle.fontSize,
),
color: Colors.white,
),
),
const SizedBox(width: SMALL_SPACE),
Text(
'$memoryIndex/$memoriesAmount',
style: platformThemeData(
context,
material: (data) =>
data.textTheme.titleSmall!.copyWith(
color: Colors.white,
),
cupertino: (data) =>
data.textTheme.navTitleTextStyle.copyWith(
color: Colors.white,
),
),
),
],
),
const SizedBox(width: SMALL_SPACE),
Text(
'$memoryIndex/$memoriesAmount',
style: platformThemeData(
context,
material: (data) => data.textTheme.titleSmall!.copyWith(
color: Colors.white,
),
cupertino: (data) =>
data.textTheme.navTitleTextStyle.copyWith(
color: Colors.white,
),
),
)
],
),
),

View File

@ -60,11 +60,9 @@ class KeyValueInfo extends StatelessWidget {
HapticFeedback.lightImpact();
Clipboard.setData(ClipboardData(text: value));
if (isMaterial(context)) {
context.showSuccessSnackBar(
message: 'Copied to clipboard!',
);
}
context.showSuccessSnackBar(
message: 'Copied to clipboard!',
);
},
)
: null,

View File

@ -43,12 +43,18 @@ class _RawMemoryDisplayState extends State<RawMemoryDisplay> {
Future<File> createTempVideo() async {
final tempDirectory = await getTemporaryDirectory();
final path = '${tempDirectory.path}/${widget.filename ?? 'video.mp4'}';
print("#" * 50);
print(widget.filename);
print(path);
final file = File(path);
print(await file.exists());
print(widget.data);
/*
if (await file.exists()) {
// File already exists, so just return it
return file;
}
}*/
// File needs to be created
await file.create();

View File

@ -441,6 +441,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
morphing_text:
dependency: "direct main"
description:
name: morphing_text
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
nested:
dependency: transitive
description:

View File

@ -63,6 +63,7 @@ dependencies:
flutter_osm_plugin: ^0.39.0
url_launcher: ^6.1.5
apple_maps_flutter: ^1.2.0
morphing_text: ^1.0.1
dev_dependencies:
flutter_test: