diff --git a/README.md b/README.md index 9bc50b90613d3ec0d8259a04e0078d05e4ff3cd3..88785c5c8a5cdbf5fedbf3be0d7f46fa551e0b2e 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ This repository requires [Flutter](https://flutter.dev/docs/get-started/install) Clone the project and enter the project folder. ```sh -git clone https://github.com/anfeichtinger/flutter_production_boilerplate.git -cd flutter_production_boilerplate +git clone https://github.com/anfeichtinger/timebox.git +cd timebox ``` You can remove the screenshots located in [assets/img/](./assets/img). diff --git a/android/app/build.gradle b/android/app/build.gradle index e250f3245849cdd07374ed6a416bb0e316ab31a4..f377acdc7f8f30a56eb9c32c1a0c28fbd6ecb5e5 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -34,7 +34,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "dev.feichtinger.flutterproductionboilerplate.flutter_production_boilerplate" + applicationId "dev.feichtinger.flutterproductionboilerplate.timebox" minSdkVersion 23 targetSdkVersion 30 versionCode flutterVersionCode.toInteger() diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index f291f2eab5b114cfe4826a4f048cc71ac30fcaba..417f493f4fdb9873ec42ccde07fed1576fdb21fc 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,5 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="dev.feichtinger.flutterproductionboilerplate.flutter_production_boilerplate"> + package="dev.feichtinger.flutterproductionboilerplate.timebox"> <!-- Flutter needs it to communicate with the running application to allow setting breakpoints, to provide hot reload, etc. --> diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 3a2dbbbd0d53dda401842a068383beb7dd523082..041e836a16cdfc29077b065f911d06ca0b449d72 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="dev.feichtinger.flutterproductionboilerplate.flutter_production_boilerplate"> + package="dev.feichtinger.flutterproductionboilerplate.timebox"> <queries> <intent> <action android:name="android.intent.action.VIEW" /> @@ -7,7 +7,7 @@ </intent> </queries> <application - android:label="flutter_production_boilerplate" + android:label="timebox" android:icon="@mipmap/ic_launcher"> <activity android:name=".MainActivity" diff --git a/android/app/src/main/kotlin/dev/feichtinger/flutterproductionboilerplate/flutter_production_boilerplate/MainActivity.kt b/android/app/src/main/kotlin/dev/feichtinger/flutterproductionboilerplate/flutter_production_boilerplate/MainActivity.kt index a54c301d84e3f95999fd425cc71a6345d2644158..dbbce1a43bc5cacd2c24531cb04db1cfe66955ae 100644 --- a/android/app/src/main/kotlin/dev/feichtinger/flutterproductionboilerplate/flutter_production_boilerplate/MainActivity.kt +++ b/android/app/src/main/kotlin/dev/feichtinger/flutterproductionboilerplate/flutter_production_boilerplate/MainActivity.kt @@ -1,4 +1,4 @@ -package dev.feichtinger.flutterproductionboilerplate.flutter_production_boilerplate +package dev.feichtinger.flutterproductionboilerplate.timebox import io.flutter.embedding.android.FlutterActivity diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml index f291f2eab5b114cfe4826a4f048cc71ac30fcaba..417f493f4fdb9873ec42ccde07fed1576fdb21fc 100644 --- a/android/app/src/profile/AndroidManifest.xml +++ b/android/app/src/profile/AndroidManifest.xml @@ -1,5 +1,5 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="dev.feichtinger.flutterproductionboilerplate.flutter_production_boilerplate"> + package="dev.feichtinger.flutterproductionboilerplate.timebox"> <!-- Flutter needs it to communicate with the running application to allow setting breakpoints, to provide hot reload, etc. --> diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 952b2ea0c49dc734b16ee59fe40403a4d2e2f4fb..f5c75115c7a762b9da6591c45dbcac47acced3e6 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -11,7 +11,7 @@ <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleName</key> - <string>flutter_production_boilerplate</string> + <string>timebox</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> diff --git a/lib/cubit/bottom_nav_cubit.dart b/lib/cubit/bottom_nav_cubit.dart deleted file mode 100644 index ee612164e5ad113f9396c30c71f8ec4c63ca94fe..0000000000000000000000000000000000000000 --- a/lib/cubit/bottom_nav_cubit.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:bloc/bloc.dart'; - -class BottomNavCubit extends Cubit<int> { - BottomNavCubit() : super(0); - - void updateIndex(int index) => emit(index); - - void getFirstScreen() => emit(0); - - void getSecondScreen() => emit(1); -} diff --git a/lib/cubit/date_time_cubit.dart b/lib/cubit/date_time_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..120a174a282554ddb9da754db2eb3716edd1558a --- /dev/null +++ b/lib/cubit/date_time_cubit.dart @@ -0,0 +1,7 @@ +import 'package:bloc/bloc.dart'; + +class DateTimeCubit extends Cubit<DateTime> { + DateTimeCubit() : super(DateTime.now()); + + void selectDateTime(DateTime dateTime) => emit(dateTime); +} diff --git a/lib/cubit/theme_cubit.dart b/lib/cubit/theme_cubit.dart index a131f7ff660a8ec4c89ba8f058499e71c65b88e8..1ef946c4e4ec6ebd5345d35f954afd3dc54e13f8 100644 --- a/lib/cubit/theme_cubit.dart +++ b/lib/cubit/theme_cubit.dart @@ -1,7 +1,7 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_production_boilerplate/config/theme.dart'; +import 'package:timebox/config/theme.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; part 'theme_state.dart'; diff --git a/lib/main.dart b/lib/main.dart index 52faed4c45f7f5e2b62854cbbbcafeb1f155186c..532abbc6998ef81250e3b7ed7e90f44e07df2db3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,8 +4,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart'; -import 'package:flutter_production_boilerplate/cubit/theme_cubit.dart'; -import 'package:flutter_production_boilerplate/ui/screens/skeleton_screen.dart'; +import 'package:timebox/cubit/theme_cubit.dart'; +import 'package:timebox/ui/screens/skeleton_screen.dart'; import 'package:hive/hive.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:path_provider/path_provider.dart'; diff --git a/lib/ui/screens/first_screen.dart b/lib/ui/screens/first_screen.dart deleted file mode 100644 index a917732c62ca7455c7cfe913cbb83f54a7191964..0000000000000000000000000000000000000000 --- a/lib/ui/screens/first_screen.dart +++ /dev/null @@ -1,145 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_production_boilerplate/config/theme.dart'; -import 'package:flutter_production_boilerplate/cubit/theme_cubit.dart'; -import 'package:flutter_production_boilerplate/ui/widgets/header.dart'; -import 'package:flutter_production_boilerplate/ui/widgets/first_screen/info_card.dart'; -import 'package:ionicons/ionicons.dart'; - -class FirstScreen extends StatelessWidget { - const FirstScreen({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Material( - color: Theme.of(context).backgroundColor, - child: ListView( - padding: const EdgeInsets.symmetric(horizontal: 16), - physics: const BouncingScrollPhysics(), - children: [ - const Header(text: 'app_name'), - - Card( - elevation: 2, - color: Theme.of(context).cardColor, - - /// Example: Getting border radius circular as const - /// Nested Widgets do not need to be declared as const. - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(8))), - child: SwitchListTile( - onChanged: (bool newValue) { - /// Example: Change theme with Cubit - BlocProvider.of<ThemeCubit>(context).getTheme(ThemeState( - newValue ? AppThemes.darkTheme : AppThemes.lightTheme)); - }, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(8))), - value: Theme.of(context).brightness == Brightness.dark, - title: Row( - children: [ - /// Examle: Ionicons - /// Available icons -> https://ionic.io/ionicons - Icon(Ionicons.moon_outline, - color: Theme.of(context).primaryColor), - const SizedBox(width: 16), - Text( - /// Example: Use the easy_translations package - tr('dark_mode_title'), - style: Theme.of(context) - .textTheme - .subtitle1! - .apply(fontWeightDelta: 2), - ), - ], - ), - ), - ), - - /// Example: Good way to add space between items - const SizedBox(height: 8), - Card( - elevation: 2, - - /// Example: Many items have their own colors inside of the ThemData - /// You can overwrite them in [config/theme.dart]. - color: Theme.of(context).cardColor, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(8))), - child: SwitchListTile( - onChanged: (bool newValue) { - /// Example: Change locale - /// The initial locale is automatically determined by the library. - /// Changing the locale like this will persist the selected locale. - context.setLocale( - newValue ? const Locale('de') : const Locale('en')); - }, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(8))), - value: context.locale == const Locale('de'), - title: Row( - children: [ - Icon(Ionicons.text_outline, - color: Theme.of(context).primaryColor), - const SizedBox(width: 16), - Text( - tr('language_switch_title'), - style: Theme.of(context) - .textTheme - .subtitle1! - .apply(fontWeightDelta: 2), - ), - ], - ), - ), - ), - const SizedBox(height: 8), - GridView.count( - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - crossAxisCount: 2, - crossAxisSpacing: 8, - mainAxisSpacing: 8, - childAspectRatio: 4 / 5, - children: const [ - /// Example: it is good practice to put widgets in separate files. - /// This way the screen files won't become too large and - /// the code becomes more clear. - InfoCard( - title: 'localization_title', - content: 'localization_content', - icon: Ionicons.text_outline, - isPrimaryColor: true), - InfoCard( - title: 'linting_title', - content: 'linting_content', - icon: Ionicons.options_outline, - isPrimaryColor: false), - InfoCard( - title: 'storage_title', - content: 'storage_content', - icon: Ionicons.folder_outline, - isPrimaryColor: false), - InfoCard( - title: 'dark_mode_title', - content: 'dark_mode_content', - icon: Ionicons.moon_outline, - isPrimaryColor: true), - InfoCard( - title: 'state_title', - content: 'state_content', - icon: Ionicons.notifications_outline, - isPrimaryColor: true), - InfoCard( - title: 'display_title', - content: 'display_content', - icon: Ionicons.speedometer_outline, - isPrimaryColor: false), - ], - ), - const SizedBox(height: 36), - ]), - ); - } -} diff --git a/lib/ui/screens/second_screen.dart b/lib/ui/screens/second_screen.dart deleted file mode 100644 index b03cbfefeb89d714f9e8253ff2e89c0c85719676..0000000000000000000000000000000000000000 --- a/lib/ui/screens/second_screen.dart +++ /dev/null @@ -1,127 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_production_boilerplate/ui/widgets/header.dart'; -import 'package:flutter_production_boilerplate/ui/widgets/second_screen/grid_item.dart'; -import 'package:flutter_production_boilerplate/ui/widgets/second_screen/link_card.dart'; -import 'package:flutter_production_boilerplate/ui/widgets/second_screen/text_divider.dart'; -import 'package:ionicons/ionicons.dart'; - -class SecondScreen extends StatelessWidget { - const SecondScreen({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Material( - color: Theme.of(context).backgroundColor, - child: ListView( - padding: const EdgeInsets.symmetric(horizontal: 16), - physics: const BouncingScrollPhysics(), - children: [ - const Header(text: 'bottom_nav_second'), - const LinkCard( - title: 'github_card_title', - icon: Ionicons.logo_github, - url: - 'https://github.com/anfeichtinger/flutter_production_boilerplate'), - const TextDivider(text: 'author_divider_title'), - const LinkCard( - title: 'website_card_title', - icon: Ionicons.person_circle_outline, - url: 'https://feichtinger.dev'), - const SizedBox(height: 8), - GridView.count( - physics: const NeverScrollableScrollPhysics(), - crossAxisCount: 2, - childAspectRatio: 2 / 1, - crossAxisSpacing: 8, - mainAxisSpacing: 8, - shrinkWrap: true, - children: const [ - GridItem( - title: 'instagram_card_title', - icon: Ionicons.logo_instagram, - url: 'https://www.instagram.com/anfeichtinger', - ), - GridItem( - title: 'twitter_card_title', - icon: Ionicons.logo_twitter, - url: 'https://twitter.com/_pharrax', - ), - GridItem( - title: 'donate_card_title', - icon: Ionicons.heart_outline, - url: - 'https://www.paypal.com/donate?hosted_button_id=EE3W7PS6AHEP8&source=url', - ), - ], - ), - const TextDivider(text: 'packages_divider_title'), - GridView.count( - physics: const NeverScrollableScrollPhysics(), - crossAxisCount: 2, - childAspectRatio: 2 / 1, - crossAxisSpacing: 8, - mainAxisSpacing: 8, - shrinkWrap: true, - children: const [ - GridItem( - title: 'flutter_bloc', - icon: Ionicons.apps_outline, - url: 'https://pub.dev/packages/flutter_bloc', - ), - GridItem( - title: 'bloc', - icon: Ionicons.grid_outline, - url: 'https://pub.dev/packages/bloc', - ), - GridItem( - title: 'hydrated_bloc', - icon: Ionicons.folder_open_outline, - url: 'https://pub.dev/packages/hydrated_bloc', - ), - GridItem( - title: 'equatable', - icon: Ionicons.git_compare_outline, - url: 'https://pub.dev/packages/equatable', - ), - GridItem( - title: 'lints', - icon: Ionicons.options_outline, - url: 'https://pub.dev/packages/flutter_lints', - ), - GridItem( - title: 'path_provider', - icon: Ionicons.extension_puzzle_outline, - url: 'https://pub.dev/packages/path_provider', - ), - GridItem( - title: 'flutter_displaymode', - icon: Ionicons.speedometer_outline, - url: 'https://pub.dev/packages/flutter_displaymode', - ), - GridItem( - title: 'easy_localization', - icon: Ionicons.text_outline, - url: 'https://pub.dev/packages/easy_localization', - ), - GridItem( - title: 'hive', - icon: Ionicons.folder_outline, - url: 'https://pub.dev/packages/hive', - ), - GridItem( - title: 'url_launcher', - icon: Ionicons.share_outline, - url: 'https://pub.dev/packages/url_launcher', - ), - GridItem( - title: 'ionicons', - icon: Ionicons.logo_ionic, - url: 'https://pub.dev/packages/ionicons', - ), - ], - ), - const SizedBox(height: 36), - ]), - ); - } -} diff --git a/lib/ui/screens/skeleton_screen.dart b/lib/ui/screens/skeleton_screen.dart index bb8ae4b4fa308488becd24fb134f2e6a54e79b0e..83a55b26a0c66c03f40adf1b698905c7ed86b5f3 100644 --- a/lib/ui/screens/skeleton_screen.dart +++ b/lib/ui/screens/skeleton_screen.dart @@ -1,20 +1,12 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_production_boilerplate/cubit/bottom_nav_cubit.dart'; -import 'package:flutter_production_boilerplate/ui/screens/first_screen.dart'; -import 'package:flutter_production_boilerplate/ui/screens/second_screen.dart'; -import 'package:flutter_production_boilerplate/ui/widgets/app_bar_gone.dart'; -import 'package:flutter_production_boilerplate/ui/widgets/bottom_nav_bar.dart'; +import 'package:timebox/cubit/date_time_cubit.dart'; +import 'package:timebox/ui/widgets/app_bar_gone.dart'; class SkeletonScreen extends StatelessWidget { const SkeletonScreen({Key? key}) : super(key: key); - final _pageNavigation = const [ - FirstScreen(), - SecondScreen(), - ]; - @override Widget build(BuildContext context) { return BlocProvider<BottomNavCubit>( @@ -23,14 +15,8 @@ class SkeletonScreen extends StatelessWidget { builder: (BuildContext context, int state) { return Scaffold( appBar: const AppBarGone(), - - /// When switching between tabs this will fade the old - /// layout out and the new layout in. - body: AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: _pageNavigation.elementAt(state)), - /// Cannot be const, tab status will not update. - bottomNavigationBar: BottomNavBar(), + body: ListView.builder(itemBuilder: itemBuilder), + bottomNavigationBar: BottomAppBar(), ); }, ), diff --git a/lib/ui/widgets/bottom_nav_bar.dart b/lib/ui/widgets/bottom_nav_bar.dart index 27d5356c5b7dd88ed3dc725ce6bcfef88fd3d293..efb8075efc9752515618a603c0b1143970e7d7e9 100644 --- a/lib/ui/widgets/bottom_nav_bar.dart +++ b/lib/ui/widgets/bottom_nav_bar.dart @@ -1,19 +1,16 @@ -import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_production_boilerplate/cubit/bottom_nav_cubit.dart'; -import 'package:ionicons/ionicons.dart'; -class BottomNavBar extends StatelessWidget { - /// It is okay not to use a const constructor here. - /// Using const breaks updating of selected BottomNavigationBarItem. - // ignore: prefer_const_constructors_in_immutables - BottomNavBar({Key? key}) : super(key: key); +class BottomApplicationBar extends StatelessWidget { + final List<Widget> leftWidgets; + final List<Widget> rightWidgets; + + const BottomApplicationBar( + {Key? key, required this.leftWidgets, required this.rightWidgets}) + : super(key: key); @override Widget build(BuildContext context) { return Card( - margin: const EdgeInsets.only(top: 2, right: 8, left: 8), elevation: 4, color: Theme.of(context).bottomAppBarColor, shape: const RoundedRectangleBorder( @@ -22,24 +19,19 @@ class BottomNavBar extends StatelessWidget { topRight: Radius.circular(12), ), ), - child: BottomNavigationBar( - currentIndex: context.read<BottomNavCubit>().state, - onTap: (index) => context.read<BottomNavCubit>().updateIndex(index), - type: BottomNavigationBarType.fixed, - elevation: 0, - backgroundColor: Colors.transparent, - selectedItemColor: Theme.of(context).primaryColor, - unselectedItemColor: Theme.of(context).textTheme.bodyText1!.color, - items: [ - BottomNavigationBarItem( - icon: const Icon(Ionicons.home_outline), - label: tr('bottom_nav_first'), - ), - BottomNavigationBarItem( - icon: const Icon(Ionicons.information_circle_outline), - label: tr('bottom_nav_second'), + child: BottomAppBar( + color: Theme.of(context).bottomAppBarColor, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Row(children: leftWidgets), + const Spacer(), + Row(children: rightWidgets), + ], ), - ], + ), ), ); } diff --git a/lib/ui/widgets/header.dart b/lib/ui/widgets/header.dart deleted file mode 100644 index c0bcf1d85dd0ca30c6aaab110f40c63dcb30d45b..0000000000000000000000000000000000000000 --- a/lib/ui/widgets/header.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; - -class Header extends StatelessWidget { - final String text; - - const Header({Key? key, required this.text}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 2.0, vertical: 36), - child: Text( - tr(text), - textAlign: TextAlign.start, - style: - Theme.of(context).textTheme.headline4!.apply(fontFamily: 'Poppins'), - ), - ); - } -} diff --git a/pubspec.yaml b/pubspec.yaml index 7020d5c97c7cec7553e4170dc78ef166d2d3c636..028035ddf360021ec0599acde92938bd22de7fed 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,4 +1,4 @@ -name: flutter_production_boilerplate +name: timebox description: A new Flutter project containing bloc, hive & easy localization. # The following line prevents the package from being accidentally published to