mirror of
https://github.com/Myzel394/quid_faciam_hodie.git
synced 2025-06-18 15:25:27 +02:00
created help sheets; improvements & bugfixes
This commit is contained in:
parent
909d4f8d89
commit
6d28ee51dd
4
lib/constants/help_sheet_id.dart
Normal file
4
lib/constants/help_sheet_id.dart
Normal file
@ -0,0 +1,4 @@
|
||||
enum HelpSheetID {
|
||||
mainScreen,
|
||||
timelineScreen,
|
||||
}
|
@ -1,2 +1,3 @@
|
||||
const CACHE_KEY = '_cache';
|
||||
const SETTINGS_KEY = 'settings';
|
||||
const USER_HELP_SHEETS_KEY = 'user_help_sheets';
|
||||
|
@ -4,6 +4,7 @@
|
||||
"generalError": "There was an error",
|
||||
"generalCancelButtonLabel": "Cancel",
|
||||
"generalContinueButtonLabel": "Continue",
|
||||
"generalUnderstoodButtonLabel": "OK",
|
||||
|
||||
"welcomeScreenDescription": "Find out what you did all the days and unlock moments you completely forgot!",
|
||||
"welcomeScreenSubtitle": "What did I do today?",
|
||||
@ -46,6 +47,10 @@
|
||||
"mainScreenTakeVideoActionSaveVideo": "Taking video, please hold still...",
|
||||
"mainScreenTakeVideoActionUploadingVideo": "Uploading video...",
|
||||
|
||||
"mainScreenHelpSheetTitle": "Record your best moments in life",
|
||||
"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.",
|
||||
|
||||
"recordingOverlayIsRecording": "Recording",
|
||||
|
||||
|
||||
@ -153,6 +158,13 @@
|
||||
|
||||
|
||||
"timelineScreenTitle": "Timeline",
|
||||
"timelineScreenHelpSheetTitle": "Welcome to your timeline",
|
||||
"timelineHelpContentDescription": "Your memories are displayed in chronological order. You can swipe left or right to navigate through the memories of the given day. You can also swipe up or down to navigate through the days of your memories. The timeline automatically progresses through your memories.",
|
||||
"timelineHelpContentHoldDownExplanation": "Hold down to pause the timeline from progressing to your next memory.",
|
||||
"timelineHelpContentTapTwiceExplanation": "Tap twice to see more details about your memory.",
|
||||
|
||||
|
||||
"helpSheetDontShowAgain": "Don't show this help sheet again",
|
||||
|
||||
|
||||
"enumMapping_ResolutionPreset_low": "Low",
|
||||
|
@ -85,7 +85,9 @@ class _MyAppState extends State<MyApp> {
|
||||
TimelineScreen.ID: (context) => const TimelineScreen(),
|
||||
GrantPermissionScreen.ID: (context) => const GrantPermissionScreen(),
|
||||
CalendarScreen.ID: (context) => const CalendarScreen(),
|
||||
ServerLoadingScreen.ID: (context) => const ServerLoadingScreen(),
|
||||
ServerLoadingScreen.ID: (context) => const ServerLoadingScreen(
|
||||
isInitialLoading: true,
|
||||
),
|
||||
EmptyScreen.ID: (context) => const EmptyScreen(),
|
||||
SettingsScreen.ID: (context) => const SettingsScreen(),
|
||||
},
|
||||
|
@ -14,12 +14,16 @@ class CacheManager {
|
||||
final existingEntry = await storage.read(key: cacheKey);
|
||||
|
||||
if (existingEntry != null) {
|
||||
final entry = jsonDecode(existingEntry);
|
||||
final DateTime creationDate = DateTime.parse(entry['creationDate']);
|
||||
try {
|
||||
final entry = jsonDecode(existingEntry);
|
||||
final DateTime creationDate = DateTime.parse(entry['creationDate']);
|
||||
|
||||
// Check if the entry is still valid using CACHE_INVALIDATION_DURATION as the validity duration.
|
||||
return DateTime.now().difference(creationDate) <
|
||||
CACHE_INVALIDATION_DURATION;
|
||||
// Check if the entry is still valid using CACHE_INVALIDATION_DURATION as the validity duration.
|
||||
return DateTime.now().difference(creationDate) <
|
||||
CACHE_INVALIDATION_DURATION;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
27
lib/managers/user_help_sheets_manager.dart
Normal file
27
lib/managers/user_help_sheets_manager.dart
Normal file
@ -0,0 +1,27 @@
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:quid_faciam_hodie/constants/help_sheet_id.dart';
|
||||
import 'package:quid_faciam_hodie/constants/storage_keys.dart';
|
||||
|
||||
const storage = FlutterSecureStorage();
|
||||
|
||||
class UserHelpSheetsManager {
|
||||
static String _createKey(final HelpSheetID helpID) =>
|
||||
'$USER_HELP_SHEETS_KEY/$helpID';
|
||||
|
||||
static Future<bool> getIfAlreadyShown(final HelpSheetID helpID) async =>
|
||||
(await storage.read(key: _createKey(helpID))) == 'true';
|
||||
|
||||
static Future<void> setAsShown(final HelpSheetID helpID) async =>
|
||||
storage.write(
|
||||
key: _createKey(helpID),
|
||||
value: 'true',
|
||||
);
|
||||
|
||||
static Future<void> deleteAll() async {
|
||||
const keys = HelpSheetID.values;
|
||||
|
||||
for (final key in keys) {
|
||||
await storage.delete(key: _createKey(key));
|
||||
}
|
||||
}
|
||||
}
|
@ -94,7 +94,14 @@ class _LoginScreenState extends AuthState<LoginScreen> with Loadable {
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
Navigator.pushReplacementNamed(context, ServerLoadingScreen.ID);
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ServerLoadingScreen(
|
||||
nextScreen: MainScreen.ID,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,25 +1,30 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:expandable_bottom_sheet/expandable_bottom_sheet.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
|
||||
import 'package:location/location.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:quid_faciam_hodie/constants/help_sheet_id.dart';
|
||||
import 'package:quid_faciam_hodie/constants/spacing.dart';
|
||||
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/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';
|
||||
import 'package:quid_faciam_hodie/utils/loadable.dart';
|
||||
import 'package:quid_faciam_hodie/utils/tag_location_to_image.dart';
|
||||
import 'package:quid_faciam_hodie/widgets/animate_in_builder.dart';
|
||||
import 'package:quid_faciam_hodie/widgets/fade_and_move_in_animation.dart';
|
||||
import 'package:quid_faciam_hodie/widgets/help_sheet.dart';
|
||||
import 'package:quid_faciam_hodie/widgets/icon_button_child.dart';
|
||||
import 'package:quid_faciam_hodie/widgets/sheet_indicator.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
@ -330,211 +335,218 @@ class _MainScreenState extends AuthRequiredState<MainScreen> with Loadable {
|
||||
);
|
||||
}
|
||||
|
||||
return Container(
|
||||
color: Colors.black,
|
||||
child: ExpandableBottomSheet(
|
||||
background: SafeArea(
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: AnimateInBuilder(
|
||||
builder: (showPreview) => AnimatedOpacity(
|
||||
opacity: showPreview ? 1.0 : 0.0,
|
||||
duration: const Duration(milliseconds: 1100),
|
||||
curve: Curves.easeOutQuad,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(SMALL_SPACE),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1 / controller!.value.aspectRatio,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
fit: StackFit.expand,
|
||||
children: <Widget>[
|
||||
controller!.buildPreview(),
|
||||
if (isRecording)
|
||||
RecordingOverlay(controller: controller!),
|
||||
if (!isRecording) SettingsButtonOverlay(),
|
||||
if (uploadingPhotoAnimation != null)
|
||||
UploadingPhoto(
|
||||
data: uploadingPhotoAnimation!,
|
||||
),
|
||||
],
|
||||
return HelpSheet(
|
||||
title: localizations.mainScreenHelpSheetTitle,
|
||||
helpContent: const CameraHelpContent(),
|
||||
helpID: HelpSheetID.mainScreen,
|
||||
child: Container(
|
||||
color: Colors.black,
|
||||
child: ExpandableBottomSheet(
|
||||
background: SafeArea(
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: AnimateInBuilder(
|
||||
builder: (showPreview) => AnimatedOpacity(
|
||||
opacity: showPreview ? 1.0 : 0.0,
|
||||
duration: const Duration(milliseconds: 1100),
|
||||
curve: Curves.easeOutQuad,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(SMALL_SPACE),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1 / controller!.value.aspectRatio,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
fit: StackFit.expand,
|
||||
children: <Widget>[
|
||||
controller!.buildPreview(),
|
||||
if (isRecording)
|
||||
RecordingOverlay(controller: controller!),
|
||||
if (!isRecording) SettingsButtonOverlay(),
|
||||
if (uploadingPhotoAnimation != null)
|
||||
UploadingPhoto(
|
||||
data: uploadingPhotoAnimation!,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
persistentHeader: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.black,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(LARGE_SPACE),
|
||||
topRight: Radius.circular(LARGE_SPACE),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: MEDIUM_SPACE,
|
||||
horizontal: MEDIUM_SPACE,
|
||||
),
|
||||
child: SheetIndicator(),
|
||||
persistentHeader: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.black,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(LARGE_SPACE),
|
||||
topRight: Radius.circular(LARGE_SPACE),
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: SMALL_SPACE),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: FadeAndMoveInAnimation(
|
||||
translationDuration:
|
||||
DEFAULT_TRANSLATION_DURATION *
|
||||
SECONDARY_BUTTONS_DURATION_MULTIPLIER,
|
||||
opacityDuration: DEFAULT_OPACITY_DURATION *
|
||||
SECONDARY_BUTTONS_DURATION_MULTIPLIER,
|
||||
child: ChangeCameraButton(
|
||||
disabled: lockCamera || isRecording,
|
||||
onChangeCamera: () {
|
||||
final currentCameraIndex = GlobalValuesManager
|
||||
.cameras
|
||||
.indexOf(controller!.description);
|
||||
final availableCameras =
|
||||
GlobalValuesManager.cameras.length;
|
||||
|
||||
onNewCameraSelected(
|
||||
GlobalValuesManager.cameras[
|
||||
(currentCameraIndex + 1) %
|
||||
availableCameras],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: FadeAndMoveInAnimation(
|
||||
child: RecordButton(
|
||||
disabled: lockCamera,
|
||||
active: isRecording,
|
||||
onVideoBegin: () async {
|
||||
setState(() {
|
||||
isRecording = true;
|
||||
});
|
||||
|
||||
if (controller!.value.isRecordingVideo) {
|
||||
// A recording has already started, do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
await controller!.startVideoRecording();
|
||||
},
|
||||
onVideoEnd: takeVideo,
|
||||
onPhotoShot: takePhoto,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: FadeAndMoveInAnimation(
|
||||
translationDuration:
|
||||
DEFAULT_TRANSLATION_DURATION *
|
||||
SECONDARY_BUTTONS_DURATION_MULTIPLIER,
|
||||
opacityDuration: DEFAULT_OPACITY_DURATION *
|
||||
SECONDARY_BUTTONS_DURATION_MULTIPLIER,
|
||||
child: TodayPhotoButton(
|
||||
onLeave: () {
|
||||
controller!.setFlashMode(FlashMode.off);
|
||||
},
|
||||
onComeBack: () {
|
||||
if (isTorchEnabled) {
|
||||
controller!.setFlashMode(FlashMode.torch);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
expandableContent: Container(
|
||||
color: Colors.black,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: LARGE_SPACE,
|
||||
right: LARGE_SPACE,
|
||||
bottom: MEDIUM_SPACE,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
ElevatedButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor:
|
||||
MaterialStateProperty.resolveWith<Color>(
|
||||
(_) => isTorchEnabled ? Colors.white : Colors.black,
|
||||
),
|
||||
foregroundColor:
|
||||
MaterialStateProperty.resolveWith<Color>(
|
||||
(_) => isTorchEnabled ? Colors.black : Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
isTorchEnabled = !isTorchEnabled;
|
||||
|
||||
if (isTorchEnabled) {
|
||||
controller!.setFlashMode(FlashMode.torch);
|
||||
} else {
|
||||
controller!.setFlashMode(FlashMode.off);
|
||||
}
|
||||
});
|
||||
},
|
||||
child: IconButtonChild(
|
||||
icon: const Icon(Icons.flashlight_on_rounded),
|
||||
label:
|
||||
Text(localizations.mainScreenActionsTorchButton),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: MEDIUM_SPACE,
|
||||
horizontal: MEDIUM_SPACE,
|
||||
),
|
||||
child: SheetIndicator(),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor:
|
||||
MaterialStateProperty.resolveWith<Color>(
|
||||
(_) => Colors.white10,
|
||||
),
|
||||
foregroundColor:
|
||||
MaterialStateProperty.resolveWith<Color>(
|
||||
(_) => Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: zoomLevels == null
|
||||
? null
|
||||
: () {
|
||||
final newZoomLevelIndex =
|
||||
((currentZoomLevelIndex + 1) %
|
||||
zoomLevels!.length);
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: SMALL_SPACE),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: FadeAndMoveInAnimation(
|
||||
translationDuration:
|
||||
DEFAULT_TRANSLATION_DURATION *
|
||||
SECONDARY_BUTTONS_DURATION_MULTIPLIER,
|
||||
opacityDuration: DEFAULT_OPACITY_DURATION *
|
||||
SECONDARY_BUTTONS_DURATION_MULTIPLIER,
|
||||
child: ChangeCameraButton(
|
||||
disabled: lockCamera || isRecording,
|
||||
onChangeCamera: () {
|
||||
final currentCameraIndex =
|
||||
GlobalValuesManager.cameras
|
||||
.indexOf(controller!.description);
|
||||
final availableCameras =
|
||||
GlobalValuesManager.cameras.length;
|
||||
|
||||
controller!.setZoomLevel(
|
||||
zoomLevels![newZoomLevelIndex]);
|
||||
|
||||
setState(() {
|
||||
currentZoomLevelIndex = newZoomLevelIndex;
|
||||
});
|
||||
},
|
||||
child: zoomLevels == null
|
||||
? Text(formatZoomLevel(1.0))
|
||||
: Text(
|
||||
formatZoomLevel(currentZoomLevel),
|
||||
onNewCameraSelected(
|
||||
GlobalValuesManager.cameras[
|
||||
(currentCameraIndex + 1) %
|
||||
availableCameras],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: FadeAndMoveInAnimation(
|
||||
child: RecordButton(
|
||||
disabled: lockCamera,
|
||||
active: isRecording,
|
||||
onVideoBegin: () async {
|
||||
setState(() {
|
||||
isRecording = true;
|
||||
});
|
||||
|
||||
if (controller!.value.isRecordingVideo) {
|
||||
// A recording has already started, do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
await controller!.startVideoRecording();
|
||||
},
|
||||
onVideoEnd: takeVideo,
|
||||
onPhotoShot: takePhoto,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: FadeAndMoveInAnimation(
|
||||
translationDuration:
|
||||
DEFAULT_TRANSLATION_DURATION *
|
||||
SECONDARY_BUTTONS_DURATION_MULTIPLIER,
|
||||
opacityDuration: DEFAULT_OPACITY_DURATION *
|
||||
SECONDARY_BUTTONS_DURATION_MULTIPLIER,
|
||||
child: TodayPhotoButton(
|
||||
onLeave: () {
|
||||
controller!.setFlashMode(FlashMode.off);
|
||||
},
|
||||
onComeBack: () {
|
||||
if (isTorchEnabled) {
|
||||
controller!.setFlashMode(FlashMode.torch);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
expandableContent: Container(
|
||||
color: Colors.black,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: LARGE_SPACE,
|
||||
right: LARGE_SPACE,
|
||||
bottom: MEDIUM_SPACE,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: <Widget>[
|
||||
ElevatedButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor:
|
||||
MaterialStateProperty.resolveWith<Color>(
|
||||
(_) =>
|
||||
isTorchEnabled ? Colors.white : Colors.black,
|
||||
),
|
||||
foregroundColor:
|
||||
MaterialStateProperty.resolveWith<Color>(
|
||||
(_) =>
|
||||
isTorchEnabled ? Colors.black : Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
isTorchEnabled = !isTorchEnabled;
|
||||
|
||||
if (isTorchEnabled) {
|
||||
controller!.setFlashMode(FlashMode.torch);
|
||||
} else {
|
||||
controller!.setFlashMode(FlashMode.off);
|
||||
}
|
||||
});
|
||||
},
|
||||
child: IconButtonChild(
|
||||
icon: const Icon(Icons.flashlight_on_rounded),
|
||||
label: Text(
|
||||
localizations.mainScreenActionsTorchButton),
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor:
|
||||
MaterialStateProperty.resolveWith<Color>(
|
||||
(_) => Colors.white10,
|
||||
),
|
||||
foregroundColor:
|
||||
MaterialStateProperty.resolveWith<Color>(
|
||||
(_) => Colors.white,
|
||||
),
|
||||
),
|
||||
onPressed: zoomLevels == null
|
||||
? null
|
||||
: () {
|
||||
final newZoomLevelIndex =
|
||||
((currentZoomLevelIndex + 1) %
|
||||
zoomLevels!.length);
|
||||
|
||||
controller!.setZoomLevel(
|
||||
zoomLevels![newZoomLevelIndex]);
|
||||
|
||||
setState(() {
|
||||
currentZoomLevelIndex = newZoomLevelIndex;
|
||||
});
|
||||
},
|
||||
child: zoomLevels == null
|
||||
? Text(formatZoomLevel(1.0))
|
||||
: Text(
|
||||
formatZoomLevel(currentZoomLevel),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
60
lib/screens/main_screen/camera_help_content.dart
Normal file
60
lib/screens/main_screen/camera_help_content.dart
Normal file
@ -0,0 +1,60 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:quid_faciam_hodie/constants/spacing.dart';
|
||||
import 'package:quid_faciam_hodie/utils/theme.dart';
|
||||
import 'package:quid_faciam_hodie/widgets/help_content_text.dart';
|
||||
|
||||
class CameraHelpContent extends StatelessWidget {
|
||||
const CameraHelpContent({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final localizations = AppLocalizations.of(context)!;
|
||||
final iconColor = getBodyTextColor(context).withOpacity(.2);
|
||||
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
HelpContentText(
|
||||
icon: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Icons.circle_rounded,
|
||||
color: iconColor,
|
||||
),
|
||||
const Icon(
|
||||
Icons.circle_rounded,
|
||||
color: Colors.white,
|
||||
size: 13,
|
||||
),
|
||||
],
|
||||
),
|
||||
text: localizations.mainScreenHelpSheetTakePhotoExplanation,
|
||||
),
|
||||
const SizedBox(height: MEDIUM_SPACE),
|
||||
HelpContentText(
|
||||
icon: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Icons.circle_rounded,
|
||||
color: iconColor,
|
||||
),
|
||||
Icon(
|
||||
Icons.circle_rounded,
|
||||
color: Colors.white,
|
||||
size: 19,
|
||||
),
|
||||
Icon(
|
||||
Icons.square_rounded,
|
||||
color: Colors.red,
|
||||
size: 10,
|
||||
),
|
||||
],
|
||||
),
|
||||
text: localizations.mainScreenHelpSheetTakeVideoExplanation,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -5,11 +5,11 @@ import 'package:provider/provider.dart';
|
||||
import 'package:quid_faciam_hodie/constants/spacing.dart';
|
||||
import 'package:quid_faciam_hodie/managers/global_values_manager.dart';
|
||||
import 'package:quid_faciam_hodie/models/memories.dart';
|
||||
import 'package:quid_faciam_hodie/screens/grant_permission_screen.dart';
|
||||
import 'package:quid_faciam_hodie/screens/login_screen.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
import 'empty_screen.dart';
|
||||
import 'grant_permission_screen.dart';
|
||||
import 'main_screen.dart';
|
||||
import 'server_loading_screen/dot_animation.dart';
|
||||
import 'welcome_screen.dart';
|
||||
@ -18,10 +18,12 @@ class ServerLoadingScreen extends StatefulWidget {
|
||||
static const ID = '/';
|
||||
|
||||
final String? nextScreen;
|
||||
final bool isInitialLoading;
|
||||
|
||||
const ServerLoadingScreen({
|
||||
Key? key,
|
||||
this.nextScreen,
|
||||
this.isInitialLoading = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@ -33,15 +35,27 @@ class _ServerLoadingScreenState extends State<ServerLoadingScreen> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
load();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
load();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> load() async {
|
||||
while (!(await GlobalValuesManager.hasGrantedPermissions())) {
|
||||
if (widget.isInitialLoading) {
|
||||
await Navigator.pushNamed(
|
||||
context,
|
||||
GrantPermissionScreen.ID,
|
||||
WelcomeScreen.ID,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (widget.nextScreen != WelcomeScreen.ID) {
|
||||
while (!(await GlobalValuesManager.hasGrantedPermissions())) {
|
||||
await Navigator.pushNamed(
|
||||
context,
|
||||
GrantPermissionScreen.ID,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await GlobalValuesManager.waitForInitialization();
|
||||
|
@ -1,12 +1,15 @@
|
||||
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/help_sheet_id.dart';
|
||||
import 'package:quid_faciam_hodie/extensions/date.dart';
|
||||
import 'package:quid_faciam_hodie/models/memories.dart';
|
||||
import 'package:quid_faciam_hodie/models/timeline.dart';
|
||||
import 'package:quid_faciam_hodie/screens/timeline_screen/timeline_help_content.dart';
|
||||
import 'package:quid_faciam_hodie/utils/loadable.dart';
|
||||
import 'package:quid_faciam_hodie/widgets/help_sheet.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
import 'calendar_screen.dart';
|
||||
import 'empty_screen.dart';
|
||||
@ -111,29 +114,39 @@ class _TimelineScreenState extends State<TimelineScreen> with Loadable {
|
||||
|
||||
return true;
|
||||
},
|
||||
child: PlatformScaffold(
|
||||
appBar: isCupertino(context)
|
||||
? PlatformAppBar(
|
||||
title: Text(localizations.timelineScreenTitle),
|
||||
)
|
||||
: null,
|
||||
body: ChangeNotifierProvider.value(
|
||||
value: timeline,
|
||||
child: PageView.builder(
|
||||
controller: pageController,
|
||||
scrollDirection: Axis.vertical,
|
||||
itemCount: timeline.values.length,
|
||||
onPageChanged: (newPage) {
|
||||
if (timeline.currentIndex != newPage) {
|
||||
// User manually changed page
|
||||
timeline.setCurrentIndex(newPage);
|
||||
child: HelpSheet(
|
||||
title: localizations.timelineScreenHelpSheetTitle,
|
||||
helpContent: const TimelineHelpContent(),
|
||||
helpID: HelpSheetID.timelineScreen,
|
||||
onSheetShown: timeline.pause,
|
||||
onSheetHidden: () {
|
||||
timeline.resume();
|
||||
print("dfsjnifksdf");
|
||||
},
|
||||
child: PlatformScaffold(
|
||||
appBar: isCupertino(context)
|
||||
? PlatformAppBar(
|
||||
title: Text(localizations.timelineScreenTitle),
|
||||
)
|
||||
: null,
|
||||
body: ChangeNotifierProvider.value(
|
||||
value: timeline,
|
||||
child: PageView.builder(
|
||||
controller: pageController,
|
||||
scrollDirection: Axis.vertical,
|
||||
itemCount: timeline.values.length,
|
||||
onPageChanged: (newPage) {
|
||||
if (timeline.currentIndex != newPage) {
|
||||
// User manually changed page
|
||||
timeline.setCurrentIndex(newPage);
|
||||
|
||||
timeline.setMemoryIndex(0);
|
||||
}
|
||||
},
|
||||
itemBuilder: (_, index) => TimelinePage(
|
||||
date: timeline.dateAtIndex(index),
|
||||
memories: timeline.atIndex(index),
|
||||
timeline.setMemoryIndex(0);
|
||||
}
|
||||
},
|
||||
itemBuilder: (_, index) => TimelinePage(
|
||||
date: timeline.dateAtIndex(index),
|
||||
memories: timeline.atIndex(index),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
41
lib/screens/timeline_screen/timeline_help_content.dart
Normal file
41
lib/screens/timeline_screen/timeline_help_content.dart
Normal file
@ -0,0 +1,41 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:quid_faciam_hodie/constants/spacing.dart';
|
||||
import 'package:quid_faciam_hodie/utils/theme.dart';
|
||||
import 'package:quid_faciam_hodie/widgets/help_content_text.dart';
|
||||
|
||||
class TimelineHelpContent extends StatelessWidget {
|
||||
const TimelineHelpContent({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final localizations = AppLocalizations.of(context)!;
|
||||
final textColor = getBodyTextColor(context);
|
||||
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
localizations.timelineHelpContentDescription,
|
||||
textAlign: TextAlign.center,
|
||||
style: getBodyTextTextStyle(context),
|
||||
),
|
||||
const SizedBox(height: LARGE_SPACE),
|
||||
HelpContentText(
|
||||
icon: Icon(
|
||||
Icons.touch_app_rounded,
|
||||
color: textColor,
|
||||
),
|
||||
text: localizations.timelineHelpContentHoldDownExplanation,
|
||||
),
|
||||
const SizedBox(height: MEDIUM_SPACE),
|
||||
HelpContentText(
|
||||
icon: Icon(
|
||||
Icons.align_vertical_bottom_sharp,
|
||||
color: textColor,
|
||||
),
|
||||
text: localizations.timelineHelpContentTapTwiceExplanation,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ 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/managers/user_help_sheets_manager.dart';
|
||||
|
||||
import 'welcome_screen/pages/get_started_page.dart';
|
||||
import 'welcome_screen/pages/guide_page.dart';
|
||||
@ -19,6 +20,13 @@ class WelcomeScreen extends StatefulWidget {
|
||||
class _WelcomeScreenState extends State<WelcomeScreen> {
|
||||
final controller = PageController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
UserHelpSheetsManager.deleteAll();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
|
@ -24,9 +24,11 @@ mixin Loadable {
|
||||
try {
|
||||
await callback();
|
||||
} finally {
|
||||
setState(() {
|
||||
_IDs.remove(id ?? _generalLoadingID);
|
||||
});
|
||||
try {
|
||||
setState(() {
|
||||
_IDs.remove(id ?? _generalLoadingID);
|
||||
});
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
34
lib/widgets/help_content_text.dart
Normal file
34
lib/widgets/help_content_text.dart
Normal file
@ -0,0 +1,34 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:quid_faciam_hodie/utils/theme.dart';
|
||||
|
||||
class HelpContentText extends StatelessWidget {
|
||||
final String text;
|
||||
final Widget icon;
|
||||
|
||||
const HelpContentText({
|
||||
Key? key,
|
||||
required this.text,
|
||||
required this.icon,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: icon,
|
||||
),
|
||||
const Spacer(flex: 1),
|
||||
Expanded(
|
||||
flex: 8,
|
||||
child: Text(
|
||||
text,
|
||||
style: getBodyTextTextStyle(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
117
lib/widgets/help_sheet.dart
Normal file
117
lib/widgets/help_sheet.dart
Normal file
@ -0,0 +1,117 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
|
||||
import 'package:quid_faciam_hodie/constants/help_sheet_id.dart';
|
||||
import 'package:quid_faciam_hodie/constants/spacing.dart';
|
||||
import 'package:quid_faciam_hodie/managers/user_help_sheets_manager.dart';
|
||||
import 'package:quid_faciam_hodie/widgets/help_sheet/help_sheet_form.dart';
|
||||
|
||||
class HelpSheet extends StatefulWidget {
|
||||
final Widget child;
|
||||
final String title;
|
||||
final Widget helpContent;
|
||||
final HelpSheetID helpID;
|
||||
final bool forceShow;
|
||||
final VoidCallback? onSheetShown;
|
||||
final VoidCallback? onSheetHidden;
|
||||
|
||||
const HelpSheet({
|
||||
Key? key,
|
||||
required this.child,
|
||||
required this.title,
|
||||
required this.helpContent,
|
||||
required this.helpID,
|
||||
this.forceShow = false,
|
||||
this.onSheetShown,
|
||||
this.onSheetHidden,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<HelpSheet> createState() => _HelpSheetState();
|
||||
}
|
||||
|
||||
class _HelpSheetState extends State<HelpSheet> {
|
||||
bool isShowingSheet = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
if (widget.forceShow) {
|
||||
showSheet();
|
||||
} else {
|
||||
checkIfSheetShouldBeShown();
|
||||
}
|
||||
}
|
||||
|
||||
void showSheet() {
|
||||
if (isShowingSheet) {
|
||||
return;
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
isShowingSheet = true;
|
||||
});
|
||||
|
||||
if (widget.onSheetShown != null) {
|
||||
widget.onSheetShown!();
|
||||
}
|
||||
|
||||
final dontShowSheetAgain = await showPlatformModalSheet(
|
||||
material: MaterialModalSheetData(
|
||||
isDismissible: false,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(LARGE_SPACE),
|
||||
topRight: Radius.circular(LARGE_SPACE),
|
||||
),
|
||||
),
|
||||
),
|
||||
context: context,
|
||||
builder: (_) => HelpSheetForm(
|
||||
helpContent: widget.helpContent,
|
||||
title: widget.title,
|
||||
),
|
||||
);
|
||||
|
||||
if (widget.onSheetHidden != null) {
|
||||
widget.onSheetHidden!();
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
isShowingSheet = false;
|
||||
});
|
||||
}
|
||||
|
||||
if (dontShowSheetAgain) {
|
||||
await UserHelpSheetsManager.setAsShown(widget.helpID);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> checkIfSheetShouldBeShown() async {
|
||||
final hasSheetBeShownAlready =
|
||||
await UserHelpSheetsManager.getIfAlreadyShown(widget.helpID);
|
||||
|
||||
if (!hasSheetBeShownAlready) {
|
||||
showSheet();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedScale(
|
||||
scale: isShowingSheet ? .99 : 1,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
67
lib/widgets/help_sheet/help_sheet_form.dart
Normal file
67
lib/widgets/help_sheet/help_sheet_form.dart
Normal file
@ -0,0 +1,67 @@
|
||||
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';
|
||||
import 'package:quid_faciam_hodie/widgets/modal_sheet.dart';
|
||||
|
||||
class HelpSheetForm extends StatefulWidget {
|
||||
final String title;
|
||||
final Widget helpContent;
|
||||
|
||||
const HelpSheetForm({
|
||||
Key? key,
|
||||
required this.helpContent,
|
||||
required this.title,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<HelpSheetForm> createState() => _HelpSheetFormState();
|
||||
}
|
||||
|
||||
class _HelpSheetFormState extends State<HelpSheetForm> {
|
||||
bool dontShowSheetAgain = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final localizations = AppLocalizations.of(context)!;
|
||||
|
||||
return ModalSheet(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
widget.title,
|
||||
textAlign: TextAlign.center,
|
||||
style: getTitleTextStyle(context),
|
||||
),
|
||||
const SizedBox(height: MEDIUM_SPACE),
|
||||
widget.helpContent,
|
||||
const SizedBox(height: LARGE_SPACE),
|
||||
PlatformElevatedButton(
|
||||
child: Text(localizations.generalUnderstoodButtonLabel),
|
||||
onPressed: () => Navigator.pop(context, dontShowSheetAgain),
|
||||
),
|
||||
const SizedBox(height: MEDIUM_SPACE),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
PlatformSwitch(
|
||||
value: dontShowSheetAgain,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
dontShowSheetAgain = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(width: SMALL_SPACE),
|
||||
Text(
|
||||
localizations.helpSheetDontShowAgain,
|
||||
style: getBodyTextTextStyle(context),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.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 ModalSheet extends StatelessWidget {
|
||||
final Widget child;
|
||||
@ -12,30 +14,37 @@ class ModalSheet extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Wrap(
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
child: Material(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(LARGE_SPACE),
|
||||
topRight: Radius.circular(LARGE_SPACE),
|
||||
),
|
||||
color: platformThemeData(
|
||||
context,
|
||||
material: (data) =>
|
||||
data.bottomSheetTheme.modalBackgroundColor ??
|
||||
data.bottomAppBarColor,
|
||||
cupertino: (data) => data.barBackgroundColor,
|
||||
final innerChild = Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: MEDIUM_SPACE),
|
||||
child: child,
|
||||
);
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
PlatformWidget(
|
||||
material: (_, __) => Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(LARGE_SPACE),
|
||||
topRight: Radius.circular(LARGE_SPACE),
|
||||
),
|
||||
color: getSheetColor(context),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: MEDIUM_SPACE),
|
||||
child: innerChild,
|
||||
),
|
||||
cupertino: (_, __) => CupertinoPopupSurface(
|
||||
isSurfacePainted: false,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(MEDIUM_SPACE),
|
||||
child: child,
|
||||
color: Colors.white,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: LARGE_SPACE),
|
||||
child: innerChild,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user