From 0b0c2ce15fd57d3d5ebb77b8ebca4133ead5836f Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Wed, 17 Aug 2022 19:57:55 +0200 Subject: [PATCH] made app cupertino compatible; improvements; bugfixes --- ios/Flutter/Debug.xcconfig | 1 + ios/Flutter/Release.xcconfig | 1 + ios/Runner.xcodeproj/project.pbxproj | 77 ++++- .../contents.xcworkspacedata | 3 + lib/constants/themes.dart | 14 +- lib/main.dart | 16 +- lib/screens/calendar_screen.dart | 21 +- .../calendar_screen/calendar_month.dart | 28 +- .../calendar_screen/days_of_week_strip.dart | 30 +- lib/screens/grant_permission_screen.dart | 21 +- .../permissions_required_page.dart | 77 +++-- lib/screens/login_screen.dart | 63 +++- lib/screens/main_screen.dart | 315 ++++++++++-------- .../main_screen/change_camera_button.dart | 11 +- lib/screens/main_screen/record_button.dart | 120 ++++--- .../main_screen/today_photo_button.dart | 40 +-- lib/screens/server_loading_screen.dart | 24 +- lib/screens/timeline_screen.dart | 10 +- lib/screens/timeline_screen/memory_view.dart | 24 +- .../timeline_screen/timeline_overlay.dart | 39 ++- lib/screens/welcome_screen.dart | 88 +++-- lib/widgets/icon_button_child.dart | 30 ++ pubspec.lock | 7 + pubspec.yaml | 1 + 24 files changed, 696 insertions(+), 365 deletions(-) create mode 100644 lib/widgets/icon_button_child.dart diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index 592ceee..ec97fc6 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index 592ceee..c4855bf 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index dfc5090..6ae67da 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -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 = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 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 = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 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 = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 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 = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 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 = ""; }; + 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 = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -72,6 +89,8 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, + 928A05FA6D1D23BECAA39FAE /* Pods */, + A5CF9B62B23D4F71C0785FAA /* Frameworks */, ); sourceTree = ""; }; @@ -98,6 +117,14 @@ path = Runner; sourceTree = ""; }; + A5CF9B62B23D4F71C0785FAA /* Frameworks */ = { + isa = PBXGroup; + children = ( + D38127B4F5788EF3381B2B4C /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; /* 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 = ( diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata index 1d526a1..21a3cc1 100644 --- a/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/lib/constants/themes.dart b/lib/constants/themes.dart index c0c5fab..6f6c94b 100644 --- a/lib/constants/themes.dart +++ b/lib/constants/themes.dart @@ -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, + ), + ), +); diff --git a/lib/main.dart b/lib/main.dart index 426bec8..1ee19f2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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 { 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: { diff --git a/lib/screens/calendar_screen.dart b/lib/screens/calendar_screen.dart index ba66911..0b675e8 100644 --- a/lib/screens/calendar_screen.dart +++ b/lib/screens/calendar_screen.dart @@ -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(); - final theme = Theme.of(context); final calendarManager = CalendarManager(memories: memoriesManager.memories); final monthMapping = calendarManager.getMappingForList(); return Consumer( - 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(), ), diff --git a/lib/screens/calendar_screen/calendar_month.dart b/lib/screens/calendar_screen/calendar_month.dart index 3de192c..5a8608f 100644 --- a/lib/screens/calendar_screen/calendar_month.dart +++ b/lib/screens/calendar_screen/calendar_month.dart @@ -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, + ), ), ); } diff --git a/lib/screens/calendar_screen/days_of_week_strip.dart b/lib/screens/calendar_screen/days_of_week_strip.dart index c8b38e2..d000edf 100644 --- a/lib/screens/calendar_screen/days_of_week_strip.dart +++ b/lib/screens/calendar_screen/days_of_week_strip.dart @@ -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, ), ), ), diff --git a/lib/screens/grant_permission_screen.dart b/lib/screens/grant_permission_screen.dart index 6dadc94..e681f22 100644 --- a/lib/screens/grant_permission_screen.dart +++ b/lib/screens/grant_permission_screen.dart @@ -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); + }, + ), ), ), ); diff --git a/lib/screens/grant_permission_screen/permissions_required_page.dart b/lib/screens/grant_permission_screen/permissions_required_page.dart index b2253d2..6fc5a11 100644 --- a/lib/screens/grant_permission_screen/permissions_required_page.dart +++ b/lib/screens/grant_permission_screen/permissions_required_page.dart @@ -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 { children: [ 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: [ - 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: [ + 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: [ - 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: [ + Text( + localizations + .permissionsRequiredPageGrantMicrophonePermission, + ), + if (hasGrantedMicrophonePermission) + Icon(context.platformIcons.checkMark), + if (!hasGrantedMicrophonePermission) const SizedBox(), + ], + ), ), ), ], diff --git a/lib/screens/login_screen.dart b/lib/screens/login_screen.dart index df5103d..b50d730 100644 --- a/lib/screens/login_screen.dart +++ b/lib/screens/login_screen.dart @@ -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 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 with Loadable { children: [ 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), + ), + ), ], ), ), diff --git a/lib/screens/main_screen.dart b/lib/screens/main_screen.dart index 40b560c..08c23b1 100644 --- a/lib/screens/main_screen.dart +++ b/lib/screens/main_screen.dart @@ -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 with Loadable { void initState() { super.initState(); - onNewCameraSelected(GlobalValuesManager.cameras[0]); + loadCameras(); } @override @@ -77,6 +78,12 @@ class _MainScreenState extends AuthRequiredState with Loadable { _updateCamera(state); } + Future 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 with Loadable { }); await controller!.initialize(); + await controller!.prepareForVideoRecording(); await determineZoomLevels(); @@ -165,31 +173,34 @@ class _MainScreenState extends AuthRequiredState 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 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 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: [ - 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 with Loadable { ), ), child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ 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: [ - 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 with Loadable { ], ), ), - expandableContent: Padding( - padding: const EdgeInsets.only( - left: LARGE_SPACE, - right: LARGE_SPACE, - bottom: MEDIUM_SPACE, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - ElevatedButton.icon( - icon: const Icon(Icons.flashlight_on_rounded), - label: Text(AppLocalizations.of(context)! - .mainScreenActionsTorchButton), - style: ButtonStyle( - backgroundColor: MaterialStateProperty.resolveWith( - (_) => isTorchEnabled ? Colors.white : Colors.black, - ), - foregroundColor: MaterialStateProperty.resolveWith( - (_) => 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: [ + ElevatedButton.icon( + icon: const Icon(Icons.flashlight_on_rounded), + label: Text(AppLocalizations.of(context)! + .mainScreenActionsTorchButton), + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.resolveWith( + (_) => isTorchEnabled ? Colors.white : Colors.black, + ), + foregroundColor: + MaterialStateProperty.resolveWith( + (_) => 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( + (_) => Colors.white10, + ), + foregroundColor: + MaterialStateProperty.resolveWith( + (_) => 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( - (_) => Colors.white10, - ), - foregroundColor: MaterialStateProperty.resolveWith( - (_) => 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), - ), - ), - ], + ], + ), ), ), ), diff --git a/lib/screens/main_screen/change_camera_button.dart b/lib/screens/main_screen/change_camera_button.dart index f2513b0..92e194a 100644 --- a/lib/screens/main_screen/change_camera_button.dart +++ b/lib/screens/main_screen/change_camera_button.dart @@ -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, ), ], diff --git a/lib/screens/main_screen/record_button.dart b/lib/screens/main_screen/record_button.dart index 74ba664..297a432 100644 --- a/lib/screens/main_screen/record_button.dart +++ b/lib/screens/main_screen/record_button.dart @@ -28,6 +28,17 @@ class _RecordButtonState extends State { 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 { } setState(() { + animateToVideoIcon = false; videoInAnimationActive = true; }); @@ -96,69 +108,69 @@ class _RecordButtonState extends State { }); }, // 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: [ - 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), + ), ), ), ], diff --git a/lib/screens/main_screen/today_photo_button.dart b/lib/screens/main_screen/today_photo_button.dart index a112d52..617705d 100644 --- a/lib/screens/main_screen/today_photo_button.dart +++ b/lib/screens/main_screen/today_photo_button.dart @@ -70,7 +70,7 @@ class _TodayPhotoButtonState extends State { @override Widget build(BuildContext context) { - return InkWell( + return GestureDetector( onTap: () async { widget.onLeave(); @@ -85,25 +85,27 @@ class _TodayPhotoButtonState extends State { 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!, - ), ), ), ); diff --git a/lib/screens/server_loading_screen.dart b/lib/screens/server_loading_screen.dart index 0ea0c79..d490cef 100644 --- a/lib/screens/server_loading_screen.dart +++ b/lib/screens/server_loading_screen.dart @@ -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 { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - 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 { 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, + ), + ), ], ), ), diff --git a/lib/screens/timeline_screen.dart b/lib/screens/timeline_screen.dart index d352e76..3d53d8e 100644 --- a/lib/screens/timeline_screen.dart +++ b/lib/screens/timeline_screen.dart @@ -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 with Loadable { return true; }, - child: Scaffold( + child: PlatformScaffold( + appBar: isCupertino(context) + ? PlatformAppBar( + title: Text('Timeline'), + ) + : null, body: ChangeNotifierProvider.value( value: timeline, child: PageView.builder( diff --git a/lib/screens/timeline_screen/memory_view.dart b/lib/screens/timeline_screen/memory_view.dart index 8fbd682..22844d7 100644 --- a/lib/screens/timeline_screen/memory_view.dart +++ b/lib/screens/timeline_screen/memory_view.dart @@ -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 { @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 { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - 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: diff --git a/lib/screens/timeline_screen/timeline_overlay.dart b/lib/screens/timeline_screen/timeline_overlay.dart index f553a12..4489bce 100644 --- a/lib/screens/timeline_screen/timeline_overlay.dart +++ b/lib/screens/timeline_screen/timeline_overlay.dart @@ -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(); return Stack( children: [ 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, + ), + ), ) ], ), diff --git a/lib/screens/welcome_screen.dart b/lib/screens/welcome_screen.dart index 42a8a1c..784b25b 100644 --- a/lib/screens/welcome_screen.dart +++ b/lib/screens/welcome_screen.dart @@ -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: [ - 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: [ + 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, + ); + }, + ) + ], + ), ), ), ); diff --git a/lib/widgets/icon_button_child.dart b/lib/widgets/icon_button_child.dart new file mode 100644 index 0000000..923bdcd --- /dev/null +++ b/lib/widgets/icon_button_child.dart @@ -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: [ + icon, + SizedBox(width: gap), + Flexible(child: label), + ], + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index f23a00a..3228c27 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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: diff --git a/pubspec.yaml b/pubspec.yaml index 777c8ca..ac36d10 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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: