diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da03a7f..b5e5d09 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,10 +17,10 @@ jobs: uses: cashapp/activate-hermit@v1 - name: Install Dependencies - run: flutter pub get + run: make get - name: Run Flutter Analyze - run: flutter analyze + run: make analyze - name: Run Flutter Test - run: flutter test + run: make test diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cbe1001 --- /dev/null +++ b/Makefile @@ -0,0 +1,42 @@ +.PHONY: all +all: run + +.PHONY: run +run: + flutter run + +.PHONY: get +get: + flutter pub get + +.PHONY: clean +clean: + flutter clean + +.PHONY: build +build: + flutter build apk + +.PHONY: test +test: + flutter test + +.PHONY: analyze +analyze: + flutter analyze + +.PHONY: generate +generate: + flutter gen-l10n + +.PHONY: help +help: + @echo "Available commands:" + @echo " make run - Run the Flutter app" + @echo " make get - Get packages" + @echo " make clean - Clean the project" + @echo " make build - Build the app for release" + @echo " make test - Run tests" + @echo " make analyze - Analyze the project's Dart code" + @echo " make generate - Generate code" + @echo " make watch - Watch for file changes and generate code automatically" diff --git a/analysis_options.yaml b/analysis_options.yaml index 0bd999b..3610009 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,4 +1,8 @@ include: package:flutter_lints/flutter.yaml +analyzer: + exclude: + - "lib/l10n/**" + linter: rules: diff --git a/bin/.make-4.4.pkg b/bin/.make-4.4.pkg new file mode 120000 index 0000000..383f451 --- /dev/null +++ b/bin/.make-4.4.pkg @@ -0,0 +1 @@ +hermit \ No newline at end of file diff --git a/bin/make b/bin/make new file mode 120000 index 0000000..46c1196 --- /dev/null +++ b/bin/make @@ -0,0 +1 @@ +.make-4.4.pkg \ No newline at end of file diff --git a/l10n.yaml b/l10n.yaml new file mode 100644 index 0000000..9c69156 --- /dev/null +++ b/l10n.yaml @@ -0,0 +1,6 @@ +arb-dir: lib/l10n +template-arb-file: app_en.arb +output-class: Loc +output-localization-file: app_localizations.dart +synthetic-package: false +nullable-getter: false diff --git a/lib/features/app/app.dart b/lib/features/app/app.dart new file mode 100644 index 0000000..3f47cb6 --- /dev/null +++ b/lib/features/app/app.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_starter/features/home/home_page.dart'; +import 'package:flutter_starter/l10n/app_localizations.dart'; + +class App extends StatelessWidget { + const App({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + useMaterial3: true, + ), + home: const HomePage(), + localizationsDelegates: Loc.localizationsDelegates, + supportedLocales: const [ + Locale('en', ''), + ], + ); + } +} diff --git a/lib/features/home/home_page.dart b/lib/features/home/home_page.dart new file mode 100644 index 0000000..8c0bdcf --- /dev/null +++ b/lib/features/home/home_page.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_starter/l10n/app_localizations.dart'; + +class HomePage extends StatefulWidget { + const HomePage({super.key}); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + int _counter = 0; + + void _incrementCounter() { + setState(() { + _counter++; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Text(Loc.of(context).appName), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(Loc.of(context).youHavePushedTheButton), + Text( + '$_counter', + style: Theme.of(context).textTheme.headlineMedium, + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: _incrementCounter, + tooltip: 'Increment', + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb new file mode 100644 index 0000000..b75047b --- /dev/null +++ b/lib/l10n/app_en.arb @@ -0,0 +1,4 @@ +{ + "appName": "Flutter Starter App", + "youHavePushedTheButton": "You have pushed the button this many times:" +} diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart new file mode 100644 index 0000000..52e4320 --- /dev/null +++ b/lib/l10n/app_localizations.dart @@ -0,0 +1,136 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'app_localizations_en.dart'; + +/// Callers can lookup localized strings with an instance of Loc +/// returned by `Loc.of(context)`. +/// +/// Applications need to include `Loc.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'l10n/app_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: Loc.localizationsDelegates, +/// supportedLocales: Loc.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the Loc.supportedLocales +/// property. +abstract class Loc { + Loc(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static Loc of(BuildContext context) { + return Localizations.of(context, Loc)!; + } + + static const LocalizationsDelegate delegate = _LocDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [ + Locale('en') + ]; + + /// No description provided for @appName. + /// + /// In en, this message translates to: + /// **'Flutter Starter App'** + String get appName; + + /// No description provided for @youHavePushedTheButton. + /// + /// In en, this message translates to: + /// **'You have pushed the button this many times:'** + String get youHavePushedTheButton; +} + +class _LocDelegate extends LocalizationsDelegate { + const _LocDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupLoc(locale)); + } + + @override + bool isSupported(Locale locale) => ['en'].contains(locale.languageCode); + + @override + bool shouldReload(_LocDelegate old) => false; +} + +Loc lookupLoc(Locale locale) { + + + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'en': return LocEn(); + } + + throw FlutterError( + 'Loc.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.' + ); +} diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart new file mode 100644 index 0000000..e5e66ac --- /dev/null +++ b/lib/l10n/app_localizations_en.dart @@ -0,0 +1,12 @@ +import 'app_localizations.dart'; + +/// The translations for English (`en`). +class LocEn extends Loc { + LocEn([String locale = 'en']) : super(locale); + + @override + String get appName => 'Flutter Starter App'; + + @override + String get youHavePushedTheButton => 'You have pushed the button this many times:'; +} diff --git a/lib/main.dart b/lib/main.dart index fd25cc4..de76364 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,68 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:flutter_starter/features/app/app.dart'; void main() { - runApp(const MyApp()); -} - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), - useMaterial3: true, - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - title: Text(widget.title), - ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), - ); - } + runApp(const App()); } diff --git a/pubspec.lock b/pubspec.lock index 84b8305..0851f20 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -62,11 +62,24 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + url: "https://pub.dev" + source: hosted + version: "0.18.1" lints: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 5245bfc..d657739 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,6 +9,9 @@ environment: dependencies: flutter: sdk: flutter + flutter_localizations: + sdk: flutter + intl: ^0.18.1 dev_dependencies: flutter_test: diff --git a/test/widget_test.dart b/test/widget_test.dart index bbaf237..c141ed7 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -1,19 +1,11 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - import 'package:flutter/material.dart'; +import 'package:flutter_starter/features/app/app.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:flutter_starter/main.dart'; - void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); + await tester.pumpWidget(const App()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget);