made app cupertino compatible; improvements; bugfixes

This commit is contained in:
Myzel394 2022-08-17 19:57:55 +02:00
parent cee08c080f
commit 0b0c2ce15f
24 changed files with 696 additions and 365 deletions

View File

@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

View File

@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

View File

@ -13,6 +13,7 @@
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
F2449A063F9DD54E63262023 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D38127B4F5788EF3381B2B4C /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@ -32,9 +33,11 @@
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
6B64EB075DA14AE8EEB89C89 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
8B5ED7BEA3FBEF103E66F847 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -42,6 +45,8 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D3486A5CDDD946E1FF3C0592 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
D38127B4F5788EF3381B2B4C /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -49,12 +54,24 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
F2449A063F9DD54E63262023 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
928A05FA6D1D23BECAA39FAE /* Pods */ = {
isa = PBXGroup;
children = (
6B64EB075DA14AE8EEB89C89 /* Pods-Runner.debug.xcconfig */,
8B5ED7BEA3FBEF103E66F847 /* Pods-Runner.release.xcconfig */,
D3486A5CDDD946E1FF3C0592 /* Pods-Runner.profile.xcconfig */,
);
name = Pods;
path = Pods;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
@ -72,6 +89,8 @@
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
928A05FA6D1D23BECAA39FAE /* Pods */,
A5CF9B62B23D4F71C0785FAA /* Frameworks */,
);
sourceTree = "<group>";
};
@ -98,6 +117,14 @@
path = Runner;
sourceTree = "<group>";
};
A5CF9B62B23D4F71C0785FAA /* Frameworks */ = {
isa = PBXGroup;
children = (
D38127B4F5788EF3381B2B4C /* Pods_Runner.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -105,12 +132,14 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
6BF4497D0DD8B6C4E3A61453 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
8C74D292913673DDB09B3D1F /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@ -183,6 +212,45 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
6BF4497D0DD8B6C4E3A61453 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
8C74D292913673DDB09B3D1F /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@ -272,7 +340,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@ -288,6 +356,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 6FJFK7645A;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -349,7 +418,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@ -398,7 +467,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.5;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@ -416,6 +485,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 6FJFK7645A;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -438,6 +508,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 6FJFK7645A;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (

View File

@ -4,4 +4,7 @@
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -1,6 +1,7 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
final LIGHT_THEME = ThemeData(
final LIGHT_THEME_MATERIAL = ThemeData(
textTheme: ThemeData().textTheme.copyWith(
headline1: const TextStyle(
fontSize: 32,
@ -17,7 +18,7 @@ final LIGHT_THEME = ThemeData(
),
);
final DARK_THEME = ThemeData.dark().copyWith(
final DARK_THEME_MATERIAL = ThemeData.dark().copyWith(
textTheme: ThemeData.dark().textTheme.copyWith(
headline1: const TextStyle(
fontSize: 32,
@ -33,3 +34,12 @@ final DARK_THEME = ThemeData.dark().copyWith(
),
),
);
final LIGHT_THEME_CUPERTINO = CupertinoThemeData().copyWith(
textTheme: CupertinoThemeData().textTheme.copyWith(
navLargeTitleTextStyle: const TextStyle(
fontSize: 32,
fontWeight: FontWeight.w500,
),
),
);

View File

@ -1,8 +1,8 @@
import 'package:camera/camera.dart';
import 'package:flutter/foundation.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:provider/provider.dart';
import 'package:quid_faciam_hodie/constants/themes.dart';
import 'package:quid_faciam_hodie/screens/calendar_screen.dart';
@ -24,7 +24,6 @@ void main() async {
DeviceOrientation.portraitDown,
]);
GlobalValuesManager.setCameras(await availableCameras());
GlobalValuesManager.initializeServer();
runApp(const MyApp());
@ -46,11 +45,16 @@ class _MyAppState extends State<MyApp> {
Widget build(BuildContext context) {
return ChangeNotifierProvider.value(
value: memories,
child: MaterialApp(
child: PlatformApp(
title: 'Quid faciam hodie?',
theme: LIGHT_THEME,
darkTheme: DARK_THEME,
themeMode: ThemeMode.system,
material: (_, __) => MaterialAppData(
theme: LIGHT_THEME_MATERIAL,
darkTheme: DARK_THEME_MATERIAL,
themeMode: ThemeMode.system,
),
cupertino: (_, __) => CupertinoAppData(
theme: LIGHT_THEME_CUPERTINO,
),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
routes: {

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:flutter_sticky_header/flutter_sticky_header.dart';
import 'package:provider/provider.dart';
import 'package:quid_faciam_hodie/constants/spacing.dart';
@ -9,7 +10,7 @@ import 'calendar_screen/calendar_month.dart';
import 'calendar_screen/days_of_week_strip.dart';
class CalendarScreen extends StatelessWidget {
static const ID = 'calendar';
static const ID = '/calendar';
const CalendarScreen({
Key? key,
@ -18,21 +19,31 @@ class CalendarScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final memoriesManager = context.read<Memories>();
final theme = Theme.of(context);
final calendarManager = CalendarManager(memories: memoriesManager.memories);
final monthMapping = calendarManager.getMappingForList();
return Consumer<Memories>(
builder: (context, memories, _) => Scaffold(
builder: (context, memories, _) => PlatformScaffold(
appBar: isCupertino(context)
? PlatformAppBar(
title: Text('Calendar'),
)
: null,
body: Padding(
padding: const EdgeInsets.symmetric(vertical: MEDIUM_SPACE),
padding: EdgeInsets.only(
top: isCupertino(context) ? HUGE_SPACE : MEDIUM_SPACE,
),
child: CustomScrollView(
reverse: true,
slivers: [
SliverStickyHeader(
header: Container(
color: theme.canvasColor,
color: platformThemeData(
context,
material: (data) => data.canvasColor,
cupertino: (data) => data.barBackgroundColor,
),
padding: const EdgeInsets.symmetric(vertical: SMALL_SPACE),
child: const DaysOfWeekStrip(),
),

View File

@ -2,6 +2,7 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_calendar_widget/flutter_calendar_widget.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:intl/intl.dart';
import 'package:quid_faciam_hodie/constants/spacing.dart';
import 'package:quid_faciam_hodie/constants/values.dart';
@ -119,8 +120,6 @@ class CalendarMonth extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return FlutterCalendar(
focusedDate: firstDate,
selectionMode: CalendarSelectionMode.single,
@ -157,13 +156,28 @@ class CalendarMonth extends StatelessWidget {
calenderMargin: EdgeInsets.symmetric(vertical: MEDIUM_SPACE),
),
textStyle: CalendarTextStyle(
headerTextStyle: theme.textTheme.subtitle1!,
dayOfWeekTextColor: theme.textTheme.bodyText2!.color!,
dayTextColor: theme.textTheme.bodyText1!.color!,
headerTextStyle: platformThemeData(
context,
material: (data) => data.textTheme.subtitle1!,
cupertino: (data) => data.textTheme.navTitleTextStyle,
),
dayTextColor: platformThemeData(
context,
material: (data) => data.textTheme.bodyText1!.color!,
cupertino: (data) => data.textTheme.textStyle.color!,
),
// Background color
selectedDayTextColor: theme.textTheme.bodyText1!.color!,
selectedDayTextColor: platformThemeData(
context,
material: (data) => data.textTheme.bodyText1!.color!,
cupertino: (data) => data.textTheme.textStyle.color!,
),
// Foreground color
focusedDayTextColor: theme.dialogBackgroundColor,
focusedDayTextColor: platformThemeData(
context,
material: (data) => data.dialogBackgroundColor,
cupertino: (data) => data.barBackgroundColor,
),
),
);
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_calendar_widget/flutter_calendar_widget.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:intl/intl.dart';
class DaysOfWeekStrip extends StatelessWidget {
@ -20,20 +21,23 @@ class DaysOfWeekStrip extends StatelessWidget {
TableRow(
children: List.generate(
7,
(index) => Container(
child: Align(
child: Text(
DateFormat.E().format(
DateTime(1900, 1, 1)
.add(
Duration(days: index),
)
.add(
Duration(
days: 8 - getWeekdayNumber(DayOfWeek.mon),
),
(index) => Align(
child: Text(
DateFormat.E().format(
DateTime(1900, 1, 1)
.add(
Duration(days: index),
)
.add(
Duration(
days: 8 - getWeekdayNumber(DayOfWeek.mon),
),
),
),
),
style: platformThemeData(
context,
material: (data) => data.textTheme.bodyText1!,
cupertino: (data) => data.textTheme.textStyle,
),
),
),

View File

@ -1,11 +1,13 @@
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/main_screen.dart';
import 'grant_permission_screen/permissions_required_page.dart';
class GrantPermissionScreen extends StatelessWidget {
static const ID = 'grant_permission';
static const ID = '/grant_permission';
const GrantPermissionScreen({Key? key}) : super(key: key);
@ -13,15 +15,18 @@ class GrantPermissionScreen extends StatelessWidget {
Widget build(BuildContext context) {
final localizations = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(
return PlatformScaffold(
appBar: PlatformAppBar(
title: Text(localizations.grantPermissionScreenTitle),
),
body: Center(
child: PermissionsRequiredPage(
onPermissionsGranted: () {
Navigator.pushReplacementNamed(context, MainScreen.ID);
},
body: Padding(
padding: const EdgeInsets.all(MEDIUM_SPACE),
child: Center(
child: PermissionsRequiredPage(
onPermissionsGranted: () {
Navigator.pushReplacementNamed(context, MainScreen.ID);
},
),
),
),
);

View File

@ -1,7 +1,9 @@
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:permission_handler/permission_handler.dart';
import 'package:quid_faciam_hodie/constants/spacing.dart';
import 'package:quid_faciam_hodie/widgets/icon_button_child.dart';
class PermissionsRequiredPage extends StatefulWidget {
final VoidCallback onPermissionsGranted;
@ -61,58 +63,77 @@ class _PermissionsRequiredPageState extends State<PermissionsRequiredPage> {
children: <Widget>[
Text(
localizations.permissionsRequiredPageTitle,
style: Theme.of(context).textTheme.headline1,
style: platformThemeData(
context,
material: (data) => data.textTheme.headline1,
cupertino: (data) => data.textTheme.navLargeTitleTextStyle,
),
),
const SizedBox(height: MEDIUM_SPACE),
Text(localizations.permissionsRequiredPageDescription),
Text(
localizations.permissionsRequiredPageDescription,
style: platformThemeData(
context,
material: (data) => data.textTheme.bodyText1,
cupertino: (data) => data.textTheme.textStyle,
),
),
const SizedBox(height: LARGE_SPACE),
if (hasDeniedForever) ...[
Text(localizations.permissionsRequiredPagePermanentlyDenied),
const SizedBox(height: LARGE_SPACE),
TextButton.icon(
PlatformElevatedButton(
onPressed: () => openAppSettings(),
icon: const Icon(Icons.settings),
label: Text(localizations.permissionsRequiredPageOpenSettings),
child: IconButtonChild(
icon: Icon(context.platformIcons.settings),
label: Text(localizations.permissionsRequiredPageOpenSettings),
),
),
] else ...[
TextButton.icon(
PlatformTextButton(
onPressed: hasGrantedCameraPermission
? null
: () async {
await Permission.camera.request();
await checkPermissions();
},
icon: const Icon(Icons.camera_alt),
label: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
localizations.permissionsRequiredPageGrantCameraPermission,
),
if (hasGrantedCameraPermission) const Icon(Icons.check),
if (!hasGrantedCameraPermission) const SizedBox(),
],
child: IconButtonChild(
icon: Icon(context.platformIcons.photoCamera),
label: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
localizations.permissionsRequiredPageGrantCameraPermission,
),
if (hasGrantedCameraPermission)
Icon(context.platformIcons.checkMark),
if (!hasGrantedCameraPermission) const SizedBox(),
],
),
),
),
const SizedBox(height: MEDIUM_SPACE),
TextButton.icon(
PlatformTextButton(
onPressed: hasGrantedMicrophonePermission
? null
: () async {
await Permission.microphone.request();
await checkPermissions();
},
icon: const Icon(Icons.mic),
label: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
localizations
.permissionsRequiredPageGrantMicrophonePermission,
),
if (hasGrantedMicrophonePermission) const Icon(Icons.check),
if (!hasGrantedMicrophonePermission) const SizedBox(),
],
child: IconButtonChild(
icon: Icon(context.platformIcons.mic),
label: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
localizations
.permissionsRequiredPageGrantMicrophonePermission,
),
if (hasGrantedMicrophonePermission)
Icon(context.platformIcons.checkMark),
if (!hasGrantedMicrophonePermission) const SizedBox(),
],
),
),
),
],

View File

@ -1,16 +1,19 @@
import 'package:flutter/cupertino.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/constants/spacing.dart';
import 'package:quid_faciam_hodie/extensions/snackbar.dart';
import 'package:quid_faciam_hodie/managers/authentication_manager.dart';
import 'package:quid_faciam_hodie/screens/server_loading_screen.dart';
import 'package:quid_faciam_hodie/utils/loadable.dart';
import 'package:quid_faciam_hodie/widgets/icon_button_child.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
final supabase = Supabase.instance.client;
class LoginScreen extends StatefulWidget {
static const ID = 'login';
static const ID = '/login';
const LoginScreen({Key? key}) : super(key: key);
@ -85,10 +88,9 @@ class _LoginScreenState extends AuthState<LoginScreen> with Loadable {
@override
Widget build(BuildContext context) {
final localizations = AppLocalizations.of(context)!;
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
return PlatformScaffold(
appBar: PlatformAppBar(
title: Text(localizations.loginScreenTitle),
),
body: Padding(
@ -99,38 +101,63 @@ class _LoginScreenState extends AuthState<LoginScreen> with Loadable {
children: <Widget>[
Text(
localizations.loginScreenTitle,
style: theme.textTheme.headline1,
style: platformThemeData(
context,
material: (data) => data.textTheme.headline1,
cupertino: (data) => data.textTheme.navLargeTitleTextStyle,
),
),
const SizedBox(height: LARGE_SPACE),
Text(localizations.loginScreenHelpText),
Text(
localizations.loginScreenHelpText,
style: platformThemeData(
context,
material: (data) => data.textTheme.bodyText1,
cupertino: (data) => data.textTheme.textStyle,
),
),
const SizedBox(height: MEDIUM_SPACE),
TextField(
PlatformTextField(
controller: emailController,
autofocus: true,
autofillHints: const [AutofillHints.email],
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
decoration: InputDecoration(
labelText: localizations.loginScreenFormEmailLabel,
prefixIcon: const Icon(Icons.email),
material: (_, __) => MaterialTextFieldData(
decoration: InputDecoration(
labelText: localizations.loginScreenFormEmailLabel,
prefixIcon: Icon(context.platformIcons.mail),
),
),
cupertino: (_, __) => CupertinoTextFieldData(
placeholder: localizations.loginScreenFormEmailLabel,
prefix: Icon(context.platformIcons.mail),
),
),
const SizedBox(height: SMALL_SPACE),
TextField(
PlatformTextField(
obscureText: true,
controller: passwordController,
decoration: InputDecoration(
labelText: localizations.loginScreenFormPasswordLabel,
prefixIcon: const Icon(Icons.lock),
material: (_, __) => MaterialTextFieldData(
decoration: InputDecoration(
labelText: localizations.loginScreenFormPasswordLabel,
prefixIcon: Icon(context.platformIcons.padLock),
),
),
cupertino: (_, __) => CupertinoTextFieldData(
placeholder: localizations.loginScreenFormPasswordLabel,
prefix: Icon(context.platformIcons.padLock),
),
onSubmitted: (value) => callWithLoading(signIn),
),
const SizedBox(height: MEDIUM_SPACE),
ElevatedButton.icon(
icon: const Icon(Icons.arrow_right),
label: Text(localizations.loginScreenFormSubmitButton),
PlatformElevatedButton(
onPressed: isLoading ? null : () => callWithLoading(signIn),
)
child: IconButtonChild(
icon: Icon(context.platformIcons.forward),
label: Text(localizations.loginScreenFormSubmitButton),
),
),
],
),
),

View File

@ -5,6 +5,7 @@ import 'package:camera/camera.dart';
import 'package:expandable_bottom_sheet/expandable_bottom_sheet.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/constants/spacing.dart';
import 'package:quid_faciam_hodie/constants/values.dart';
import 'package:quid_faciam_hodie/extensions/snackbar.dart';
@ -23,7 +24,7 @@ import 'main_screen/today_photo_button.dart';
import 'main_screen/uploading_photo.dart';
class MainScreen extends StatefulWidget {
static const ID = 'main';
static const ID = '/main';
const MainScreen({Key? key}) : super(key: key);
@ -63,7 +64,7 @@ class _MainScreenState extends AuthRequiredState<MainScreen> with Loadable {
void initState() {
super.initState();
onNewCameraSelected(GlobalValuesManager.cameras[0]);
loadCameras();
}
@override
@ -77,6 +78,12 @@ class _MainScreenState extends AuthRequiredState<MainScreen> with Loadable {
_updateCamera(state);
}
Future<void> loadCameras() async {
GlobalValuesManager.setCameras(await availableCameras());
onNewCameraSelected(GlobalValuesManager.cameras[0]);
}
void _updateCamera(final AppLifecycleState state) {
final CameraController? cameraController = controller;
@ -125,6 +132,7 @@ class _MainScreenState extends AuthRequiredState<MainScreen> with Loadable {
});
await controller!.initialize();
await controller!.prepareForVideoRecording();
await determineZoomLevels();
@ -165,31 +173,34 @@ class _MainScreenState extends AuthRequiredState<MainScreen> with Loadable {
});
try {
context.showPendingSnackBar(
message: localizations.mainScreenTakePhotoActionTakingPhoto,
);
if (isMaterial(context))
context.showPendingSnackBar(
message: localizations.mainScreenTakePhotoActionTakingPhoto,
);
controller!.setFlashMode(FlashMode.off);
final file = File((await controller!.takePicture()).path);
setState(() {
uploadingPhotoAnimation = file.readAsBytesSync();
});
context.showPendingSnackBar(
message: localizations.mainScreenTakePhotoActionUploadingPhoto,
);
if (isMaterial(context))
context.showPendingSnackBar(
message: localizations.mainScreenTakePhotoActionUploadingPhoto,
);
try {
await FileManager.uploadFile(_user, file);
} catch (error) {
context.showErrorSnackBar(message: error.toString());
if (isMaterial(context))
context.showErrorSnackBar(message: error.toString());
return;
}
context.showSuccessSnackBar(
message: localizations.mainScreenUploadSuccess,
);
if (isMaterial(context))
context.showSuccessSnackBar(
message: localizations.mainScreenUploadSuccess,
);
} finally {
setState(() {
lockCamera = false;
@ -215,28 +226,31 @@ class _MainScreenState extends AuthRequiredState<MainScreen> with Loadable {
});
try {
context.showPendingSnackBar(
message: localizations.mainScreenTakeVideoActionSaveVideo,
);
if (isMaterial(context))
context.showPendingSnackBar(
message: localizations.mainScreenTakeVideoActionSaveVideo,
);
final file = File((await controller!.stopVideoRecording()).path);
context.showPendingSnackBar(
message: localizations.mainScreenTakeVideoActionUploadingVideo,
);
if (isMaterial(context))
context.showPendingSnackBar(
message: localizations.mainScreenTakeVideoActionUploadingVideo,
);
try {
await FileManager.uploadFile(_user, file);
} catch (error) {
if (mounted) {
if (isMaterial(context)) {
context.showErrorSnackBar(message: error.toString());
}
return;
}
context.showSuccessSnackBar(
message: localizations.mainScreenUploadSuccess,
);
if (isMaterial(context))
context.showSuccessSnackBar(
message: localizations.mainScreenUploadSuccess,
);
} finally {
setState(() {
lockCamera = false;
@ -248,18 +262,25 @@ class _MainScreenState extends AuthRequiredState<MainScreen> with Loadable {
Widget build(BuildContext context) {
final localizations = AppLocalizations.of(context)!;
return Scaffold(
return PlatformScaffold(
backgroundColor: Colors.black,
bottomSheet: () {
body: () {
if (isLoading) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
const CircularProgressIndicator(),
PlatformCircularProgressIndicator(),
const SizedBox(height: MEDIUM_SPACE),
Text(localizations.mainScreenLoadingCamera),
Text(
localizations.mainScreenLoadingCamera,
style: platformThemeData(
context,
material: (data) => data.textTheme.bodyText1,
cupertino: (data) => data.textTheme.textStyle,
),
),
],
),
);
@ -304,72 +325,83 @@ class _MainScreenState extends AuthRequiredState<MainScreen> with Loadable {
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
const Padding(
padding: EdgeInsets.symmetric(vertical: MEDIUM_SPACE),
padding: EdgeInsets.symmetric(
vertical: MEDIUM_SPACE,
horizontal: MEDIUM_SPACE,
),
child: SheetIndicator(),
),
Padding(
padding: const EdgeInsets.all(LARGE_SPACE),
padding: const EdgeInsets.symmetric(vertical: SMALL_SPACE),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
FadeAndMoveInAnimation(
translationDuration: DEFAULT_TRANSLATION_DURATION *
SECONDARY_BUTTONS_DURATION_MULTIPLIER,
opacityDuration: DEFAULT_OPACITY_DURATION *
SECONDARY_BUTTONS_DURATION_MULTIPLIER,
child: ChangeCameraButton(
disabled: lockCamera,
onChangeCamera: () {
final currentCameraIndex = GlobalValuesManager
.cameras
.indexOf(controller!.description);
final availableCameras =
GlobalValuesManager.cameras.length;
Expanded(
child: FadeAndMoveInAnimation(
translationDuration: DEFAULT_TRANSLATION_DURATION *
SECONDARY_BUTTONS_DURATION_MULTIPLIER,
opacityDuration: DEFAULT_OPACITY_DURATION *
SECONDARY_BUTTONS_DURATION_MULTIPLIER,
child: ChangeCameraButton(
disabled: lockCamera,
onChangeCamera: () {
final currentCameraIndex = GlobalValuesManager
.cameras
.indexOf(controller!.description);
final availableCameras =
GlobalValuesManager.cameras.length;
onNewCameraSelected(
GlobalValuesManager.cameras[
(currentCameraIndex + 1) %
availableCameras],
);
},
onNewCameraSelected(
GlobalValuesManager.cameras[
(currentCameraIndex + 1) %
availableCameras],
);
},
),
),
),
FadeAndMoveInAnimation(
child: RecordButton(
disabled: lockCamera,
active: isRecording,
onVideoBegin: () async {
setState(() {
isRecording = true;
});
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;
}
if (controller!.value.isRecordingVideo) {
// A recording has already started, do nothing.
return;
}
await controller!.startVideoRecording();
},
onVideoEnd: takeVideo,
onPhotoShot: takePhoto,
await controller!.startVideoRecording();
},
onVideoEnd: takeVideo,
onPhotoShot: takePhoto,
),
),
),
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);
}
},
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);
}
},
),
),
),
],
@ -378,69 +410,76 @@ class _MainScreenState extends AuthRequiredState<MainScreen> with Loadable {
],
),
),
expandableContent: Padding(
padding: const EdgeInsets.only(
left: LARGE_SPACE,
right: LARGE_SPACE,
bottom: MEDIUM_SPACE,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
ElevatedButton.icon(
icon: const Icon(Icons.flashlight_on_rounded),
label: Text(AppLocalizations.of(context)!
.mainScreenActionsTorchButton),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith<Color>(
(_) => isTorchEnabled ? Colors.white : Colors.black,
),
foregroundColor: MaterialStateProperty.resolveWith<Color>(
(_) => isTorchEnabled ? Colors.black : Colors.white,
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.icon(
icon: const Icon(Icons.flashlight_on_rounded),
label: Text(AppLocalizations.of(context)!
.mainScreenActionsTorchButton),
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);
}
});
},
),
onPressed: () {
setState(() {
isTorchEnabled = !isTorchEnabled;
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);
if (isTorchEnabled) {
controller!.setFlashMode(FlashMode.torch);
} else {
controller!.setFlashMode(FlashMode.off);
}
});
},
),
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith<Color>(
(_) => Colors.white10,
),
foregroundColor: MaterialStateProperty.resolveWith<Color>(
(_) => Colors.white,
),
controller!
.setZoomLevel(zoomLevels![newZoomLevelIndex]);
setState(() {
currentZoomLevelIndex = newZoomLevelIndex;
});
},
child: zoomLevels == null
? const Text('1x')
: Text(
formatZoomLevel(currentZoomLevel),
),
),
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

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
class ChangeCameraButton extends StatelessWidget {
final VoidCallback onChangeCamera;
@ -13,9 +14,7 @@ class ChangeCameraButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return InkWell(
enableFeedback: false,
highlightColor: Colors.transparent,
return GestureDetector(
onTap: () {
if (disabled) {
return;
@ -34,9 +33,9 @@ class ChangeCameraButton extends StatelessWidget {
size: 60,
color: Colors.white.withOpacity(.2),
),
const Icon(
Icons.camera_alt,
size: 30,
Icon(
context.platformIcons.switchCamera,
size: 25,
color: Colors.white,
),
],

View File

@ -28,6 +28,17 @@ class _RecordButtonState extends State<RecordButton> {
bool animateToVideoIcon = false;
bool videoInAnimationActive = false;
void cancelAnimation() {
if (videoInAnimationActive || animateToVideoIcon) {
return;
}
setState(() {
videoInAnimationActive = false;
animateToVideoIcon = false;
});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
@ -57,6 +68,7 @@ class _RecordButtonState extends State<RecordButton> {
}
setState(() {
animateToVideoIcon = false;
videoInAnimationActive = true;
});
@ -96,69 +108,69 @@ class _RecordButtonState extends State<RecordButton> {
});
},
// Cancel icon animation
onTapCancel: () {
if (videoInAnimationActive || animateToVideoIcon) {
return;
}
setState(() {
videoInAnimationActive = false;
animateToVideoIcon = false;
});
},
// Cancel icon animation
onPanCancel: () {
if (videoInAnimationActive || animateToVideoIcon) {
return;
}
setState(() {
videoInAnimationActive = false;
animateToVideoIcon = false;
});
},
onTapCancel: cancelAnimation,
onPanCancel: cancelAnimation,
onLongPressCancel: cancelAnimation,
child: Opacity(
opacity: widget.disabled ? 0.5 : 1.0,
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Icon(
Icons.circle,
size: 75,
color: Colors.white.withOpacity(.2),
),
AnimatedScale(
duration: animateToVideoIcon ? kLongPressTimeout : OUT_DURATION,
curve: Curves.easeInOut,
scale: animateToVideoIcon ? (75 / 50) : 1,
child: const Icon(
Icons.circle,
size: 50,
color: Colors.white,
AnimatedContainer(
duration: videoInAnimationActive ? Duration.zero : OUT_DURATION,
width: 60,
height: 60,
decoration: BoxDecoration(
color: videoInAnimationActive
? Colors.white
: Colors.white.withOpacity(.2),
borderRadius: BorderRadius.circular(30),
),
),
AnimatedScale(
duration: () {
if (videoInAnimationActive) {
return Duration(milliseconds: 400);
}
if (animateToVideoIcon) {
return kLongPressTimeout;
}
return OUT_DURATION;
}(),
curve: Curves.easeInOut,
duration: animateToVideoIcon
? const Duration(milliseconds: 180)
: OUT_DURATION,
scale: videoInAnimationActive ? 1 : 0,
child: const Icon(
Icons.circle,
size: 65,
color: Colors.red,
),
),
AnimatedScale(
curve: animateToVideoIcon ? Curves.easeOut : Curves.linear,
duration: animateToVideoIcon
? const Duration(milliseconds: 250)
: OUT_DURATION,
scale: videoInAnimationActive ? 1 : .6,
child: const Icon(
Icons.stop,
size: 45,
color: Colors.white,
scale: () {
if (videoInAnimationActive) {
return .6;
}
if (animateToVideoIcon) {
return 60 / 40;
}
return 1.0;
}(),
child: AnimatedContainer(
duration: () {
if (videoInAnimationActive) {
return Duration(milliseconds: 400);
}
if (animateToVideoIcon) {
return kLongPressTimeout;
}
return OUT_DURATION;
}(),
width: 40,
height: 40,
decoration: BoxDecoration(
color: videoInAnimationActive ? Colors.red : Colors.white,
borderRadius: videoInAnimationActive
? BorderRadius.circular(4)
: BorderRadius.circular(50),
),
),
),
],

View File

@ -70,7 +70,7 @@ class _TodayPhotoButtonState extends State<TodayPhotoButton> {
@override
Widget build(BuildContext context) {
return InkWell(
return GestureDetector(
onTap: () async {
widget.onLeave();
@ -85,25 +85,27 @@ class _TodayPhotoButtonState extends State<TodayPhotoButton> {
widget.onComeBack();
},
child: Container(
width: 45,
height: 45,
decoration: BoxDecoration(
border: Border.all(
color: Colors.white,
width: 2,
child: Align(
child: Container(
width: 45,
height: 45,
decoration: BoxDecoration(
border: Border.all(
color: Colors.white,
width: 2,
),
borderRadius: BorderRadius.circular(SMALL_SPACE),
color: Colors.grey,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(SMALL_SPACE),
child: (data == null || type == null)
? const SizedBox.shrink()
: RawMemoryDisplay(
data: data!,
type: type!,
),
),
borderRadius: BorderRadius.circular(SMALL_SPACE),
color: Colors.grey,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(SMALL_SPACE),
child: (data == null || type == null)
? const SizedBox.shrink()
: RawMemoryDisplay(
data: data!,
type: type!,
),
),
),
);

View File

@ -1,5 +1,6 @@
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/managers/global_values_manager.dart';
@ -11,7 +12,7 @@ import 'server_loading_screen/dot_animation.dart';
import 'welcome_screen.dart';
class ServerLoadingScreen extends StatefulWidget {
static const ID = 'server_loading';
static const ID = '/server_loading';
final String? nextScreen;
@ -63,7 +64,11 @@ class _ServerLoadingScreenState extends State<ServerLoadingScreen> {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
const Icon(Icons.cloud, size: 60),
const Icon(
Icons.cloud,
size: 60,
color: Colors.white,
),
const SizedBox(height: SMALL_SPACE),
const DotAnimation(
initialFadeInDelay: Duration.zero,
@ -87,9 +92,20 @@ class _ServerLoadingScreenState extends State<ServerLoadingScreen> {
fadeOutDelay: Duration.zero,
),
const SizedBox(height: SMALL_SPACE),
const Icon(Icons.smartphone, size: 60),
const Icon(
Icons.phone_android_rounded,
size: 60,
color: Colors.white,
),
const SizedBox(height: LARGE_SPACE),
Text(localizations.serverLoadingScreenDescription),
Text(
localizations.serverLoadingScreenDescription,
style: platformThemeData(
context,
material: (data) => data.textTheme.bodyText1,
cupertino: (data) => data.textTheme.textStyle,
),
),
],
),
),

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:provider/provider.dart';
import 'package:quid_faciam_hodie/extensions/date.dart';
import 'package:quid_faciam_hodie/models/memories.dart';
@ -12,7 +13,7 @@ import 'timeline_screen/timeline_page.dart';
final supabase = Supabase.instance.client;
class TimelineScreen extends StatefulWidget {
static const ID = 'timeline';
static const ID = '/timeline';
final DateTime? date;
@ -92,7 +93,12 @@ class _TimelineScreenState extends State<TimelineScreen> with Loadable {
return true;
},
child: Scaffold(
child: PlatformScaffold(
appBar: isCupertino(context)
? PlatformAppBar(
title: Text('Timeline'),
)
: null,
body: ChangeNotifierProvider.value(
value: timeline,
child: PageView.builder(

View File

@ -3,6 +3,7 @@ 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:quid_faciam_hodie/constants/spacing.dart';
import 'package:quid_faciam_hodie/enums.dart';
import 'package:quid_faciam_hodie/foreign_types/memory.dart';
@ -81,14 +82,19 @@ class _MemoryViewState extends State<MemoryView> {
@override
Widget build(BuildContext context) {
final localizations = AppLocalizations.of(context)!;
final theme = Theme.of(context);
if (status == MemoryFetchStatus.error) {
return Center(
child: Text(
localizations.memoryViewDownloadFailed,
style: theme.textTheme.bodyText2!.copyWith(
color: Colors.white,
style: platformThemeData(
context,
material: (data) => data.textTheme.bodyText2!.copyWith(
color: Colors.white,
),
cupertino: (data) => data.textTheme.textStyle.copyWith(
color: Colors.white,
),
),
),
);
@ -126,15 +132,21 @@ class _MemoryViewState extends State<MemoryView> {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
const CircularProgressIndicator(),
PlatformCircularProgressIndicator(),
const SizedBox(height: SMALL_SPACE),
() {
switch (status) {
case MemoryFetchStatus.downloading:
return Text(
localizations.memoryViewIsDownloading,
style: theme.textTheme.bodyText2!.copyWith(
color: Colors.white,
style: platformThemeData(
context,
material: (data) => data.textTheme.bodyText2!.copyWith(
color: Colors.white,
),
cupertino: (data) => data.textTheme.textStyle.copyWith(
color: Colors.white,
),
),
);
default:

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:quid_faciam_hodie/constants/spacing.dart';
@ -18,14 +19,14 @@ class TimelineOverlay extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final timeline = context.watch<TimelineModel>();
return Stack(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(
top: LARGE_SPACE,
padding: EdgeInsets.only(
// Cupertino needs more space as the top bar is shown to provide a pop button
top: isCupertino(context) ? HUGE_SPACE : LARGE_SPACE,
left: MEDIUM_SPACE,
right: MEDIUM_SPACE,
),
@ -36,9 +37,16 @@ class TimelineOverlay extends StatelessWidget {
child: Text(
DateFormat.yMMMd().format(date),
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headline1!.copyWith(
color: Colors.white,
),
style: platformThemeData(
context,
material: (data) => data.textTheme.headline1!.copyWith(
color: Colors.white,
),
cupertino: (data) =>
data.textTheme.navLargeTitleTextStyle.copyWith(
color: Colors.white,
),
),
),
),
),
@ -59,16 +67,27 @@ class TimelineOverlay extends StatelessWidget {
curve: Curves.linearToEaseOut,
child: Icon(
Icons.public,
size: theme.textTheme.titleSmall!.fontSize,
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: Theme.of(context).textTheme.titleSmall!.copyWith(
color: Colors.white,
),
style: platformThemeData(
context,
material: (data) => data.textTheme.titleSmall!.copyWith(
color: Colors.white,
),
cupertino: (data) =>
data.textTheme.navTitleTextStyle.copyWith(
color: Colors.white,
),
),
)
],
),

View File

@ -1,57 +1,73 @@
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/widgets/icon_button_child.dart';
import 'package:quid_faciam_hodie/widgets/logo.dart';
import 'grant_permission_screen.dart';
class WelcomeScreen extends StatelessWidget {
static const ID = 'welcome';
static const ID = '/';
const WelcomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final localizations = AppLocalizations.of(context)!;
final theme = Theme.of(context);
return Scaffold(
return PlatformScaffold(
body: Padding(
padding: const EdgeInsets.all(MEDIUM_SPACE),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
const Logo(),
const SizedBox(height: LARGE_SPACE),
Text(
localizations.appTitleQuestion,
textAlign: TextAlign.center,
style: theme.textTheme.headline1,
),
const SizedBox(height: SMALL_SPACE),
Text(
localizations.welcomeScreenSubtitle,
style: theme.textTheme.bodySmall,
),
const SizedBox(height: LARGE_SPACE),
Text(
localizations.welcomeScreenDescription,
textAlign: TextAlign.center,
style: theme.textTheme.bodyText2,
),
const SizedBox(height: LARGE_SPACE),
ElevatedButton.icon(
icon: const Icon(Icons.arrow_right),
label: Text(localizations.welcomeScreenStartButtonTitle),
onPressed: () {
Navigator.pushReplacementNamed(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Logo(),
const SizedBox(height: LARGE_SPACE),
Text(
localizations.appTitleQuestion,
textAlign: TextAlign.center,
style: platformThemeData(
context,
GrantPermissionScreen.ID,
);
},
),
],
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.pushReplacementNamed(
context,
GrantPermissionScreen.ID,
);
},
)
],
),
),
),
);

View File

@ -0,0 +1,30 @@
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
class IconButtonChild extends StatelessWidget {
final Widget label;
final Widget icon;
const IconButtonChild({
Key? key,
required this.label,
required this.icon,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final double scale = MediaQuery.maybeOf(context)?.textScaleFactor ?? 1;
final double gap = scale <= 1 ? 8 : lerpDouble(8, 4, min(scale - 1, 1))!;
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
icon,
SizedBox(width: gap),
Flexible(child: label),
],
);
}
}

View File

@ -158,6 +158,13 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_platform_widgets:
dependency: "direct main"
description:
name: flutter_platform_widgets
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
flutter_plugin_android_lifecycle:
dependency: transitive
description:

View File

@ -53,6 +53,7 @@ dependencies:
expandable_bottom_sheet: ^1.1.1+1
flutter_sticky_header: ^0.6.4
flutter_calendar_widget: ^0.0.2
flutter_platform_widgets: ^2.0.0
dev_dependencies:
flutter_test: