added empty screen; improvements & bugfixes

This commit is contained in:
Myzel394 2022-08-18 00:03:00 +02:00
parent 927b15b63f
commit e8c850f5f0
16 changed files with 379 additions and 222 deletions

File diff suppressed because one or more lines are too long

View File

@ -25,7 +25,7 @@ class Memory {
creationDate: DateTime.parse(jsonData['created_at']),
location: jsonData['location'],
isPublic: jsonData['is_public'],
userID: jsonData['user'],
userID: jsonData['user_id'],
);
}

View File

@ -52,5 +52,11 @@
"memorySheetDownloadMemory": "In Gallerie speichern",
"memorySheetUpdateMemoryMakePublic": "Veröffentlichen",
"memorySheetUpdateMemoryMakePrivate": "Privat machen",
"memorySheetDeleteMemory": "Erinnerung löschen"
"memorySheetDeleteMemory": "Erinnerung löschen",
"emptyScreenTitle": "Houston, wir haben ein Problem",
"emptyScreenSubtitle": "Der Benutzer hat noch keine Erinnerungen erstellt!",
"emptyScreenDescription": "Um deinen Zeitstrahl sehen zu können musst du zuerst ein paar Erinnerungen erstellen! :)",
"emptyScreenCreateMemory": "Erinnerung erstellen"
}

View File

@ -52,5 +52,11 @@
"memorySheetDownloadMemory": "Download to Gallery",
"memorySheetUpdateMemoryMakePublic": "Make Public",
"memorySheetUpdateMemoryMakePrivate": "Make Private",
"memorySheetDeleteMemory": "Delete Memory"
"memorySheetDeleteMemory": "Delete Memory",
"emptyScreenTitle": "Houston, we have a problem",
"emptyScreenSubtitle": "The user hasn't created any memories yet!",
"emptyScreenDescription": "To view your timeline you need to create some memories first! :)",
"emptyScreenCreateMemory": "Create a Memory"
}

View File

@ -15,6 +15,7 @@ import 'package:quid_faciam_hodie/screens/welcome_screen.dart';
import 'managers/global_values_manager.dart';
import 'models/memories.dart';
import 'screens/empty_screen.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
@ -62,6 +63,7 @@ class _MyAppState extends State<MyApp> {
GrantPermissionScreen.ID: (context) => const GrantPermissionScreen(),
CalendarScreen.ID: (context) => const CalendarScreen(),
ServerLoadingScreen.ID: (context) => const ServerLoadingScreen(),
EmptyScreen.ID: (context) => const EmptyScreen(),
},
initialRoute: ServerLoadingScreen.ID,
),

View File

@ -47,7 +47,7 @@ class FileManager {
}
final memoryResponse = await supabase.from('memories').insert({
'user': user.id,
'user_id': user.id,
'location': path,
}).execute();
@ -60,7 +60,7 @@ class FileManager {
final response = await supabase
.from('memories')
.select()
.eq('user', user.id)
.eq('user_id', user.id)
.order('created_at', ascending: false)
.limit(1)
.single()

View File

@ -39,7 +39,6 @@ class Memories extends PropertyChangeNotifier<String> {
}
void removeMemoryByID(final String id) {
print("remooooooved");
_memories.removeWhere((memory) => memory.id == id);
notifyListeners('memories');
}
@ -93,8 +92,9 @@ class Memories extends PropertyChangeNotifier<String> {
final memory = Memory.parse(response.newRecord!);
final id = response.oldRecord!['id'];
removeMemoryByID(id);
addMemory(memory);
_memories.removeWhere((memory) => memory.id == id);
memories.add(memory);
break;
}

View File

@ -0,0 +1,68 @@
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:lottie/lottie.dart';
import 'package:quid_faciam_hodie/constants/spacing.dart';
import 'package:quid_faciam_hodie/screens/main_screen.dart';
import 'package:quid_faciam_hodie/utils/theme.dart';
import '../widgets/icon_button_child.dart';
class EmptyScreen extends StatelessWidget {
static const ID = '/empty';
const EmptyScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final localizations = AppLocalizations.of(context)!;
return PlatformScaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Lottie.asset('assets/lottie/flying-astronaut.json'),
const SizedBox(height: LARGE_SPACE),
Padding(
padding: const EdgeInsets.all(MEDIUM_SPACE),
child: Column(
children: <Widget>[
Text(
localizations.emptyScreenTitle,
textAlign: TextAlign.center,
style: getTitleTextStyle(context),
),
const SizedBox(height: MEDIUM_SPACE),
Text(
localizations.emptyScreenSubtitle,
textAlign: TextAlign.center,
style: getSubTitleTextStyle(context),
),
const SizedBox(height: SMALL_SPACE),
Text(
textAlign: TextAlign.center,
localizations.emptyScreenDescription,
style: getBodyTextTextStyle(context),
),
],
),
),
const SizedBox(height: MEDIUM_SPACE),
PlatformElevatedButton(
child: IconButtonChild(
icon: Icon(context.platformIcons.back),
label: Text(localizations.emptyScreenCreateMemory),
),
onPressed: () {
Navigator.pushNamedAndRemoveUntil(
context, MainScreen.ID, (_) => false);
},
),
],
),
),
);
}
}

