mirror of
https://github.com/Myzel394/quid_faciam_hodie.git
synced 2025-06-19 07:35:26 +02:00
improved welcome screen
This commit is contained in:
parent
d17a62db90
commit
0a0e45bac2
1
assets/images/live_photo.svg
Normal file
1
assets/images/live_photo.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 7.3 KiB |
@ -1,3 +1,26 @@
|
|||||||
const SUPABASE_API_URL = 'https://gmqzelvauqziurlloawb.supabase.co';
|
import 'dart:convert';
|
||||||
const SUPABASE_API_KEY =
|
|
||||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImdtcXplbHZhdXF6aXVybGxvYXdiIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NjAzODE5MDcsImV4cCI6MTk3NTk1NzkwN30.D_964EIlD9WRFnG6MWtQtmIg04eMBbZhIEF7zl--bKw';
|
// Encode keys to base64 to avoid simple bots from scraping them
|
||||||
|
final SUPABASE_API_URL = String.fromCharCodes(
|
||||||
|
base64.decode(
|
||||||
|
'aHR0cHM6Ly9nbXF6ZWx' + '2YXVxeml1cmxsb2F3Yi5zdXBhYmFzZS5jbwo=',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final SUPABASE_API_KEY = String.fromCharCodes(
|
||||||
|
base64.decode(
|
||||||
|
'ZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SnBjM01pT2lKemRYQmhZbUZ6' +
|
||||||
|
'WlNJc0luSmxaaUk2SW1kdGNYcGxiSFpoZFhGNmFYVnliR3h2WVhkaUlpd2ljbTlzWlNJNkltRnVi' +
|
||||||
|
'MjRpTENKcFlYUWlPakUyTmpBek9ERTVNRGNzSW1WNGNDSTZNVGszTlRrMU56a3dOMzAuRF85NjRF' +
|
||||||
|
'SWxEOVdSRm5HNk1XdFF0bUlnMDRlTUJiWmhJRUY3emwtLWJLdwo=',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final PEXELS_API_KEY = String.fromCharCodes(
|
||||||
|
base64.decode(
|
||||||
|
'NTYz' +
|
||||||
|
'NDkyYW' +
|
||||||
|
'Q2ZjkxNzAwMDAxM' +
|
||||||
|
'DAwMDAxYzE2ODA' +
|
||||||
|
'0MGU2NjkzNGNlMT' +
|
||||||
|
'kzNjdmZjA5NGU2NDMyM2IK',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
@ -8,3 +8,12 @@ final UnmodifiableSetView<double> DEFAULT_ZOOM_LEVELS =
|
|||||||
UnmodifiableSetView({0.6, 1, 2, 5, 10, 20, 50, 100});
|
UnmodifiableSetView({0.6, 1, 2, 5, 10, 20, 50, 100});
|
||||||
const CALENDAR_DATE_IN_MAX_DELAY = Duration(milliseconds: 500);
|
const CALENDAR_DATE_IN_MAX_DELAY = Duration(milliseconds: 500);
|
||||||
const CACHE_INVALIDATION_DURATION = Duration(days: 7);
|
const CACHE_INVALIDATION_DURATION = Duration(days: 7);
|
||||||
|
const WELCOME_SCREEN_PHOTOS_QUERIES = [
|
||||||
|
'happy',
|
||||||
|
'people',
|
||||||
|
'couple',
|
||||||
|
'family',
|
||||||
|
'fun',
|
||||||
|
'friends',
|
||||||
|
'romantic',
|
||||||
|
];
|
||||||
|
@ -26,4 +26,10 @@ class StatusController extends PropertyChangeNotifier<String> {
|
|||||||
_done = true;
|
_done = true;
|
||||||
notifyListeners('done');
|
notifyListeners('done');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
_done = false;
|
||||||
|
_isForwarding = false;
|
||||||
|
notifyListeners('done');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
"generalError": "Ein Fehler ist aufgetreten",
|
"generalError": "Ein Fehler ist aufgetreten",
|
||||||
"generalCancelButtonLabel": "Abbrechen",
|
"generalCancelButtonLabel": "Abbrechen",
|
||||||
|
"generalContinueButtonLabel": "Weiter",
|
||||||
|
|
||||||
"welcomeScreenDescription": "Finde heraus was du den ganzen Tag gemacht hast und erlebe Erinnerungen wieder, die du komplett vergessen hast!",
|
"welcomeScreenDescription": "Finde heraus was du den ganzen Tag gemacht hast und erlebe Erinnerungen wieder, die du komplett vergessen hast!",
|
||||||
"welcomeScreenSubtitle": "Was hab ich heute gemacht?",
|
"welcomeScreenSubtitle": "Was hab ich heute gemacht?",
|
||||||
|
@ -3,10 +3,14 @@
|
|||||||
|
|
||||||
"generalError": "There was an error",
|
"generalError": "There was an error",
|
||||||
"generalCancelButtonLabel": "Cancel",
|
"generalCancelButtonLabel": "Cancel",
|
||||||
|
"generalContinueButtonLabel": "Continue",
|
||||||
|
|
||||||
"welcomeScreenDescription": "Find out what you did all the days and unlock moments you completely forgot!",
|
"welcomeScreenDescription": "Find out what you did all the days and unlock moments you completely forgot!",
|
||||||
"welcomeScreenSubtitle": "What did I do today?",
|
"welcomeScreenSubtitle": "What did I do today?",
|
||||||
"welcomeScreenStartButtonTitle": "Start",
|
"welcomeScreenStartButtonTitle": "Start",
|
||||||
|
"welcomeScreenCreateMemoriesGuideDescription": "Create photos and video thorough the day...",
|
||||||
|
"welcomeScreenViewMemoriesGuideDescription": "...and relieve your best moments at the end of the day!",
|
||||||
|
"welcomeScreenGetStartedLabel": "Get started",
|
||||||
|
|
||||||
|
|
||||||
"serverLoadingScreenDescription": "We are loading your data",
|
"serverLoadingScreenDescription": "We are loading your data",
|
||||||
|
19
lib/managers/photo_manager.dart
Normal file
19
lib/managers/photo_manager.dart
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
class PhotoManager {
|
||||||
|
static const MAX_PHOTOS_PER_PAGE = 80;
|
||||||
|
|
||||||
|
// Searches for photos based on `query` and returns a random one.
|
||||||
|
static Future<String> getRandomPhoto(final String query) async {
|
||||||
|
final url =
|
||||||
|
'https://api.pexels.com/v1/search?query=$query&per_page=$MAX_PHOTOS_PER_PAGE&orientation=portait';
|
||||||
|
final response = await http.get(Uri.parse(url));
|
||||||
|
final data = jsonDecode(response.body);
|
||||||
|
final photoIndex = Random().nextInt(data['per_page']);
|
||||||
|
|
||||||
|
return data['photos'][photoIndex]['src']['portrait'];
|
||||||
|
}
|
||||||
|
}
|
@ -82,6 +82,7 @@ class _MemorySlideState extends State<MemorySlide>
|
|||||||
return Consumer<TimelineModel>(
|
return Consumer<TimelineModel>(
|
||||||
builder: (___, timeline, ____) => Status(
|
builder: (___, timeline, ____) => Status(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
|
isIndeterminate: controller == null,
|
||||||
paused: timeline.paused,
|
paused: timeline.paused,
|
||||||
hideProgressBar: !timeline.showOverlay,
|
hideProgressBar: !timeline.showOverlay,
|
||||||
child: MemoryView(
|
child: MemoryView(
|
||||||
|
@ -2,71 +2,74 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
|
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
|
||||||
import 'package:quid_faciam_hodie/constants/spacing.dart';
|
import 'package:quid_faciam_hodie/constants/spacing.dart';
|
||||||
import 'package:quid_faciam_hodie/widgets/icon_button_child.dart';
|
|
||||||
import 'package:quid_faciam_hodie/widgets/logo.dart';
|
|
||||||
|
|
||||||
import 'grant_permission_screen.dart';
|
import 'welcome_screen/pages/get_started_page.dart';
|
||||||
|
import 'welcome_screen/pages/guide_page.dart';
|
||||||
|
import 'welcome_screen/pages/initial_page.dart';
|
||||||
|
|
||||||
class WelcomeScreen extends StatelessWidget {
|
class WelcomeScreen extends StatefulWidget {
|
||||||
static const ID = '/welcome';
|
static const ID = '/welcome';
|
||||||
|
|
||||||
const WelcomeScreen({Key? key}) : super(key: key);
|
const WelcomeScreen({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<WelcomeScreen> createState() => _WelcomeScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WelcomeScreenState extends State<WelcomeScreen> {
|
||||||
|
final controller = PageController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
controller.dispose();
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void nextPage() {
|
||||||
|
controller.animateToPage(
|
||||||
|
(controller.page! + 1).toInt(),
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.ease,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final localizations = AppLocalizations.of(context)!;
|
final localizations = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
return PlatformScaffold(
|
return PlatformScaffold(
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: const EdgeInsets.all(MEDIUM_SPACE),
|
padding: const EdgeInsets.symmetric(vertical: MEDIUM_SPACE),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(
|
child: PageView.builder(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
itemBuilder: (context, index) {
|
||||||
children: <Widget>[
|
switch (index) {
|
||||||
const Logo(),
|
case 0:
|
||||||
const SizedBox(height: LARGE_SPACE),
|
return InitialPage(
|
||||||
Text(
|
onNextPage: nextPage,
|
||||||
localizations.appTitleQuestion,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: platformThemeData(
|
|
||||||
context,
|
|
||||||
material: (data) => data.textTheme.headline1,
|
|
||||||
cupertino: (data) => data.textTheme.navLargeTitleTextStyle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: SMALL_SPACE),
|
|
||||||
Text(
|
|
||||||
localizations.welcomeScreenSubtitle,
|
|
||||||
style: platformThemeData(
|
|
||||||
context,
|
|
||||||
material: (data) => data.textTheme.bodySmall,
|
|
||||||
cupertino: (data) => data.textTheme.navTitleTextStyle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: LARGE_SPACE),
|
|
||||||
Text(
|
|
||||||
localizations.welcomeScreenDescription,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: platformThemeData(
|
|
||||||
context,
|
|
||||||
material: (data) => data.textTheme.bodyText1,
|
|
||||||
cupertino: (data) => data.textTheme.textStyle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: LARGE_SPACE),
|
|
||||||
PlatformElevatedButton(
|
|
||||||
child: IconButtonChild(
|
|
||||||
icon: Icon(context.platformIcons.forward),
|
|
||||||
label: Text(localizations.welcomeScreenStartButtonTitle),
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pushNamed(
|
|
||||||
context,
|
|
||||||
GrantPermissionScreen.ID,
|
|
||||||
);
|
);
|
||||||
},
|
case 1:
|
||||||
)
|
return GuidePage(
|
||||||
],
|
onNextPage: nextPage,
|
||||||
|
description: localizations
|
||||||
|
.welcomeScreenCreateMemoriesGuideDescription,
|
||||||
|
picture: 'assets/images/live_photo.svg',
|
||||||
|
);
|
||||||
|
case 2:
|
||||||
|
return GuidePage(
|
||||||
|
onNextPage: nextPage,
|
||||||
|
description:
|
||||||
|
localizations.welcomeScreenViewMemoriesGuideDescription,
|
||||||
|
);
|
||||||
|
case 3:
|
||||||
|
return const GetStartedPage();
|
||||||
|
default:
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
controller: controller,
|
||||||
|
itemCount: 4,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
15
lib/screens/welcome_screen/crabs/logo.dart
Normal file
15
lib/screens/welcome_screen/crabs/logo.dart
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import 'package:coast/coast.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:quid_faciam_hodie/widgets/logo.dart';
|
||||||
|
|
||||||
|
class CrabLogo extends StatelessWidget {
|
||||||
|
const CrabLogo({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Crab(
|
||||||
|
tag: 'logo',
|
||||||
|
child: Logo(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
30
lib/screens/welcome_screen/crabs/next_button.dart
Normal file
30
lib/screens/welcome_screen/crabs/next_button.dart
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import 'package:coast/coast.dart';
|
||||||
|
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/widgets/icon_button_child.dart';
|
||||||
|
|
||||||
|
class CrabNextButton extends StatelessWidget {
|
||||||
|
final VoidCallback onPressed;
|
||||||
|
|
||||||
|
const CrabNextButton({
|
||||||
|
Key? key,
|
||||||
|
required this.onPressed,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final localizations = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
|
return Crab(
|
||||||
|
tag: 'next_button',
|
||||||
|
child: PlatformElevatedButton(
|
||||||
|
onPressed: onPressed,
|
||||||
|
child: IconButtonChild(
|
||||||
|
icon: Icon(context.platformIcons.forward),
|
||||||
|
label: Text(localizations.generalContinueButtonLabel),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
52
lib/screens/welcome_screen/pages/get_started_page.dart
Normal file
52
lib/screens/welcome_screen/pages/get_started_page.dart
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
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/screens/grant_permission_screen.dart';
|
||||||
|
import 'package:quid_faciam_hodie/screens/welcome_screen/crabs/logo.dart';
|
||||||
|
import 'package:quid_faciam_hodie/utils/theme.dart';
|
||||||
|
import 'package:quid_faciam_hodie/widgets/icon_button_child.dart';
|
||||||
|
|
||||||
|
class GetStartedPage extends StatelessWidget {
|
||||||
|
const GetStartedPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final localizations = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: MEDIUM_SPACE),
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
const CrabLogo(),
|
||||||
|
const SizedBox(height: LARGE_SPACE),
|
||||||
|
Text(
|
||||||
|
localizations.appTitleQuestion,
|
||||||
|
style: getTitleTextStyle(context),
|
||||||
|
),
|
||||||
|
const SizedBox(height: MEDIUM_SPACE),
|
||||||
|
Text(
|
||||||
|
localizations.welcomeScreenGetStartedLabel,
|
||||||
|
style: getSubTitleTextStyle(context),
|
||||||
|
),
|
||||||
|
const SizedBox(height: MEDIUM_SPACE),
|
||||||
|
PlatformElevatedButton(
|
||||||
|
child: IconButtonChild(
|
||||||
|
icon: Icon(context.platformIcons.forward),
|
||||||
|
label: Text(localizations.welcomeScreenStartButtonTitle),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pushNamed(
|
||||||
|
context,
|
||||||
|
GrantPermissionScreen.ID,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
48
lib/screens/welcome_screen/pages/guide_page.dart
Normal file
48
lib/screens/welcome_screen/pages/guide_page.dart
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:quid_faciam_hodie/constants/spacing.dart';
|
||||||
|
import 'package:quid_faciam_hodie/screens/welcome_screen/crabs/next_button.dart';
|
||||||
|
import 'package:quid_faciam_hodie/screens/welcome_screen/photo_switching.dart';
|
||||||
|
import 'package:quid_faciam_hodie/utils/theme.dart';
|
||||||
|
|
||||||
|
class GuidePage extends StatelessWidget {
|
||||||
|
final String? picture;
|
||||||
|
final String description;
|
||||||
|
final VoidCallback onNextPage;
|
||||||
|
|
||||||
|
const GuidePage({
|
||||||
|
Key? key,
|
||||||
|
required this.description,
|
||||||
|
required this.onNextPage,
|
||||||
|
this.picture,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: MEDIUM_SPACE),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
picture == null
|
||||||
|
? const Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(top: LARGE_SPACE),
|
||||||
|
child: PhotoSwitching(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: SvgPicture.asset(picture!, height: 400),
|
||||||
|
const SizedBox(height: LARGE_SPACE),
|
||||||
|
Text(
|
||||||
|
description,
|
||||||
|
style: getBodyTextTextStyle(context),
|
||||||
|
),
|
||||||
|
const SizedBox(height: LARGE_SPACE),
|
||||||
|
CrabNextButton(onPressed: onNextPage),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
63
lib/screens/welcome_screen/pages/initial_page.dart
Normal file
63
lib/screens/welcome_screen/pages/initial_page.dart
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
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/screens/welcome_screen/crabs/logo.dart';
|
||||||
|
import 'package:quid_faciam_hodie/screens/welcome_screen/crabs/next_button.dart';
|
||||||
|
import 'package:quid_faciam_hodie/utils/theme.dart';
|
||||||
|
|
||||||
|
class InitialPage extends StatefulWidget {
|
||||||
|
final VoidCallback onNextPage;
|
||||||
|
|
||||||
|
const InitialPage({
|
||||||
|
Key? key,
|
||||||
|
required this.onNextPage,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<InitialPage> createState() => _InitialPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InitialPageState extends State<InitialPage> {
|
||||||
|
late String photoURL;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final localizations = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: MEDIUM_SPACE),
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
const CrabLogo(),
|
||||||
|
const SizedBox(height: HUGE_SPACE),
|
||||||
|
Text(
|
||||||
|
localizations.appTitleQuestion,
|
||||||
|
style: getTitleTextStyle(context),
|
||||||
|
),
|
||||||
|
const SizedBox(height: SMALL_SPACE),
|
||||||
|
Text(
|
||||||
|
localizations.welcomeScreenSubtitle,
|
||||||
|
style: getSubTitleTextStyle(context),
|
||||||
|
),
|
||||||
|
const SizedBox(height: MEDIUM_SPACE),
|
||||||
|
Text(
|
||||||
|
localizations.welcomeScreenDescription,
|
||||||
|
style: getBodyTextTextStyle(context),
|
||||||
|
),
|
||||||
|
const SizedBox(height: LARGE_SPACE),
|
||||||
|
CrabNextButton(
|
||||||
|
onPressed: widget.onNextPage,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
68
lib/screens/welcome_screen/photo_switching.dart
Normal file
68
lib/screens/welcome_screen/photo_switching.dart
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:quid_faciam_hodie/constants/spacing.dart';
|
||||||
|
import 'package:quid_faciam_hodie/constants/values.dart';
|
||||||
|
import 'package:quid_faciam_hodie/managers/photo_manager.dart';
|
||||||
|
import 'package:quid_faciam_hodie/utils/loadable.dart';
|
||||||
|
import 'package:quid_faciam_hodie/widgets/status.dart';
|
||||||
|
|
||||||
|
class PhotoSwitching extends StatefulWidget {
|
||||||
|
const PhotoSwitching({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PhotoSwitching> createState() => _PhotoSwitchingState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PhotoSwitchingState extends State<PhotoSwitching> with Loadable {
|
||||||
|
late String photoURL;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
callWithLoading(getNextPhoto);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> getNextPhoto() async {
|
||||||
|
final query = WELCOME_SCREEN_PHOTOS_QUERIES[
|
||||||
|
Random().nextInt(WELCOME_SCREEN_PHOTOS_QUERIES.length)];
|
||||||
|
final url = await PhotoManager.getRandomPhoto(query);
|
||||||
|
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
photoURL = url;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (isLoading) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
return ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(MEDIUM_SPACE),
|
||||||
|
child: Image.network(
|
||||||
|
photoURL,
|
||||||
|
loadingBuilder: (context, child, loadingProgress) {
|
||||||
|
if (loadingProgress == null) {
|
||||||
|
return Status(
|
||||||
|
autoStart: true,
|
||||||
|
onEnd: () async {
|
||||||
|
getNextPhoto();
|
||||||
|
},
|
||||||
|
duration: const Duration(seconds: 3),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const SizedBox.expand();
|
||||||
|
},
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -9,12 +9,20 @@ class Status extends StatefulWidget {
|
|||||||
final Widget child;
|
final Widget child;
|
||||||
final bool paused;
|
final bool paused;
|
||||||
final bool hideProgressBar;
|
final bool hideProgressBar;
|
||||||
|
final bool autoStart;
|
||||||
|
final bool isIndeterminate;
|
||||||
|
final VoidCallback? onEnd;
|
||||||
|
final Duration duration;
|
||||||
|
|
||||||
const Status({
|
const Status({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.child,
|
required this.child,
|
||||||
this.paused = false,
|
this.paused = false,
|
||||||
this.hideProgressBar = false,
|
this.hideProgressBar = false,
|
||||||
|
this.autoStart = false,
|
||||||
|
this.isIndeterminate = false,
|
||||||
|
this.duration = const Duration(seconds: 5),
|
||||||
|
this.onEnd,
|
||||||
this.controller,
|
this.controller,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@ -23,7 +31,7 @@ class Status extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _StatusState extends State<Status> with TickerProviderStateMixin {
|
class _StatusState extends State<Status> with TickerProviderStateMixin {
|
||||||
late final Animation<double> animation;
|
Animation<double>? animation;
|
||||||
AnimationController? animationController;
|
AnimationController? animationController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -48,28 +56,48 @@ class _StatusState extends State<Status> with TickerProviderStateMixin {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
if (widget.autoStart) {
|
||||||
|
initializeAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void initializeAnimation() {
|
void initializeAnimation() {
|
||||||
animationController = AnimationController(
|
animationController = AnimationController(
|
||||||
duration: widget.controller!.duration,
|
duration: widget.controller?.duration ?? widget.duration,
|
||||||
vsync: this,
|
vsync: this,
|
||||||
);
|
)..addStatusListener((status) {
|
||||||
|
if (status == AnimationStatus.completed) {
|
||||||
|
widget.onEnd?.call();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
animation =
|
animation =
|
||||||
Tween<double>(begin: 0.0, end: 1.0).animate(animationController!)
|
Tween<double>(begin: 0.0, end: 1.0).animate(animationController!)
|
||||||
..addStatusListener((status) {
|
..addStatusListener((status) {
|
||||||
if (status == AnimationStatus.completed) {
|
if (status == AnimationStatus.completed) {
|
||||||
widget.controller!.setDone();
|
widget.controller?.setDone();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
animationController!.forward();
|
animationController!.forward();
|
||||||
|
|
||||||
widget.controller!.addListener(() {
|
if (widget.controller != null) {
|
||||||
if (widget.controller!.isForwarding) {
|
widget.controller!.addListener(() {
|
||||||
animationController!.forward();
|
if (widget.controller!.isForwarding) {
|
||||||
} else {
|
animationController!.forward();
|
||||||
animationController!.stop();
|
} else {
|
||||||
}
|
animationController!.stop();
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.autoStart) {
|
||||||
|
animationController?.forward();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -82,8 +110,8 @@ class _StatusState extends State<Status> with TickerProviderStateMixin {
|
|||||||
widget.child,
|
widget.child,
|
||||||
Positioned(
|
Positioned(
|
||||||
left: 0,
|
left: 0,
|
||||||
|
right: 0,
|
||||||
bottom: SMALL_SPACE,
|
bottom: SMALL_SPACE,
|
||||||
width: MediaQuery.of(context).size.width,
|
|
||||||
height: BAR_HEIGHT,
|
height: BAR_HEIGHT,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: SMALL_SPACE),
|
padding: const EdgeInsets.symmetric(horizontal: SMALL_SPACE),
|
||||||
@ -91,7 +119,7 @@ class _StatusState extends State<Status> with TickerProviderStateMixin {
|
|||||||
duration: const Duration(milliseconds: 500),
|
duration: const Duration(milliseconds: 500),
|
||||||
curve: Curves.linearToEaseOut,
|
curve: Curves.linearToEaseOut,
|
||||||
opacity: widget.hideProgressBar ? 0.0 : 1.0,
|
opacity: widget.hideProgressBar ? 0.0 : 1.0,
|
||||||
child: (widget.controller == null)
|
child: (widget.isIndeterminate || animation == null)
|
||||||
? ClipRRect(
|
? ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(HUGE_SPACE),
|
borderRadius: BorderRadius.circular(HUGE_SPACE),
|
||||||
child: LinearProgressIndicator(
|
child: LinearProgressIndicator(
|
||||||
@ -102,13 +130,13 @@ class _StatusState extends State<Status> with TickerProviderStateMixin {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
: AnimatedBuilder(
|
: AnimatedBuilder(
|
||||||
animation: animation,
|
animation: animation!,
|
||||||
builder: (_, __) => ClipRRect(
|
builder: (_, __) => ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(HUGE_SPACE),
|
borderRadius: BorderRadius.circular(HUGE_SPACE),
|
||||||
child: LinearProgressIndicator(
|
child: LinearProgressIndicator(
|
||||||
value: animation.value,
|
value: animation!.value,
|
||||||
valueColor:
|
valueColor: const AlwaysStoppedAnimation<Color>(
|
||||||
const AlwaysStoppedAnimation<Color>(Colors.white),
|
Colors.white),
|
||||||
backgroundColor: Colors.white.withOpacity(0.1),
|
backgroundColor: Colors.white.withOpacity(0.1),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -78,6 +78,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
|
coast:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: coast
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -295,7 +302,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.15.0"
|
version: "0.15.0"
|
||||||
http:
|
http:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
|
@ -56,6 +56,8 @@ dependencies:
|
|||||||
flutter_platform_widgets: ^2.0.0
|
flutter_platform_widgets: ^2.0.0
|
||||||
lottie: ^1.4.1
|
lottie: ^1.4.1
|
||||||
settings_ui: ^2.0.2
|
settings_ui: ^2.0.2
|
||||||
|
coast: ^2.0.2
|
||||||
|
http: ^0.13.5
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@ -83,6 +85,7 @@ flutter:
|
|||||||
assets:
|
assets:
|
||||||
- assets/
|
- assets/
|
||||||
- assets/lottie/flying-astronaut.json
|
- assets/lottie/flying-astronaut.json
|
||||||
|
- assets/images/
|
||||||
|
|
||||||
# To add assets to your application, add an assets section, like this:
|
# To add assets to your application, add an assets section, like this:
|
||||||
# assets:
|
# assets:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user