View File

@ -4,6 +4,7 @@ import 'dart:typed_data';
import 'package:camera/camera.dart';
import 'package:expandable_bottom_sheet/expandable_bottom_sheet.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:quid_faciam_hodie/constants/spacing.dart';
@ -270,233 +271,244 @@ class _MainScreenState extends AuthRequiredState<MainScreen> with Loadable {
Widget build(BuildContext context) {
final localizations = AppLocalizations.of(context)!;
return PlatformScaffold(
backgroundColor: Colors.black,
body: () {
if (isLoading) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
PlatformCircularProgressIndicator(),
const SizedBox(height: MEDIUM_SPACE),
Text(
localizations.mainScreenLoadingCamera,
style: platformThemeData(
context,
material: (data) => data.textTheme.bodyText1,
cupertino: (data) => data.textTheme.textStyle,
),
),
],
),
);
}
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(
fit: StackFit.expand,
children: <Widget>[
controller!.buildPreview(),
if (isRecording)
RecordingOverlay(controller: controller!),
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),
),
),
return WillPopScope(
onWillPop: () async {
SystemChannels.platform.invokeMethod('SystemNavigator.pop');
SystemNavigator.pop();
exit(0);
},
child: PlatformScaffold(
backgroundColor: Colors.black,
body: () {
if (isLoading) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
const Padding(
padding: EdgeInsets.symmetric(
vertical: MEDIUM_SPACE,
horizontal: MEDIUM_SPACE,
),
child: SheetIndicator(),
),
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);
}
},
),
),
),
],
PlatformCircularProgressIndicator(),
const SizedBox(height: MEDIUM_SPACE),
Text(
localizations.mainScreenLoadingCamera,
style: platformThemeData(
context,
material: (data) => data.textTheme.bodyText1,
cupertino: (data) => data.textTheme.textStyle,
),
),
],
),
),
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),
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(
fit: StackFit.expand,
children: <Widget>[
controller!.buildPreview(),
if (isRecording)
RecordingOverlay(controller: controller!),
if (uploadingPhotoAnimation != null)
UploadingPhoto(
data: uploadingPhotoAnimation!,
),
],
),
),
),
),
ElevatedButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.resolveWith<Color>(
(_) => Colors.white10,
),
foregroundColor:
MaterialStateProperty.resolveWith<Color>(
(_) => Colors.white,
),
),
),
),
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,
),
onPressed: zoomLevels == null
? null
: () {
final newZoomLevelIndex =
((currentZoomLevelIndex + 1) %
zoomLevels!.length);
child: SheetIndicator(),
),
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
? const Text('1x')
: 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
? const Text('1x')
: Text(
formatZoomLevel(currentZoomLevel),
),
),
],
),
),
),
),
),
);
}(),
);
}(),
),
);
}
}

View File

@ -57,6 +57,14 @@ class _TodayPhotoButtonState extends State<TodayPhotoButton> {
final memories = context.read<Memories>();
if (memories.memories.isEmpty) {
setState(() {
data = null;
type = null;
});
return;
}
final lastMemory = memories.memories.first;
final file = await lastMemory.downloadToFile();

View File

@ -7,12 +7,13 @@ import 'package:quid_faciam_hodie/managers/global_values_manager.dart';
import 'package:quid_faciam_hodie/models/memories.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'empty_screen.dart';
import 'main_screen.dart';
import 'server_loading_screen/dot_animation.dart';
import 'welcome_screen.dart';
class ServerLoadingScreen extends StatefulWidget {
static const ID = '/server_loading';
static const ID = '/';
final String? nextScreen;
@ -44,12 +45,29 @@ class _ServerLoadingScreenState extends State<ServerLoadingScreen> {
await memories.initialize();
}
if (widget.nextScreen == null) {
Navigator.pushNamed(
context,
MainScreen.ID,
);
} else {
if (memories.memories.isEmpty) {
Navigator.pushReplacementNamed(
context,
EmptyScreen.ID,
);
} else {
Navigator.pushReplacementNamed(
context,
widget.nextScreen!,
);
}
}
} else {
Navigator.pushReplacementNamed(
context,
widget.nextScreen ?? MainScreen.ID,
WelcomeScreen.ID,
);
} else {
Navigator.pushReplacementNamed(context, WelcomeScreen.ID);
}
}

View File

@ -8,6 +8,7 @@ import 'package:quid_faciam_hodie/utils/loadable.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'calendar_screen.dart';
import 'empty_screen.dart';
import 'timeline_screen/timeline_page.dart';
final supabase = Supabase.instance.client;
@ -49,6 +50,18 @@ class _TimelineScreenState extends State<TimelineScreen> with Loadable {
timeline.setCurrentIndex(initialIndex);
memoriesModel.addListener(() {
if (!mounted) {
return;
}
if (memoriesModel.memories.isEmpty) {
Navigator.pushReplacementNamed(
context,
EmptyScreen.ID,
);
return;
}
timeline.refresh(memoriesModel.memories);
setState(() {});

View File

@ -8,7 +8,7 @@ import 'package:quid_faciam_hodie/widgets/logo.dart';
import 'grant_permission_screen.dart';
class WelcomeScreen extends StatelessWidget {
static const ID = '/';
static const ID = '/welcome';
const WelcomeScreen({Key? key}) : super(key: key);
@ -60,7 +60,7 @@ class WelcomeScreen extends StatelessWidget {
label: Text(localizations.welcomeScreenStartButtonTitle),
),
onPressed: () {
Navigator.pushReplacementNamed(
Navigator.pushNamed(
context,
GrantPermissionScreen.ID,
);

View File

@ -55,6 +55,13 @@ class _FadeAndMoveInAnimationState extends State<FadeAndMoveInAnimation>
);
}
@override
void dispose() {
translationController.dispose();
super.dispose();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();

View File

@ -1,6 +1,13 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
archive:
dependency: transitive
description:
name: archive
url: "https://pub.dartlang.org"
source: hosted
version: "3.3.1"
async:
dependency: transitive
description:
@ -329,6 +336,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
lottie:
dependency: "direct main"
description:
name: lottie
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.1"
matcher:
dependency: transitive
description:

View File

@ -54,6 +54,7 @@ dependencies:
flutter_sticky_header: ^0.6.4
flutter_calendar_widget: ^0.0.2
flutter_platform_widgets: ^2.0.0
lottie: ^1.4.1
dev_dependencies:
flutter_test:
@ -80,6 +81,7 @@ flutter:
assets:
- assets/
- assets/lottie/flying-astronaut.json
# To add assets to your application, add an assets section, like this:
# assets: