diff --git a/Gemfile.lock b/Gemfile.lock index 090bf30..e7a736a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -69,6 +69,7 @@ GEM ethon (0.16.0) ffi (>= 1.15.0) ffi (1.17.0-arm64-darwin) + ffi (1.17.0-x86_64-darwin) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) @@ -102,6 +103,7 @@ GEM PLATFORMS arm64-darwin-22 + x86_64-darwin-21 DEPENDENCIES cocoapods (= 1.15.2) diff --git a/assets/back.png b/assets/back.png deleted file mode 100644 index df42fee..0000000 Binary files a/assets/back.png and /dev/null differ diff --git a/assets/company/heart.svg b/assets/company/heart.svg new file mode 100644 index 0000000..3e26354 --- /dev/null +++ b/assets/company/heart.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/company/info.svg b/assets/company/info.svg new file mode 100644 index 0000000..bd97063 --- /dev/null +++ b/assets/company/info.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/company/radio_button_unchecked.svg b/assets/company/radio_button_unchecked.svg new file mode 100644 index 0000000..45af45a --- /dev/null +++ b/assets/company/radio_button_unchecked.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/company/task_alt.svg b/assets/company/task_alt.svg new file mode 100644 index 0000000..75cbab4 --- /dev/null +++ b/assets/company/task_alt.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/company/unpublished.svg b/assets/company/unpublished.svg new file mode 100644 index 0000000..a91d3ac --- /dev/null +++ b/assets/company/unpublished.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/fonts/Lato/Lato-Bold.ttf b/assets/fonts/Lato/Lato-Bold.ttf new file mode 100644 index 0000000..016068b Binary files /dev/null and b/assets/fonts/Lato/Lato-Bold.ttf differ diff --git a/assets/fonts/Lato/Lato-Regular.ttf b/assets/fonts/Lato/Lato-Regular.ttf new file mode 100644 index 0000000..bb2e887 Binary files /dev/null and b/assets/fonts/Lato/Lato-Regular.ttf differ diff --git a/assets/fonts/Lato/Lato-SemiBold.ttf b/assets/fonts/Lato/Lato-SemiBold.ttf new file mode 100644 index 0000000..3b1bccc Binary files /dev/null and b/assets/fonts/Lato/Lato-SemiBold.ttf differ diff --git a/assets/fonts/Roboto/Roboto-Bold.ttf b/assets/fonts/Roboto/Roboto-Bold.ttf new file mode 100644 index 0000000..43da14d Binary files /dev/null and b/assets/fonts/Roboto/Roboto-Bold.ttf differ diff --git a/assets/fonts/Roboto/Roboto-Regular.ttf b/assets/fonts/Roboto/Roboto-Regular.ttf new file mode 100644 index 0000000..ddf4bfa Binary files /dev/null and b/assets/fonts/Roboto/Roboto-Regular.ttf differ diff --git a/assets/ic_flash_off_white_48dp.png b/assets/ic_flash_off_white_48dp.png deleted file mode 100644 index 93a2626..0000000 Binary files a/assets/ic_flash_off_white_48dp.png and /dev/null differ diff --git a/assets/ic_flash_on_white_48dp.png b/assets/ic_flash_on_white_48dp.png deleted file mode 100644 index c9a3539..0000000 Binary files a/assets/ic_flash_on_white_48dp.png and /dev/null differ diff --git a/assets/ic_heart.png b/assets/ic_heart.png deleted file mode 100644 index 8722eba..0000000 Binary files a/assets/ic_heart.png and /dev/null differ diff --git a/assets/menuPage/diversity.svg b/assets/menuPage/diversity.svg new file mode 100644 index 0000000..ef0b50a --- /dev/null +++ b/assets/menuPage/diversity.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/menuPage/github.svg b/assets/menuPage/github.svg new file mode 100644 index 0000000..a2ce021 --- /dev/null +++ b/assets/menuPage/github.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/menuPage/groups.svg b/assets/menuPage/groups.svg new file mode 100644 index 0000000..8da51f1 --- /dev/null +++ b/assets/menuPage/groups.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/menuPage/handshake.svg b/assets/menuPage/handshake.svg new file mode 100644 index 0000000..4bd1a35 --- /dev/null +++ b/assets/menuPage/handshake.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/menuPage/info.svg b/assets/menuPage/info.svg new file mode 100644 index 0000000..3778156 --- /dev/null +++ b/assets/menuPage/info.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/menuPage/menu.svg b/assets/menuPage/menu.svg new file mode 100644 index 0000000..e89a8f8 --- /dev/null +++ b/assets/menuPage/menu.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/menuPage/rectangle.svg b/assets/menuPage/rectangle.svg new file mode 100644 index 0000000..56a5a79 --- /dev/null +++ b/assets/menuPage/rectangle.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/menuPage/star.svg b/assets/menuPage/star.svg new file mode 100644 index 0000000..b859834 --- /dev/null +++ b/assets/menuPage/star.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/menuPage/thumbs.svg b/assets/menuPage/thumbs.svg new file mode 100644 index 0000000..c214493 --- /dev/null +++ b/assets/menuPage/thumbs.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/navigation/close.svg b/assets/navigation/close.svg new file mode 100644 index 0000000..5c59ac6 --- /dev/null +++ b/assets/navigation/close.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/scan/flashlightOff.svg b/assets/scan/flashlightOff.svg new file mode 100644 index 0000000..4b8de8e --- /dev/null +++ b/assets/scan/flashlightOff.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/scan/flashlightOn.svg b/assets/scan/flashlightOn.svg new file mode 100644 index 0000000..e46c693 --- /dev/null +++ b/assets/scan/flashlightOn.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/scan/showMore.svg b/assets/scan/showMore.svg new file mode 100644 index 0000000..b3f1efa --- /dev/null +++ b/assets/scan/showMore.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/search.svg b/assets/search.svg new file mode 100644 index 0000000..6e6f29d --- /dev/null +++ b/assets/search.svg @@ -0,0 +1,3 @@ + + + diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b5f9a01..214f299 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -184,6 +184,8 @@ PODS: - nanopb/encode (= 2.30910.0) - nanopb/decode (2.30910.0) - nanopb/encode (2.30910.0) + - package_info_plus (0.4.5): + - Flutter - PromisesObjC (2.4.0) - PromisesSwift (2.4.0): - PromisesObjC (= 2.4.0) @@ -208,6 +210,7 @@ DEPENDENCIES: - Flutter (from `Flutter`) - in_app_review (from `.symlinks/plugins/in_app_review/ios`) - mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`) + - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - vibration (from `.symlinks/plugins/vibration/ios`) @@ -260,6 +263,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/in_app_review/ios" mobile_scanner: :path: ".symlinks/plugins/mobile_scanner/ios" + package_info_plus: + :path: ".symlinks/plugins/package_info_plus/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" url_launcher_ios: @@ -301,6 +306,7 @@ SPEC CHECKSUMS: MLKitVision: e858c5f125ecc288e4a31127928301eaba9ae0c1 mobile_scanner: 8564358885a9253c43f822435b70f9345c87224f nanopb: 438bc412db1928dac798aa6fd75726007be04262 + package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851 Reachability: fd0ecd23705e2599e4cceeb943222ae02296cbc6 diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 70693e4..b636303 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import UIKit import Flutter -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart new file mode 100644 index 0000000..2040e35 --- /dev/null +++ b/lib/i18n/strings.g.dart @@ -0,0 +1,253 @@ +/// Generated file. Do not edit. +/// +/// Original: lib/i18n +/// To regenerate, run: `dart run slang` +/// +/// Locales: 1 +/// Strings: 31 +/// +/// Built on 2025-01-12 at 20:22 UTC + +// coverage:ignore-file +// ignore_for_file: type=lint + +import 'package:flutter/widgets.dart'; +import 'package:slang/builder/model/node.dart'; +import 'package:slang_flutter/slang_flutter.dart'; +export 'package:slang_flutter/slang_flutter.dart'; + +const AppLocale _baseLocale = AppLocale.en; + +/// Supported locales, see extension methods below. +/// +/// Usage: +/// - LocaleSettings.setLocale(AppLocale.en) // set locale +/// - Locale locale = AppLocale.en.flutterLocale // get flutter locale from enum +/// - if (LocaleSettings.currentLocale == AppLocale.en) // locale check +enum AppLocale with BaseAppLocale { + en(languageCode: 'en', build: Translations.build); + + const AppLocale({required this.languageCode, this.scriptCode, this.countryCode, required this.build}); // ignore: unused_element + + @override final String languageCode; + @override final String? scriptCode; + @override final String? countryCode; + @override final TranslationBuilder build; + + /// Gets current instance managed by [LocaleSettings]. + Translations get translations => LocaleSettings.instance.translationMap[this]!; +} + +/// Method A: Simple +/// +/// No rebuild after locale change. +/// Translation happens during initialization of the widget (call of t). +/// Configurable via 'translate_var'. +/// +/// Usage: +/// String a = t.someKey.anotherKey; +/// String b = t['someKey.anotherKey']; // Only for edge cases! +Translations get t => LocaleSettings.instance.currentTranslations; + +/// Method B: Advanced +/// +/// All widgets using this method will trigger a rebuild when locale changes. +/// Use this if you have e.g. a settings page where the user can select the locale during runtime. +/// +/// Step 1: +/// wrap your App with +/// TranslationProvider( +/// child: MyApp() +/// ); +/// +/// Step 2: +/// final t = Translations.of(context); // Get t variable. +/// String a = t.someKey.anotherKey; // Use t variable. +/// String b = t['someKey.anotherKey']; // Only for edge cases! +class TranslationProvider extends BaseTranslationProvider { + TranslationProvider({required super.child}) : super(settings: LocaleSettings.instance); + + static InheritedLocaleData of(BuildContext context) => InheritedLocaleData.of(context); +} + +/// Method B shorthand via [BuildContext] extension method. +/// Configurable via 'translate_var'. +/// +/// Usage (e.g. in a widget's build method): +/// context.t.someKey.anotherKey +extension BuildContextTranslationsExtension on BuildContext { + Translations get t => TranslationProvider.of(this).translations; +} + +/// Manages all translation instances and the current locale +class LocaleSettings extends BaseFlutterLocaleSettings { + LocaleSettings._() : super(utils: AppLocaleUtils.instance); + + static final instance = LocaleSettings._(); + + // static aliases (checkout base methods for documentation) + static AppLocale get currentLocale => instance.currentLocale; + static Stream getLocaleStream() => instance.getLocaleStream(); + static AppLocale setLocale(AppLocale locale, {bool? listenToDeviceLocale = false}) => instance.setLocale(locale, listenToDeviceLocale: listenToDeviceLocale); + static AppLocale setLocaleRaw(String rawLocale, {bool? listenToDeviceLocale = false}) => instance.setLocaleRaw(rawLocale, listenToDeviceLocale: listenToDeviceLocale); + static AppLocale useDeviceLocale() => instance.useDeviceLocale(); + @Deprecated('Use [AppLocaleUtils.supportedLocales]') static List get supportedLocales => instance.supportedLocales; + @Deprecated('Use [AppLocaleUtils.supportedLocalesRaw]') static List get supportedLocalesRaw => instance.supportedLocalesRaw; + static void setPluralResolver({String? language, AppLocale? locale, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) => instance.setPluralResolver( + language: language, + locale: locale, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ); +} + +/// Provides utility functions without any side effects. +class AppLocaleUtils extends BaseAppLocaleUtils { + AppLocaleUtils._() : super(baseLocale: _baseLocale, locales: AppLocale.values); + + static final instance = AppLocaleUtils._(); + + // static aliases (checkout base methods for documentation) + static AppLocale parse(String rawLocale) => instance.parse(rawLocale); + static AppLocale parseLocaleParts({required String languageCode, String? scriptCode, String? countryCode}) => instance.parseLocaleParts(languageCode: languageCode, scriptCode: scriptCode, countryCode: countryCode); + static AppLocale findDeviceLocale() => instance.findDeviceLocale(); + static List get supportedLocales => instance.supportedLocales; + static List get supportedLocalesRaw => instance.supportedLocalesRaw; +} + +// translations + +// Path: +class Translations implements BaseTranslations { + /// Returns the current translations of the given [context]. + /// + /// Usage: + /// final t = Translations.of(context); + static Translations of(BuildContext context) => InheritedLocaleData.of(context).translations; + + /// You can call this constructor and build your own translation instance of this locale. + /// Constructing via the enum [AppLocale.build] is preferred. + Translations.build({Map? overrides, PluralResolver? cardinalResolver, PluralResolver? ordinalResolver}) + : assert(overrides == null, 'Set "translation_overrides: true" in order to enable this feature.'), + $meta = TranslationMetadata( + locale: AppLocale.en, + overrides: overrides ?? {}, + cardinalResolver: cardinalResolver, + ordinalResolver: ordinalResolver, + ) { + $meta.setFlatMapFunction(_flatMapFunction); + } + + /// Metadata for the translations of . + @override final TranslationMetadata $meta; + + /// Access flat map + dynamic operator[](String key) => $meta.getTranslation(key); + + late final Translations _root = this; // ignore: unused_field + + // Translations + late final _StringsMenuEn menu = _StringsMenuEn._(_root); + late final _StringsCompanyScreenEn companyScreen = _StringsCompanyScreenEn._(_root); + late final _StringsScanEn scan = _StringsScanEn._(_root); +} + +// Path: menu +class _StringsMenuEn { + _StringsMenuEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get aboutPola => 'O aplikacji Pola'; + String get aboutClub => ' O Klubie Jagielońskim'; + String get instruction => 'Jak oceniamy Firmy'; + String get partners => 'Partnerzy'; + String get polasFriends => 'Przyjaciele Poli'; + String get rateUS => 'Oceń Polę'; + String get team => 'Zespół'; + String get findUs => 'Znajdź nas tutaj'; + String get footer => 'Aplikacja Pola \n© Klub Jagielloński'; +} + +// Path: companyScreen +class _StringsCompanyScreenEn { + _StringsCompanyScreenEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get ourRating => 'Nasza ocena:'; + String get gradingCriteria => 'Kryteria oceniania:'; + String get polishCapital => 'Polski kapitał'; + String get producedInPoland => 'Produkuje w Polsce'; + String get researchInPoland => 'Prowadzi badania w Polsce'; + String get registeredInPoland => 'Zarejestrowana w Polsce'; + String get notConcernPart => 'Nie jest częścią zagranicznego koncernu'; + String get seeMore => ' zobacz więcej'; + String get seeLess => ' zobacz mniej'; + String points({required Object score}) => '${score} pkt'; + String get companyFriend => ' Ta firma jest przyjacielem Poli'; + String get polaFriends => 'Przyjaciele Poli'; + String get companyUnverified => 'Niestety, ta firma nie została jeszcze zweryfikowana, więc nie możemy wyświetlić jej oceny. Stale rozszerzamy naszą bazę, aby uwzględnić więcej firm.'; + String get thankYou => 'Dziękujemy za cierpliwość!'; +} + +// Path: scan +class _StringsScanEn { + _StringsScanEn._(this._root); + + final Translations _root; // ignore: unused_field + + // Translations + String get scanning => 'Skanowanie'; + String get tryAgain => 'Niestety nie udało się pobrać danych. Spróbuj ponownie.'; + String get pkt => ' pkt'; + String get wait => 'Proszę czekać, trwa Ładowanie...'; + String get lastScans => 'Ostatnie skany:'; + String get error => 'Wystąpił błąd'; + String get closeError => 'Zamknij.'; + String get search => 'Wyszukaj'; +} + +/// Flat map(s) containing all translations. +/// Only for edge cases! For simple maps, use the map function of this library. + +extension on Translations { + dynamic _flatMapFunction(String path) { + switch (path) { + case 'menu.aboutPola': return 'O aplikacji Pola'; + case 'menu.aboutClub': return ' O Klubie Jagielońskim'; + case 'menu.instruction': return 'Jak oceniamy Firmy'; + case 'menu.partners': return 'Partnerzy'; + case 'menu.polasFriends': return 'Przyjaciele Poli'; + case 'menu.rateUS': return 'Oceń Polę'; + case 'menu.team': return 'Zespół'; + case 'menu.findUs': return 'Znajdź nas tutaj'; + case 'menu.footer': return 'Aplikacja Pola \n© Klub Jagielloński'; + case 'companyScreen.ourRating': return 'Nasza ocena:'; + case 'companyScreen.gradingCriteria': return 'Kryteria oceniania:'; + case 'companyScreen.polishCapital': return 'Polski kapitał'; + case 'companyScreen.producedInPoland': return 'Produkuje w Polsce'; + case 'companyScreen.researchInPoland': return 'Prowadzi badania w Polsce'; + case 'companyScreen.registeredInPoland': return 'Zarejestrowana w Polsce'; + case 'companyScreen.notConcernPart': return 'Nie jest częścią zagranicznego koncernu'; + case 'companyScreen.seeMore': return ' zobacz więcej'; + case 'companyScreen.seeLess': return ' zobacz mniej'; + case 'companyScreen.points': return ({required Object score}) => '${score} pkt'; + case 'companyScreen.companyFriend': return ' Ta firma jest przyjacielem Poli'; + case 'companyScreen.polaFriends': return 'Przyjaciele Poli'; + case 'companyScreen.companyUnverified': return 'Niestety, ta firma nie została jeszcze zweryfikowana, więc nie możemy wyświetlić jej oceny. Stale rozszerzamy naszą bazę, aby uwzględnić więcej firm.'; + case 'companyScreen.thankYou': return 'Dziękujemy za cierpliwość!'; + case 'scan.scanning': return 'Skanowanie'; + case 'scan.tryAgain': return 'Niestety nie udało się pobrać danych. Spróbuj ponownie.'; + case 'scan.pkt': return ' pkt'; + case 'scan.wait': return 'Proszę czekać, trwa Ładowanie...'; + case 'scan.lastScans': return 'Ostatnie skany:'; + case 'scan.error': return 'Wystąpił błąd'; + case 'scan.closeError': return 'Zamknij.'; + case 'scan.search': return 'Wyszukaj'; + default: return null; + } + } +} diff --git a/lib/i18n/strings.i18n.json b/lib/i18n/strings.i18n.json new file mode 100644 index 0000000..aa7d890 --- /dev/null +++ b/lib/i18n/strings.i18n.json @@ -0,0 +1,40 @@ +{ + "menu": { + "aboutPola": "O aplikacji Pola", + "aboutClub": " O Klubie Jagielońskim", + "instruction": "Jak oceniamy Firmy", + "partners": "Partnerzy", + "polasFriends": "Przyjaciele Poli", + "rateUS": "Oceń Polę", + "team": "Zespół", + "findUs": "Znajdź nas tutaj", + "footer": "Aplikacja Pola \n© Klub Jagielloński" + }, + "companyScreen": { + "ourRating": "Nasza ocena:", + "gradingCriteria": "Kryteria oceniania:", + "polishCapital": "Polski kapitał", + "producedInPoland": "Produkuje w Polsce", + "researchInPoland": "Prowadzi badania w Polsce", + "registeredInPoland": "Zarejestrowana w Polsce", + "notConcernPart":"Nie jest częścią zagranicznego koncernu", + "seeMore":" zobacz więcej", + "seeLess":" zobacz mniej", + "points":"$score pkt", + "companyFriend": " Ta firma jest przyjacielem Poli", + "polaFriends": "Przyjaciele Poli", + "companyUnverified": "Niestety, ta firma nie została jeszcze zweryfikowana, więc nie możemy wyświetlić jej oceny. Stale rozszerzamy naszą bazę, aby uwzględnić więcej firm.", + "thankYou": "Dziękujemy za cierpliwość!" + }, + "scan": { + "scanning": "Skanowanie", + "tryAgain": "Niestety nie udało się pobrać danych. Spróbuj ponownie.", + "pkt":" pkt", + "wait": "Proszę czekać, trwa Ładowanie...", + "lastScans": "Ostatnie skany:", + "error": "Wystąpił błąd", + "closeError": "Zamknij.", + "search": "Wyszukaj" + } + +} diff --git a/lib/main.dart b/lib/main.dart index 4520abf..5e18481 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,10 +6,11 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:logging/logging.dart'; import 'package:pola_flutter/analytics/analytics_main_tab.dart'; import 'package:pola_flutter/analytics/pola_analytics.dart'; +import 'package:pola_flutter/i18n/strings.g.dart'; import 'package:pola_flutter/models/search_result.dart'; import 'package:pola_flutter/pages/dialpad/dialpad.dart'; import 'package:pola_flutter/pages/scan/scan.dart'; -import 'package:pola_flutter/pages/web/web_view_page.dart'; +import 'package:pola_flutter/ui/web_view_tab.dart'; import 'firebase_options.dart'; import 'pages/detail/detail.dart'; @@ -22,7 +23,7 @@ void main() async { options: DefaultFirebaseOptions.currentPlatform, ); } - runApp(PolaApp()); + runApp(TranslationProvider(child: PolaApp())); } class PolaApp extends StatefulWidget { @@ -69,24 +70,15 @@ class _PolaAppState extends State { body: IndexedStack( index: _selectedIndex, children: _tabs, - ) - ), + )), ), ); } final List _tabs = [ MainPage(), - WebViewPage( - title: "Wyszukiwarka", - url: "https://www.pola-app.pl/m/search/", - showBackButton: false - ), - WebViewPage( - title: "Wiadomości", - url: "https://www.pola-app.pl/m/blog/", - showBackButton: false - ) + WebViewTab(title: "Wyszukiwarka", url: "https://www.pola-app.pl/m/search/"), + WebViewTab(title: "Wiadomości", url: "https://www.pola-app.pl/m/blog/") ]; AnalyticsMainTab _getTabParameter(int index) { @@ -128,17 +120,6 @@ class RouteGenerator { return MaterialPageRoute(builder: (_) => MainPage()); case '/dialpad': return MaterialPageRoute(builder: (_) => DialPadPage()); - case '/web': - if (args is String) { - return MaterialPageRoute( - builder: (_) => WebViewPage( - title: "O Aplikacji Pola", - url: args, - showBackButton: true - ), - ); - } - return MaterialPageRoute(builder: (_) => MainPage()); default: return MaterialPageRoute(builder: (_) => MainPage()); } diff --git a/lib/pages/detail/company_score_widget.dart b/lib/pages/detail/company_score_widget.dart new file mode 100644 index 0000000..5eeeb11 --- /dev/null +++ b/lib/pages/detail/company_score_widget.dart @@ -0,0 +1,174 @@ +import 'package:flutter/material.dart'; +import 'package:pola_flutter/i18n/strings.g.dart'; +import 'package:pola_flutter/theme/assets.gen.dart'; +import 'package:pola_flutter/theme/colors.dart'; +import 'package:pola_flutter/theme/fonts.gen.dart'; +import 'package:pola_flutter/theme/text_size.dart'; +import 'polish_capital_graph.dart'; + +class CompanyScoreData { + final double plCapital; + final bool plWorkers; + final bool plRnD; + final bool plRegistered; + final bool plNotGlobEnt; + final int plScore; + + CompanyScoreData( + {required this.plCapital, + required this.plWorkers, + required this.plRnD, + required this.plRegistered, + required this.plNotGlobEnt, + required this.plScore}); +} + +class CompanyScoreWidget extends StatelessWidget { + final CompanyScoreData data; + + const CompanyScoreWidget({super.key, required this.data}); + + @override + Widget build(BuildContext context) { + final Translations t = Translations.of(context); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Align( + alignment: Alignment.centerLeft, + child: Row( + children: [ + Assets.company.info.svg(height: 24.0, width: 24.0), + const SizedBox(width: 8.0), + Text( + t.companyScreen.ourRating, + style: TextStyle( + fontSize: TextSize.mediumTitle, + fontWeight: FontWeight.w600, + fontFamily: FontFamily.lato, + color: AppColors.text, + ), + ), + const SizedBox(width: 8.0), + Text( + t.companyScreen.points(score: data.plScore), + style: TextStyle( + fontSize: TextSize.newsTitle, + fontWeight: FontWeight.w700, + fontFamily: FontFamily.lato, + color: AppColors.text, + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(10.0), + child: TweenAnimationBuilder( + duration: Duration(milliseconds: data.plScore * 10), + tween: Tween(begin: 0, end: data.plScore.toDouble()), + builder: (_, double score, __) { + return LinearProgressIndicator( + value: score / 100.0, + backgroundColor: AppColors.buttonBackground, + valueColor: const AlwaysStoppedAnimation( + AppColors.defaultRed), + minHeight: 12.0, + ); + })), + ), + const SizedBox(height: 17.0), + Divider( + thickness: 1.0, + color: AppColors.divider, + indent: 0, + endIndent: 0, + ), + Padding( + padding: const EdgeInsets.only(top: 22.0), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + t.companyScreen.gradingCriteria, + style: TextStyle( + fontSize: TextSize.mediumTitle, + fontWeight: FontWeight.w600, + fontFamily: FontFamily.lato, + color: AppColors.text, + ), + ), + ), + ), + const SizedBox(height: 22.0), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + PolishCapitalGraph(percentage: data.plCapital), + const SizedBox(width: 35.0), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _DetailItem(t.companyScreen.producedInPoland, data.plWorkers), + const SizedBox(height: 14.0), + _DetailItem(t.companyScreen.researchInPoland, data.plRnD), + const SizedBox(height: 14.0), + _DetailItem( + t.companyScreen.registeredInPoland, data.plRegistered), + const SizedBox(height: 14.0), + _DetailItem( + t.companyScreen.notConcernPart, data.plNotGlobEnt), + ], + ), + ), + ], + ), + const SizedBox(height: 22.0), + Divider( + thickness: 1.0, + color: AppColors.divider, + indent: 0, + endIndent: 0, + ), + ], + ); + } +} + +class _DetailItem extends StatelessWidget { + const _DetailItem(this.text, this.state, {Key? key}) : super(key: key); + + final String text; + final bool state; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(right: 3.0), + child: state + ? Assets.company.taskAlt.svg() + : Assets.company.radioButtonUnchecked.svg()), + Expanded( + child: Text( + text, + style: TextStyle( + fontSize: TextSize.description, + fontWeight: FontWeight.w400, + fontFamily: FontFamily.lato, + color: AppColors.text, + ), + softWrap: true, + overflow: TextOverflow.visible, + ), + ), + ], + ); + } +} diff --git a/lib/pages/detail/detail.dart b/lib/pages/detail/detail.dart index c113ea9..a47248f 100644 --- a/lib/pages/detail/detail.dart +++ b/lib/pages/detail/detail.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:pola_flutter/analytics/pola_analytics.dart'; -import 'package:pola_flutter/models/brand.dart'; import 'package:pola_flutter/models/search_result.dart'; -import 'package:pola_flutter/ui/progress_indicator_text.dart'; -import 'package:url_launcher/url_launcher.dart'; - -import 'detail_lidl.dart'; +import 'package:pola_flutter/pages/detail/detail_content.dart'; +import 'package:pola_flutter/pages/detail/detail_lidl.dart'; +import 'package:pola_flutter/theme/colors.dart'; +import 'package:pola_flutter/ui/menu_icon_button.dart'; +import 'package:pola_flutter/pages/detail/text_marquee.dart'; +import 'package:pola_flutter/theme/text_size.dart'; class DetailPage extends StatelessWidget { DetailPage({Key? key, required this.searchResult}) : super(key: key); @@ -18,32 +18,30 @@ class DetailPage extends StatelessWidget { return LidlDetailPage(searchResult: searchResult); } - final company = searchResult.companies?.first; return Scaffold( backgroundColor: Colors.white, + appBar: AppBar( - title: Text(searchResult.name ?? ""), - leading: new IconButton( - icon: new Icon(Icons.arrow_back), + title: TextMarquee( + searchResult.name ?? "", + style: TextStyle( + color: AppColors.text, + fontSize: TextSize.newsTitle, + fontWeight: FontWeight.w600, + ), + ), + leading: IconButton( + icon: Icon(Icons.arrow_back), onPressed: () => Navigator.of(context).pop(), ), + actions: [MenuIconButton(color: AppColors.text)], ), body: SafeArea( - child: SingleChildScrollView( + child: SingleChildScrollView( child: Column( children: [ - Padding( - padding: EdgeInsets.all(12.0), - child: Align( - child: Text(searchResult.name ?? "", - style: TextStyle( - fontSize: 18.0, - )), - alignment: Alignment.centerLeft, - )), - LinearProgressIndicatorWithText((company?.plScore ?? 0).toDouble(), - (((company?.plScore ?? "0").toString()) + " pkt")), - _DetailContent(searchResult) + SizedBox(height: 16.0), + DetailContent(searchResult), ], ), ), @@ -51,165 +49,3 @@ class DetailPage extends StatelessWidget { ); } } - -class _DetailContent extends StatelessWidget { - _DetailContent(this.searchResult); - - final SearchResult searchResult; - - @override - Widget build(BuildContext context) { - if (searchResult.companies == null) { - return Padding( - padding: EdgeInsets.all(8.0), - child: Text(searchResult.altText ?? "")); - } - final company = searchResult.companies!.first; - return Column( - children: [ - Padding( - padding: EdgeInsets.all(8.0), - child: Column( - children: [ - Padding( - padding: EdgeInsets.all(4.0), - child: Align( - child: Text("udział polskiego kapitału"), - alignment: Alignment.centerLeft, - )), - LinearProgressIndicatorWithText( - (company.plCapital ?? 0).toDouble(), - (company.plCapital ?? 0).toString() + "%"), - ], - ), - ), - _DetailItem("produkuje w Polsce", (company.plWorkers ?? 0) != 0), - _DetailItem("prowadzi badania w Polsce", (company.plRnD ?? 0) != 0), - _DetailItem("zarejestrowana w Polsce", (company.plRegistered ?? 0) != 0), - _DetailItem("nie jest częścią zagranicznego koncernu", - (company.plNotGlobEnt ?? 0) != 0), - Padding( - padding: EdgeInsets.all(8.0), - child: Text(company.description ?? "")), - _DetailCompanyLogotype(company.logotypeUrl), - _BrandLogotypes(searchResult.allCompanyBrands), - _ReadMoreButton(searchResult) - ], - ); - } -} - -class _DetailItem extends StatelessWidget { - _DetailItem(this.text, this.state); - - final String text; - final bool state; - - @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Padding( - padding: EdgeInsets.all(4.0), - child: SizedBox( - height: 32.0, - width: 32.0, - child: Container( - child: Checkbox( - checkColor: Colors.white, - value: state, - onChanged: (bool? value) {}, - ), - ), - ), - ), - Text(text) - ], - ); - } -} - -class _ReadMoreButton extends StatelessWidget { - _ReadMoreButton(this.searchResult); - - final SearchResult searchResult; - final PolaAnalytics _analytics = PolaAnalytics.instance(); - - @override - Widget build(BuildContext context) { - final stringUrl = searchResult.companies?.first.officialUrl; - if (stringUrl == null) { - return Container(); - } - Uri? url = Uri.tryParse(stringUrl); - if (url == null) { - return Container(); - } - - return ElevatedButton( - onPressed: () { - _analytics.readMore(searchResult, stringUrl); - launchUrl( - url, - mode: LaunchMode.externalApplication, - ); - }, - child: Text("Czytaj więcej!"), - ); - } -} - -class _DetailCompanyLogotype extends StatelessWidget { - _DetailCompanyLogotype(this.url); - - final String? url; - - @override - Widget build(BuildContext context) { - final url = this.url; - if (url == null) { - return Container(); - } - - return _LogoView(url); - } -} - -class _BrandLogotypes extends StatelessWidget { - _BrandLogotypes(this.brands); - - final List? brands; - - @override - Widget build(BuildContext context) { - final logotypes = brands?.map((brand) => brand.logotypeUrl).where((url) => url != null).toList().cast(); - if (logotypes == null) { - return Container(); - } - - return SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: logotypes - .map((logotypeUrl) => Padding( - padding: EdgeInsets.all(8.0), - child: _LogoView(logotypeUrl))) - .toList(), - ), - ); - } -} - -class _LogoView extends StatelessWidget { - _LogoView(this.url); - - final String url; - - @override - Widget build(BuildContext context) { - return Image.network(url, height: 100.0, fit: BoxFit.contain); - } -} diff --git a/lib/pages/detail/detail_content.dart b/lib/pages/detail/detail_content.dart new file mode 100644 index 0000000..adceb4a --- /dev/null +++ b/lib/pages/detail/detail_content.dart @@ -0,0 +1,186 @@ +import 'package:flutter/material.dart'; +import 'package:pola_flutter/models/company.dart'; +import 'package:pola_flutter/models/search_result.dart'; +import 'package:pola_flutter/theme/assets.gen.dart'; +import 'package:pola_flutter/theme/colors.dart'; +import 'package:pola_flutter/theme/fonts.gen.dart'; +import 'package:pola_flutter/theme/text_size.dart'; +import 'company_score_widget.dart'; +import 'expandandable_text.dart'; +import 'logotypes.dart'; +import 'no_score_message.dart'; +import 'friends_bar.dart'; + +class DetailContent extends StatelessWidget { + const DetailContent(this.searchResult, {Key? key}) : super(key: key); + + final SearchResult searchResult; + + @override + Widget build(BuildContext context) { + if (searchResult.companies == null || searchResult.companies!.isEmpty) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Text(searchResult.altText ?? ""), + ); + } + + final company = searchResult.companies!.first; + + final hasLogo = company.logotypeUrl != null; + final hasDescription = company.description?.isNotEmpty ?? false; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if ((company.isFriend ?? false)) FriendsBar(), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 17.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _ScoreSection(company: company), + if (hasDescription) ...[ + Padding( + padding: const EdgeInsets.symmetric(vertical: 22.0), + child: ExpandableText(company.description ?? ""), + ), + if (hasLogo) + Divider( + thickness: 1.0, + color: AppColors.divider, + indent: 0, + endIndent: 0, + ), + ], + if (hasLogo) + Logotypes( + logotypes: searchResult.logotypes(), + searchResult: searchResult), + const SizedBox(height: 26.0), + if (hasLogo) + Divider( + thickness: 1.0, + color: AppColors.divider, + indent: 0, + endIndent: 0, + ), + ], + ), + ), + ], + ); + } +} + +class DetailItem extends StatelessWidget { + const DetailItem(this.text, this.state, {Key? key}) : super(key: key); + + final String text; + final bool state; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(right: 3.0), + child: state + ? Assets.company.taskAlt.svg() + : Assets.company.radioButtonUnchecked.svg(), + ), + Expanded( + child: Text( + text, + style: TextStyle( + fontSize: TextSize.description, + fontWeight: FontWeight.w400, + fontFamily: FontFamily.lato, + color: AppColors.text, + ), + softWrap: true, + overflow: TextOverflow.visible, + ), + ), + ], + ); + } +} + +extension on SearchResult { + List logotypes() { + var brandLogotypes = allCompanyBrands?.map((brand) { + final brandLogotype = brand.logotypeUrl; + if (brandLogotype != null) { + return Logotype(brandLogotype, null); + } else { + return null; + } + }).toList() ?? + []; + + final logotypeCompany = companies?.first.logotype(); + + brandLogotypes.insert(0, logotypeCompany); + + return brandLogotypes + .where((logotype) => logotype != null) + .cast() + .toList(); + } +} + +extension on Company { + Logotype? logotype() { + final logotypeUrl = this.logotypeUrl; + if (logotypeUrl != null) { + return Logotype(logotypeUrl, officialUrl); + } else { + return null; + } + } + + CompanyScoreData? _scoreData() { + final int? plCapital = this.plCapital; + final int? plWorkers = this.plWorkers; + final int? plRnD = this.plRnD; + final int? plRegistered = this.plRegistered; + final int? plNotGlobEnt = this.plNotGlobEnt; + final int? plScore = this.plScore; + + if (plCapital != null && + plWorkers != null && + plRnD != null && + plRegistered != null && + plNotGlobEnt != null && + plScore != null) { + return CompanyScoreData( + plCapital: plCapital.toDouble(), + plWorkers: plWorkers != 0, + plRnD: plRnD != 0, + plRegistered: plRegistered != 0, + plNotGlobEnt: plNotGlobEnt != 0, + plScore: plScore); + } + return null; + } +} + +class _ScoreSection extends StatelessWidget { + final Company company; + + const _ScoreSection({required this.company}); + + @override + Widget build(BuildContext context) { + final scoreData = company._scoreData(); + + if (scoreData != null) { + return CompanyScoreWidget(data: scoreData); + } else { + return NoScoreMessage(); + } + } +} diff --git a/lib/pages/detail/expandandable_text.dart b/lib/pages/detail/expandandable_text.dart new file mode 100644 index 0000000..26f0483 --- /dev/null +++ b/lib/pages/detail/expandandable_text.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/gestures.dart'; +import 'package:pola_flutter/i18n/strings.g.dart'; +import 'package:pola_flutter/theme/colors.dart'; +import 'package:pola_flutter/theme/fonts.gen.dart'; + +class ExpandableText extends StatefulWidget { + final String text; + ExpandableText(this.text); + + @override + _ExpandableTextState createState() => _ExpandableTextState(); +} + +class _ExpandableTextState extends State { + bool isExpanded = false; + + @override + Widget build(BuildContext context) { + final link = TextSpan( + style: TextStyle( + color: AppColors.inactive, + fontSize: 11.0, + fontWeight: FontWeight.w700, + fontFamily: FontFamily.lato, + ), + text: isExpanded ? t.companyScreen.seeLess : t.companyScreen.seeMore, + recognizer: TapGestureRecognizer() + ..onTap = () { + setState(() { + isExpanded = !isExpanded; + }); + }, + ); + + return LayoutBuilder( + builder: (context, constraints) { + final textSpan = TextSpan( + text: widget.text, + style: TextStyle( + color:AppColors.text, + fontSize: 11.0, + fontWeight: FontWeight.w400, + fontFamily: FontFamily.lato, + ), + ); + + final textPainter = TextPainter( + text: textSpan, + maxLines: isExpanded ? null : 3, + ellipsis: '...', + textDirection: TextDirection.ltr, + ); + + textPainter.layout( + minWidth: constraints.minWidth, + maxWidth: constraints.maxWidth, + ); + + final linkTextPainter = TextPainter( + text: link, + textDirection: TextDirection.ltr, + ); + + linkTextPainter.layout( + minWidth: constraints.minWidth, + maxWidth: constraints.maxWidth, + ); + + if (!isExpanded && textPainter.didExceedMaxLines) { + final pos = textPainter.getPositionForOffset(Offset( + textPainter.width - linkTextPainter.width, + textPainter.height, + )); + final end = textPainter.getOffsetBefore(pos.offset); + final text = TextSpan( + text: widget.text.substring(0, end), + style: TextStyle(color:AppColors.text), + children: [link], + ); + + return RichText( + text: text, + ); + } else { + return RichText( + text: TextSpan( + text: widget.text, + style: TextStyle(color:AppColors.text), + children: [if (isExpanded) link], + ), + ); + } + }, + ); + } +} diff --git a/lib/pages/detail/friends_bar.dart b/lib/pages/detail/friends_bar.dart new file mode 100644 index 0000000..8b4994d --- /dev/null +++ b/lib/pages/detail/friends_bar.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:pola_flutter/theme/assets.gen.dart'; +import 'package:pola_flutter/theme/colors.dart'; +import 'package:pola_flutter/theme/fonts.gen.dart'; +import 'package:pola_flutter/theme/text_size.dart'; +import 'package:pola_flutter/ui/web_view_dialog.dart'; +import 'package:pola_flutter/i18n/strings.g.dart'; + +class FriendsBar extends StatelessWidget { + @override + Widget build(BuildContext context) { + final Translations t = Translations.of(context); + return Padding( + padding: const EdgeInsets.only(bottom: 20.0), + child: GestureDetector( + onTap: () { + showWebViewDialog( + context: context, + url: "https://www.pola-app.pl/m/friends", + title: t.companyScreen.polaFriends); + }, + child: Container( + height: 40.0, + color: AppColors.buttonBackground, + alignment: Alignment.center, + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(left: 19.0), + child: Assets.company.heart.svg()), + Expanded( + child: Center( + child: Text( + t.companyScreen.companyFriend, + style: TextStyle( + fontSize: TextSize.smallTitle, + fontWeight: FontWeight.w700, + fontFamily: FontFamily.lato, + color: AppColors.defaultRed, + ), + textAlign: TextAlign.center, + ), + ), + ), + Padding( + padding: const EdgeInsets.only(right: 19.0), + child: Assets.company.heart.svg()), + ], + ), + ), + )); + } +} diff --git a/lib/pages/detail/logotypes.dart b/lib/pages/detail/logotypes.dart new file mode 100644 index 0000000..9737c51 --- /dev/null +++ b/lib/pages/detail/logotypes.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:pola_flutter/analytics/pola_analytics.dart'; +import 'package:pola_flutter/models/search_result.dart'; +import 'package:pola_flutter/theme/colors.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class Logotype { + final String imageUrl; + final String? websiteUrl; + + Logotype(this.imageUrl, this.websiteUrl); +} + +class Logotypes extends StatelessWidget { + final List logotypes; + final SearchResult searchResult; + final PolaAnalytics analytics = PolaAnalytics.instance(); + + Logotypes({required this.logotypes, required this.searchResult}); + + @override + Widget build(BuildContext context) { + if (logotypes.isEmpty) { + return Container(); + } + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: logotypes.map((logotype) { + return GestureDetector( + onTap: () { + final url = logotype.websiteUrl; + if (url == null) return; + final Uri? uri = Uri.tryParse(url); + if (uri == null) return; + + analytics.readMore(searchResult, url); + launchUrl( + uri, + mode: LaunchMode.externalApplication, + ); + }, + child: Padding( + padding: EdgeInsets.all(8.0), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.0), + color: AppColors.textField.withAlpha((0.7 * 255).toInt()), + boxShadow: [ + BoxShadow( + color: Colors.grey.withValues(alpha: 0.3), + spreadRadius: 1, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: _LogoView(logotype.imageUrl), + ), + ), + ), + ); + }).toList(), + ), + ); + } +} + +class _LogoView extends StatelessWidget { + _LogoView(this.url); + + final String url; + + @override + Widget build(BuildContext context) { + return Image.network(url, height: 60.0, fit: BoxFit.contain); + } +} diff --git a/lib/pages/detail/no_score_message.dart b/lib/pages/detail/no_score_message.dart new file mode 100644 index 0000000..b62eea0 --- /dev/null +++ b/lib/pages/detail/no_score_message.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:pola_flutter/theme/assets.gen.dart'; +import 'package:pola_flutter/theme/colors.dart'; +import 'package:pola_flutter/theme/fonts.gen.dart'; +import 'package:pola_flutter/theme/text_size.dart'; +import 'package:pola_flutter/i18n/strings.g.dart'; + +class NoScoreMessage extends StatelessWidget { + @override + Widget build(BuildContext context) { + final Translations t = Translations.of(context); + return Padding( + padding: const EdgeInsets.only(bottom: 22.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: + Assets.company.unpublished.svg(height: 109.42, width: 109.55), + ), + const SizedBox(height: 26.0), + Text( + t.companyScreen.companyUnverified, + style: TextStyle( + fontSize: TextSize.description, + fontWeight: FontWeight.w400, + fontFamily: FontFamily.lato, + color: AppColors.text, + ), + textAlign: TextAlign.left, + ), + SizedBox(height: 16), + Text( + t.companyScreen.thankYou, + style: TextStyle( + fontSize: TextSize.description, + fontWeight: FontWeight.w700, + fontFamily: FontFamily.lato, + color: AppColors.text, + ), + textAlign: TextAlign.left, + ) + ], + ), + ); + } +} diff --git a/lib/pages/detail/polish_capital_graph.dart b/lib/pages/detail/polish_capital_graph.dart new file mode 100644 index 0000000..78cdbc1 --- /dev/null +++ b/lib/pages/detail/polish_capital_graph.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; +import 'package:pola_flutter/theme/colors.dart'; +import 'package:pola_flutter/theme/fonts.gen.dart'; +import 'package:syncfusion_flutter_gauges/gauges.dart'; +import 'package:pola_flutter/i18n/strings.g.dart'; + +class PolishCapitalGraph extends StatelessWidget { + final double percentage; + + PolishCapitalGraph({required this.percentage}); + + @override + Widget build(BuildContext context) { + final size = 140.0; + final thickness = 0.15; + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + height: size, + width: size, + child: TweenAnimationBuilder( + duration: Duration(milliseconds: percentage.toInt() * 10), + tween: Tween(begin: 0, end: percentage), + builder: (_, double score, __) { + return SfRadialGauge( + axes: [ + RadialAxis( + minimum: 0, + maximum: 100, + showLabels: false, + showTicks: false, + axisLineStyle: AxisLineStyle( + thickness: thickness, + cornerStyle: CornerStyle.bothCurve, + color: AppColors.buttonBackground, + thicknessUnit: GaugeSizeUnit.factor, + ), + pointers: [ + RangePointer( + value: score, + cornerStyle: CornerStyle.bothCurve, + width: thickness, + sizeUnit: GaugeSizeUnit.factor, + color: AppColors.defaultRed, + ), + ], + annotations: [ + GaugeAnnotation( + positionFactor: 0.01, + angle: 90, + widget: Text( + '${percentage.toInt()}%', + style: TextStyle( + fontSize: 26.0, + fontWeight: FontWeight.w700, + fontFamily: FontFamily.lato, + color: AppColors.text), + ), + ), + ], + ), + ], + ); + })), + Padding( + padding: const EdgeInsets.only(top: 0.0), + child: Text( + t.companyScreen.polishCapital, + style: TextStyle( + fontSize: 11.0, + fontWeight: FontWeight.w400, + color: AppColors.text, + fontFamily: FontFamily.lato, + ), + ), + ), + ], + ); + } +} diff --git a/lib/pages/detail/text_marquee.dart b/lib/pages/detail/text_marquee.dart new file mode 100644 index 0000000..f889c14 --- /dev/null +++ b/lib/pages/detail/text_marquee.dart @@ -0,0 +1,191 @@ +import 'package:flutter/material.dart'; +// Copy/pasted from https://github.com/radnive/Flutter_TextMarquee/blob/main/lib/text_marquee.dart +// and added initState method to fix missing animation when added to AppBar. + +class TextMarquee extends StatefulWidget { + /// Text to be scrolled. + final String text; + + /// Text style. + final TextStyle style; + + /// Animation duration. + final Duration? duration; + + /// Animation curve. + final Curve curve; + + /// Delay time for scrolling start. + final Duration delay; + + /// The distance between the original text and its subsequent repetition. + /// ALERT: This value will be add to [startPaddingSize] + final double spaceSize; + + /// Text spacing from the beginning of the widget. + /// ALERT: This value will be add to [spaceSize] + final double startPaddingSize; + + /// If the text is arranged from the right, it should have a value of True. + final bool rtl; + + /// Create TextMarquee + const TextMarquee(this.text, + {Key? key, + this.style = const TextStyle(), + this.duration, + this.curve = Curves.linear, + this.delay = const Duration(seconds: 2), + this.spaceSize = 32, + this.startPaddingSize = 0, + this.rtl = false}) + : super(key: key); + + @override + State createState() => _TextMarqueeState(); +} + +class _TextMarqueeState extends State { + /// To control text scrolling (actually SingleChildScrollView). + final ScrollController _scrollController = ScrollController(); + + /// To save the length of the text. + double _textWidth = 0; + + /// To save whether the text length is longer than the widget length or not! + bool _isLarger = false; + + /// To save whether the text is scrolling or not! + bool _isScrolling = false; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _startAnimating(); + }); + } + + @override + void didUpdateWidget(TextMarquee oldWidget) { + // The commands inside this callback are executed after the build function. + WidgetsBinding.instance.addPostFrameCallback((_) { + // If the text is not scrolling, the animation will start. + if (!_isScrolling) { + _startAnimating(); + } + }); + super.didUpdateWidget(oldWidget); + } + + Future _startAnimating() async { + // If the text is not larger than widget, Scrolling will stop. + if (!_isLarger) { + _isScrolling = false; + return; + } + + // Apply delay to start scrolling. + await Future.delayed(widget.delay); + + // Ensure the widget is still mounted before proceeding + if (!mounted) return; + + // Calculate scrolling length. + double scrollLength = + _textWidth + widget.spaceSize + widget.startPaddingSize; + + // Scroll to the end of SingleChildScrollView. + await _scrollController.animateTo(scrollLength, + duration: (widget.duration != null) + ? widget.duration! + : Duration(milliseconds: (scrollLength * 27).toInt()), + curve: widget.curve); + + // Ensure the widget is still mounted before jumping + if (!mounted) return; + + // Jump to start of SingleChildScrollView. (without animation) + _scrollController.jumpTo(0); + + // Change scrolling status. + _isScrolling = true; + + // Repeat animation. + _startAnimating(); + } + + @override + Widget build(BuildContext context) { + // Get the text width. + _textWidth = _getTextWidth(context); + + return LayoutBuilder( + builder: (_, constraint) { + // Check if the text length is longer than the widget length or not!. + _isLarger = constraint.maxWidth <= _textWidth; + + return Directionality( + textDirection: TextDirection.ltr, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + physics: const NeverScrollableScrollPhysics(), + controller: _scrollController, + reverse: widget.rtl, + child: Row( + children: [ + SizedBox(width: widget.startPaddingSize), + Text(widget.text, style: widget.style, maxLines: 1), + (_isLarger) + ? Padding( + padding: EdgeInsets.only( + left: widget.spaceSize + widget.startPaddingSize), + child: Text(widget.text, + style: widget.style, maxLines: 1), + ) + : const SizedBox() + ], + )), + ); + }, + ); + } + + double _getTextWidth(BuildContext context) { + // If the text is blank, its length is zero. + if (widget.text.isEmpty) { + return 0; + } + + // Create textSpan from the text and its style. + final span = TextSpan(text: widget.text, style: widget.style); + + // Determine max width of the textSpan layout. + const constraints = BoxConstraints(maxWidth: double.infinity); + + // Build text widget. + final richTextWidget = Text.rich(span).build(context) as RichText; + + // Create render object from text widget. + final renderObject = richTextWidget.createRenderObject(context); + + // Set layout constraint to render object. + renderObject.layout(constraints); + + // Get text width from render object. + final boxes = renderObject.getBoxesForSelection(TextSelection( + baseOffset: 0, + extentOffset: TextSpan(text: widget.text).toPlainText().length, + )); + + // Return width of text. + return boxes.last.right; + } + + @override + void dispose() { + _isLarger = false; + _scrollController.dispose(); + super.dispose(); + } +} \ No newline at end of file diff --git a/lib/pages/menu/menu_bottom_sheet.dart b/lib/pages/menu/menu_bottom_sheet.dart new file mode 100644 index 0000000..9dcb8ba --- /dev/null +++ b/lib/pages/menu/menu_bottom_sheet.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:pola_flutter/analytics/pola_analytics.dart'; +import 'package:pola_flutter/pages/menu/menu_footer.dart'; +import 'package:pola_flutter/pages/menu/social_media_list_view.dart'; +import 'package:pola_flutter/theme/colors.dart'; +import 'menu_item_list_view.dart'; + +class MenuBottomSheet extends StatelessWidget { + final PolaAnalytics analytics; + + const MenuBottomSheet({super.key, required this.analytics}); + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(10.0), + topRight: Radius.circular(10.0), + ), + color: Colors.white, + ), + child: Column( + children: [ + const SizedBox(height: 13), + Container( + width: 47, + height: 3, + decoration: const BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.all(Radius.circular(1.5)), + ), + ), + const SizedBox(height: 11), + MenuItemListView(analytics: analytics), + const SizedBox(height: 23), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32.0), + child: Container( + height: 1, + color: AppColors.divider, + ), + ), + const SizedBox(height: 17), + SocialMediaListView(analytics: analytics), + const SizedBox(height: 17.0), + MenuFooter(), + const SizedBox(height: 33), + ], + ), + ), + ); + } +} diff --git a/lib/pages/menu/menu_footer.dart b/lib/pages/menu/menu_footer.dart new file mode 100644 index 0000000..3253f12 --- /dev/null +++ b/lib/pages/menu/menu_footer.dart @@ -0,0 +1,31 @@ +import 'package:flutter/widgets.dart'; +import 'package:pola_flutter/i18n/strings.g.dart'; +import 'package:pola_flutter/pages/menu/version_widget.dart'; +import 'package:pola_flutter/theme/fonts.gen.dart'; +import 'package:pola_flutter/theme/text_size.dart'; + +class MenuFooter extends StatelessWidget { + @override + Widget build(BuildContext context) { + final Translations t = Translations.of(context); + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 32.0), + child: Row( + children: [ + Text( + t.menu.footer, + style: TextStyle( + fontSize: TextSize.smallTitle, + fontWeight: FontWeight.w600, + fontFamily: FontFamily.lato + ), + ), + Expanded(child: Container()), + VersionWidget(), + ], + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + ) + ); + } +} diff --git a/lib/pages/menu/menu_item_list_view.dart b/lib/pages/menu/menu_item_list_view.dart new file mode 100644 index 0000000..efc0c59 --- /dev/null +++ b/lib/pages/menu/menu_item_list_view.dart @@ -0,0 +1,157 @@ +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:pola_flutter/theme/assets.gen.dart'; +import 'package:pola_flutter/theme/fonts.gen.dart'; +import 'package:pola_flutter/theme/text_size.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:pola_flutter/analytics/analytics_about_row.dart'; +import 'package:pola_flutter/analytics/pola_analytics.dart'; +import 'package:pola_flutter/i18n/strings.g.dart'; +import '../../ui/web_view_dialog.dart'; + +class MenuItemListView extends StatelessWidget { + final PolaAnalytics analytics; + + const MenuItemListView({super.key, required this.analytics}); + + @override + Widget build(BuildContext context) { + final Translations t = Translations.of(context); + return Column( + children: [ + _webViewItem( + context: context, + text: t.menu.aboutPola, + icon: Assets.menuPage.info.svg(), + analyticsRow: AnalyticsAboutRow.aboutPola, + url: 'https://www.pola-app.pl/m/about', + ), + _webViewItem( + context: context, + text: t.menu.aboutClub, + icon: Assets.menuPage.info.svg(), + analyticsRow: AnalyticsAboutRow.aboutKJ, + url: 'https://klubjagiellonski.pl/o-klubie-jagiellonskim/', + ), + _webViewItem( + context: context, + text: t.menu.instruction, + icon: Assets.menuPage.thumbs.svg(), + analyticsRow: AnalyticsAboutRow.instructionSet, + url: 'https://www.pola-app.pl/m/method', + ), + _webViewItem( + context: context, + text: t.menu.partners, + icon: Assets.menuPage.handshake.svg(), + analyticsRow: AnalyticsAboutRow.partners, + url: 'https://www.pola-app.pl/m/partners', + ), + _webViewItem( + context: context, + text: t.menu.polasFriends, + icon: Assets.menuPage.diversity.svg(), + analyticsRow: AnalyticsAboutRow.polasFriends, + url: 'https://www.pola-app.pl/m/friends', + ), + _externalUrlItem( + text: t.menu.rateUS, + icon: Assets.menuPage.star.svg(), + analyticsRow: AnalyticsAboutRow.rateUs, + url: Platform.isIOS + ? "https://apps.apple.com/app/id1038401148" + : "https://play.google.com/store/apps/details?id=pl.pola_app", + ), + _webViewItem( + context: context, + text: t.menu.team, + icon: Assets.menuPage.groups.svg(), + analyticsRow: AnalyticsAboutRow.team, + url: 'https://www.pola-app.pl/m/team', + ), + _externalUrlItem( + text: "Github", + icon: Assets.menuPage.github.svg(), + analyticsRow: AnalyticsAboutRow.github, + url: 'https://github.com/KlubJagiellonski', + ), + ], + ); + } + + Widget _webViewItem({ + required BuildContext context, + required String text, + required Widget icon, + required AnalyticsAboutRow analyticsRow, + required String url, + }) { + return _MenuBottomItem( + text: text, + icon: icon, + onClick: () { + analytics.aboutOpened(analyticsRow); + showWebViewDialog(context: context, url: url, title: text); + }, + ); + } + + Widget _externalUrlItem({ + required String text, + required Widget icon, + required AnalyticsAboutRow analyticsRow, + required String url, + }) { + return _MenuBottomItem( + text: text, + icon: icon, + onClick: () { + analytics.aboutOpened(analyticsRow); + _launchURL(url); + }, + ); + } + + void _launchURL(String url) async { + launchUrl( + Uri.parse(url), + mode: LaunchMode.externalApplication, + ); + } +} + +class _MenuBottomItem extends StatelessWidget { + final String text; + final Widget icon; + final VoidCallback onClick; + + const _MenuBottomItem({ + required this.text, + required this.icon, + required this.onClick, + }); + + final textStyle = const TextStyle( + fontWeight: FontWeight.w500, fontSize: TextSize.mediumTitle, fontFamily: FontFamily.lato); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onClick, + child: Column( + children: [ + const SizedBox(height: 16), + Row( + children: [ + const SizedBox(width: 32), + icon, + const SizedBox(width: 20.0), + Text(text, style: textStyle), + const SizedBox(width: 32), + ], + ), + ], + ), + ); + } +} diff --git a/lib/pages/menu/social_media_list_view.dart b/lib/pages/menu/social_media_list_view.dart new file mode 100644 index 0000000..706c976 --- /dev/null +++ b/lib/pages/menu/social_media_list_view.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:pola_flutter/analytics/analytics_about_row.dart'; +import 'package:pola_flutter/analytics/pola_analytics.dart'; +import 'package:pola_flutter/theme/colors.dart'; +import 'package:pola_flutter/theme/fonts.gen.dart'; +import 'package:pola_flutter/theme/text_size.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:pola_flutter/i18n/strings.g.dart'; + +class SocialMediaListView extends StatelessWidget { + final PolaAnalytics analytics; + + const SocialMediaListView({super.key, required this.analytics}); + + @override + Widget build(BuildContext context) { + final Translations t = Translations.of(context); + return Column( + children: [ + Row( + children: [ + SizedBox(width: 32.0), + Text( + t.menu.findUs, + style: TextStyle( + fontSize: TextSize.smallTitle, + fontWeight: FontWeight.w700, + fontFamily: FontFamily.lato + ), + ), + ], + ), + const SizedBox(height: 12), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32.0), + child: Row( + children: [ + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + clipBehavior: Clip.none, + child: Row( + children: [ + _socialItem( + text: "Twitter", + analyticsRow: AnalyticsAboutRow.twitter, + url: 'https://twitter.com/pola_app', + ), + const SizedBox(width: 14.0), + _socialItem( + text: "Facebook", + analyticsRow: AnalyticsAboutRow.facebook, + url: 'https://facebook.com', + ), + ], + ), + ), + ), + ], + ), + ), + ], + ); + } + + Widget _socialItem({ + required String text, + required AnalyticsAboutRow analyticsRow, + required String url, + }) { + return SocialItemView( + text: text, + onPressed: () { + analytics.aboutOpened(analyticsRow); + _launchURL(url); + }, + ); + } + + void _launchURL(String url) async { + await launchUrl( + Uri.parse(url), + mode: LaunchMode.externalApplication, + ); + } +} + +class SocialItemView extends StatelessWidget { + final String text; + final VoidCallback onPressed; + + const SocialItemView({ + super.key, + required this.text, + required this.onPressed, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: AppColors.defaultRed, + backgroundColor: AppColors.buttonBackground, + textStyle: const TextStyle(fontFamily: FontFamily.roboto), + padding: const EdgeInsets.symmetric(horizontal: 24.0), + ), + onPressed: onPressed, + child: Text(text), + ), + ); + } +} diff --git a/lib/pages/menu/version_bloc.dart b/lib/pages/menu/version_bloc.dart new file mode 100644 index 0000000..016da55 --- /dev/null +++ b/lib/pages/menu/version_bloc.dart @@ -0,0 +1,34 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +part 'version_bloc.freezed.dart'; + +@freezed +class VersionState with _$VersionState { + const factory VersionState({ + @Default(null) String? version, + }) = Initial; +} + +@freezed +class VersionEvent with _$VersionEvent { + const factory VersionEvent.onApear() = onApear; +} + +class VersionBloc extends Bloc { + VersionBloc(): super(VersionState()) { + on((event, emit) async { + await event.when( + onApear: () async => await _onApear(emit) + ); + }); + } + + _onApear(Emitter emit) async { + PackageInfo packageInfo = await PackageInfo.fromPlatform(); + String version = packageInfo.version; + String buildNumber = packageInfo.buildNumber; + emit(state.copyWith(version: 'ver $version ($buildNumber)')); + } +} diff --git a/lib/pages/menu/version_bloc.freezed.dart b/lib/pages/menu/version_bloc.freezed.dart new file mode 100644 index 0000000..f545ba2 --- /dev/null +++ b/lib/pages/menu/version_bloc.freezed.dart @@ -0,0 +1,283 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'version_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$VersionState { + String? get version => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $VersionStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $VersionStateCopyWith<$Res> { + factory $VersionStateCopyWith( + VersionState value, $Res Function(VersionState) then) = + _$VersionStateCopyWithImpl<$Res, VersionState>; + @useResult + $Res call({String? version}); +} + +/// @nodoc +class _$VersionStateCopyWithImpl<$Res, $Val extends VersionState> + implements $VersionStateCopyWith<$Res> { + _$VersionStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? version = freezed, + }) { + return _then(_value.copyWith( + version: freezed == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String?, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$InitialImplCopyWith<$Res> + implements $VersionStateCopyWith<$Res> { + factory _$$InitialImplCopyWith( + _$InitialImpl value, $Res Function(_$InitialImpl) then) = + __$$InitialImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String? version}); +} + +/// @nodoc +class __$$InitialImplCopyWithImpl<$Res> + extends _$VersionStateCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? version = freezed, + }) { + return _then(_$InitialImpl( + version: freezed == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc + +class _$InitialImpl implements Initial { + const _$InitialImpl({this.version = null}); + + @override + @JsonKey() + final String? version; + + @override + String toString() { + return 'VersionState(version: $version)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$InitialImpl && + (identical(other.version, version) || other.version == version)); + } + + @override + int get hashCode => Object.hash(runtimeType, version); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$InitialImplCopyWith<_$InitialImpl> get copyWith => + __$$InitialImplCopyWithImpl<_$InitialImpl>(this, _$identity); +} + +abstract class Initial implements VersionState { + const factory Initial({final String? version}) = _$InitialImpl; + + @override + String? get version; + @override + @JsonKey(ignore: true) + _$$InitialImplCopyWith<_$InitialImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$VersionEvent { + @optionalTypeArgs + TResult when({ + required TResult Function() onApear, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? onApear, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? onApear, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(onApear value) onApear, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(onApear value)? onApear, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(onApear value)? onApear, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $VersionEventCopyWith<$Res> { + factory $VersionEventCopyWith( + VersionEvent value, $Res Function(VersionEvent) then) = + _$VersionEventCopyWithImpl<$Res, VersionEvent>; +} + +/// @nodoc +class _$VersionEventCopyWithImpl<$Res, $Val extends VersionEvent> + implements $VersionEventCopyWith<$Res> { + _$VersionEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; +} + +/// @nodoc +abstract class _$$onApearImplCopyWith<$Res> { + factory _$$onApearImplCopyWith( + _$onApearImpl value, $Res Function(_$onApearImpl) then) = + __$$onApearImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$onApearImplCopyWithImpl<$Res> + extends _$VersionEventCopyWithImpl<$Res, _$onApearImpl> + implements _$$onApearImplCopyWith<$Res> { + __$$onApearImplCopyWithImpl( + _$onApearImpl _value, $Res Function(_$onApearImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$onApearImpl implements onApear { + const _$onApearImpl(); + + @override + String toString() { + return 'VersionEvent.onApear()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$onApearImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() onApear, + }) { + return onApear(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? onApear, + }) { + return onApear?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? onApear, + required TResult orElse(), + }) { + if (onApear != null) { + return onApear(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(onApear value) onApear, + }) { + return onApear(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(onApear value)? onApear, + }) { + return onApear?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(onApear value)? onApear, + required TResult orElse(), + }) { + if (onApear != null) { + return onApear(this); + } + return orElse(); + } +} + +abstract class onApear implements VersionEvent { + const factory onApear() = _$onApearImpl; +} diff --git a/lib/pages/menu/version_widget.dart b/lib/pages/menu/version_widget.dart new file mode 100644 index 0000000..ac4ebe1 --- /dev/null +++ b/lib/pages/menu/version_widget.dart @@ -0,0 +1,44 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:pola_flutter/theme/colors.dart'; +import 'package:pola_flutter/theme/fonts.gen.dart'; +import 'package:pola_flutter/theme/text_size.dart'; +import 'package:pola_flutter/pages/menu/version_bloc.dart'; + +class VersionWidget extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocProvider(create: (context) => VersionBloc()..add(VersionEvent.onApear()), + child: BlocBuilder( + builder: (context, state) { + return _VersionLabelWidget(version: state.version); + }, + ) + ); + } +} + +class _VersionLabelWidget extends StatelessWidget { + final String? version; + + const _VersionLabelWidget({this.version}); + + @override + Widget build(BuildContext context) { + String? version = this.version; + if (version == null) { + return Container(); + } + + return Text( + version, + style: TextStyle( + fontSize: TextSize.smallTitle, + fontWeight: FontWeight.w400, + fontFamily: FontFamily.lato, + color: AppColors.inactive, + ), + textAlign: TextAlign.end, + ); + } +} diff --git a/lib/pages/scan/companies_list.dart b/lib/pages/scan/companies_list.dart index f3ecb45..b3c8199 100644 --- a/lib/pages/scan/companies_list.dart +++ b/lib/pages/scan/companies_list.dart @@ -1,8 +1,11 @@ import 'package:flutter/material.dart'; import 'package:pola_flutter/analytics/pola_analytics.dart'; +import 'package:pola_flutter/i18n/strings.g.dart'; import 'package:pola_flutter/pages/scan/remote_button.dart'; import 'package:pola_flutter/pages/scan/scan_state.dart'; +import 'package:pola_flutter/theme/text_size.dart'; import 'package:pola_flutter/ui/list_item.dart'; +import 'dart:math'; class CompaniesList extends StatelessWidget { CompaniesList(this.state, this.listScrollController); @@ -13,35 +16,48 @@ class CompaniesList extends StatelessWidget { @override Widget build(BuildContext context) { + final int listSize = state.list.length; + _scrollToTop(); - return Column(mainAxisAlignment: MainAxisAlignment.end, children: [ - Container( - height: 200, - child: Align( - alignment: Alignment.bottomCenter, - child: ListView.builder( - controller: listScrollController, - reverse: true, - itemCount: state.list.length + (state.isLoading ? 1 : 0), - itemBuilder: (BuildContext context, int index) { - if (index == state.list.length) { - return LoadingListItem(); - } - return GestureDetector( - child: ResultListItem(state.list[index]), - onTap: () { - final result = state.list[index]; - _analytics.opensCard(result); - Navigator.pushNamed(context, '/detail', arguments: result); - }, - ); - }, + + double maxHeight = min(listSize * 47.5, 190.0); + + return Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + _ListHeader(listSize: listSize), + Container( + constraints: BoxConstraints( + maxHeight: maxHeight, + ), + child: Align( + alignment: Alignment.bottomCenter, + child: ListView.builder( + controller: listScrollController, + reverse: true, + itemCount: listSize + (state.isLoading ? 1 : 0), + itemBuilder: (BuildContext context, int index) { + if (index == listSize) { + return LoadingListItem(); + } + return GestureDetector( + child: ResultListItem(state.list[index]), + onTap: () { + final result = state.list[index]; + _analytics.opensCard(result); + Navigator.pushNamed(context, '/detail', arguments: result); + }, + ); + }, + ), ), ), - ), - RemoteButton(RemoteButtonState( - state.list.firstOrNull?.donate, state.list.firstOrNull?.code)) - ]); + RemoteButton(RemoteButtonState( + state.list.firstOrNull?.donate, + state.list.firstOrNull?.code, + )), + ], + ); } void _scrollToTop() { @@ -53,3 +69,31 @@ class CompaniesList extends StatelessWidget { }); } } + +class _ListHeader extends StatelessWidget { + final int listSize; + + const _ListHeader({required this.listSize}); + + @override + Widget build(BuildContext context) { + if (listSize > 0) { + return Padding( + padding: const EdgeInsets.only(left: 16.0), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + t.scan.lastScans, + style: TextStyle( + fontSize: TextSize.mediumTitle, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + ); + } else { + return Container(); + } + } +} diff --git a/lib/pages/scan/scan.dart b/lib/pages/scan/scan.dart index c6aa8ad..76f4312 100644 --- a/lib/pages/scan/scan.dart +++ b/lib/pages/scan/scan.dart @@ -2,15 +2,22 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; -import 'package:pola_flutter/analytics/analytics_about_row.dart'; import 'package:pola_flutter/analytics/pola_analytics.dart'; import 'package:pola_flutter/data/pola_api_repository.dart'; +import 'package:pola_flutter/pages/scan/companies_list.dart'; +import 'package:pola_flutter/pages/scan/scan_background.dart'; +import 'package:pola_flutter/pages/scan/scan_bloc.dart'; import 'package:pola_flutter/pages/scan/scan_event.dart'; +import 'package:pola_flutter/pages/scan/scan_search_button.dart'; import 'package:pola_flutter/pages/scan/scan_state.dart'; +import 'package:pola_flutter/i18n/strings.g.dart'; import 'package:pola_flutter/pages/scan/scan_vibration.dart'; -import 'package:pola_flutter/ui/menu_bottom_sheet.dart'; -import 'companies_list.dart'; -import 'scan_bloc.dart'; +import 'package:pola_flutter/pages/scan/torch_controller.dart'; +import 'package:pola_flutter/theme/assets.gen.dart'; +import 'package:pola_flutter/theme/colors.dart'; +import 'package:pola_flutter/theme/text_size.dart'; +import 'package:pola_flutter/ui/menu_icon_button.dart'; +import 'package:pola_flutter/ui/web_view_dialog.dart'; class MainPage extends StatefulWidget { @override @@ -24,10 +31,12 @@ class _MainPageState extends State { final PolaAnalytics _analytics = PolaAnalytics.instance(); ScrollController listScrollController = ScrollController(); + @override void initState() { super.initState(); - _scanBloc = ScanBloc(PolaApiRepository(), ScanVibrationImpl(), _analytics); + _scanBloc = ScanBloc(PolaApiRepository(), ScanVibrationImpl(), _analytics, + TorchControllerImpl(cameraController: cameraController)); } @override @@ -36,95 +45,105 @@ class _MainPageState extends State { appBar: AppBar( backgroundColor: Colors.transparent, elevation: 0, - title: Center( - child: IconButton( - onPressed: () { - cameraController.toggleTorch(); - }, - icon: Image.asset("assets/ic_flash_on_white_48dp.png", - height: AppBar().preferredSize.height), - ), - ), leading: IconButton( onPressed: () { _analytics.aboutPolaOpened(); - Navigator.pushNamed(context, '/web', - arguments: "https://www.pola-app.pl/m/about"); + showWebViewDialog( + context: context, + url: "https://www.pola-app.pl/m/about", + title: t.menu.aboutPola); }, - icon: Image.asset("assets/ic_launcher.png"), + icon: Assets.icLauncher.image(), + ), + actions: [MenuIconButton(color: AppColors.white)], + title: Align( + alignment: Alignment.centerLeft, + child: Text( + t.scan.scanning, + style: TextStyle( + fontSize: TextSize.newsTitle, + color: AppColors.white, + fontWeight: FontWeight.bold, + ), + ), ), - actions: [ - IconButton( - onPressed: () { - _analytics.aboutOpened(AnalyticsAboutRow.menu); - showModalBottomSheet( - backgroundColor: Colors.transparent, - isScrollControlled: true, - context: context, - builder: (BuildContext context) { - return MenuBottomSheet(analytics: _analytics); - }); - }, - icon: Image.asset("assets/menu.png"), - ) - ], ), body: Stack( children: [ _buildQrView(context), SafeArea( - child: Column( - children: [ - Center( - child: Padding( - padding: const EdgeInsets.only(top: 20.0), - child: Text( - "Umieść kod kreskowy produktu w prostokącie powyżej aby dowiedzieć się więcej o firmie, która go wyprodukowała.", - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - )))), - ], + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 20.0, horizontal: 16.0), + child: Column( + children: [ + ScanSearchButton(), + ], + ), ), ), SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Spacer(), - BlocBuilder( - bloc: _scanBloc, - builder: (context, state) { - if (state.isError) { - SchedulerBinding.instance.addPostFrameCallback((_) { - showDialog( - context: context, - barrierDismissible: false, - builder: (_) { - return AlertDialog( - title: Text('Wystąpił błąd'), - content: Text('Niestety nie udało się pobrać danych. Spróbuj ponownie.'), - actions: [ - TextButton( - child: Text('Zamknij.'), - onPressed: () { - _scanBloc.add(ScanEvent.alertDialogDismissed()); - SchedulerBinding.instance.addPostFrameCallback((_) { + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Spacer(), + BlocBuilder( + bloc: _scanBloc, + builder: (context, state) { + if (state.isError) { + SchedulerBinding.instance.addPostFrameCallback((_) { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: Text(t.scan.error), + content: Text(t.scan.tryAgain), + actions: [ + TextButton( + child: Text(t.scan.closeError), + onPressed: () { + _scanBloc + .add(ScanEvent.alertDialogDismissed()); Navigator.pop(context); - }); - }, + }, + ), + ], + ); + }, + ); + }); + } + return Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Expanded( + child: CompaniesList(state, listScrollController), + ), + Padding( + padding: const EdgeInsets.only(left: 8.0), + child: GestureDetector( + onTap: () { + _scanBloc.add(ScanEvent.torchSwitched()); + cameraController.toggleTorch(); + }, + child: Container( + decoration: BoxDecoration( + boxShadow: [], ), - ], - ); - }, - ); - }); - } - return CompaniesList(state, listScrollController); - }, - ), - ], - )), + child: state.isTorchOn + ? Assets.scan.flashlightOn.svg() + : Assets.scan.flashlightOff.svg(), + ), + ), + ) + ], + ); + }, + ), + ], + ), + ), ], ), extendBodyBehindAppBar: true, @@ -132,39 +151,22 @@ class _MainPageState extends State { } Widget _buildQrView(BuildContext context) { - var scanArea = (MediaQuery.of(context).size.width < 400 || - MediaQuery.of(context).size.height < 400) - ? 250.0 - : 300.0; return Stack( children: [ Positioned.fill( child: MobileScanner( - controller: cameraController, - onDetect: (capture) { - final List barcodes = capture.barcodes; - for (final barcode in barcodes) { - final String code = barcode.rawValue!; - debugPrint('Barcode found! $code'); - _scanBloc.add(ScanEvent.barcodeScanned(code)); - } - }), - ), - Positioned.fill( - child: Align( - alignment: Alignment.center, - child: SizedBox( - width: scanArea, - height: scanArea / 1.25, - child: DecoratedBox( - decoration: BoxDecoration( - border: Border.all(width: 5.0, color: Colors.black), - color: Colors.transparent, - ), - ), - ), + controller: cameraController, + onDetect: (capture) { + final List barcodes = capture.barcodes; + for (final barcode in barcodes) { + final String code = barcode.rawValue!; + debugPrint('Barcode found! $code'); + _scanBloc.add(ScanEvent.barcodeScanned(code)); + } + }, ), ), + ScanBackground(), ], ); } diff --git a/lib/pages/scan/scan_background.dart b/lib/pages/scan/scan_background.dart new file mode 100644 index 0000000..4ff7c66 --- /dev/null +++ b/lib/pages/scan/scan_background.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:pola_flutter/theme/assets.gen.dart'; +import 'package:pola_flutter/theme/colors.dart'; + +class ScanBackground extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Column( + children: [ + _BlackOpacity(), + Row( + children: [ + _SizedBlackOpacity(), + _RedRectangle(), + _SizedBlackOpacity(), + ], + ), + _BlackOpacity(), + ], + ); + } +} + +class _SizedBlackOpacity extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Expanded( + child: SizedBox( + height: 187, + child: Container( + color: AppColors.text.withValues(alpha: 0.7), + ), + ), + ); + } +} + +class _BlackOpacity extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Expanded( + child: Container( + color: AppColors.text.withValues(alpha: 0.7), + ), + ); + } +} + +class _RedRectangle extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Assets.menuPage.rectangle.svg(); + } +} diff --git a/lib/pages/scan/scan_bloc.dart b/lib/pages/scan/scan_bloc.dart index fc9e5bb..f917618 100644 --- a/lib/pages/scan/scan_bloc.dart +++ b/lib/pages/scan/scan_bloc.dart @@ -8,23 +8,29 @@ import 'package:pola_flutter/models/search_result.dart'; import 'package:pola_flutter/pages/scan/scan_event.dart'; import 'package:pola_flutter/pages/scan/scan_state.dart'; import 'package:pola_flutter/pages/scan/scan_vibration.dart'; +import 'package:pola_flutter/pages/scan/torch_controller.dart'; class ScanBloc extends Bloc { final PolaApi _polaApiRepository; final ScanVibration _scanVibration; final PolaAnalytics _analytics; + final TorchController _torchController; - ScanBloc(this._polaApiRepository, this._scanVibration, this._analytics, {ScanState state = const ScanState()}) + ScanBloc(this._polaApiRepository, this._scanVibration, this._analytics, + this._torchController, + {ScanState state = const ScanState()}) : super(state) { on((event, emit) async { await event.when( - barcodeScanned: (barcode) async => await _onBarcodeScanned(barcode, emit), + barcodeScanned: (barcode) async => + await _onBarcodeScanned(barcode, emit), alertDialogDismissed: () => _onAlertDialogDismissed(emit), + torchSwitched: () => _onTorchSwitched(emit), ); }); } - _onBarcodeScanned(String barcode, Emitter emit) async { + _onBarcodeScanned(String barcode, Emitter emit) async { if (state.list.any((element) => element.code == barcode) || state.isLoading || state.isError) { @@ -33,7 +39,8 @@ class ScanBloc extends Bloc { emit(state.copyWith(isLoading: true)); debugPrint('Scanned barcode event received: $barcode'); _scanVibration.vibrate(); - _analytics.barcodeScanned(barcode.toString(), AnalyticsBarcodeSource.camera); + _analytics.barcodeScanned( + barcode.toString(), AnalyticsBarcodeSource.camera); final res = await _polaApiRepository.getCompany(barcode); if (res.status == Status.COMPLETED) { @@ -47,6 +54,12 @@ class ScanBloc extends Bloc { } } + _onTorchSwitched(Emitter emit) { + _torchController.toggleTorch(); + + emit(state.copyWith(isTorchOn: !state.isTorchOn)); + } + _onAlertDialogDismissed(Emitter emit) { emit(state.copyWith(isError: false)); } diff --git a/lib/pages/scan/scan_event.dart b/lib/pages/scan/scan_event.dart index 5445b3f..5183027 100644 --- a/lib/pages/scan/scan_event.dart +++ b/lib/pages/scan/scan_event.dart @@ -6,4 +6,5 @@ part 'scan_event.freezed.dart'; class ScanEvent with _$ScanEvent { const factory ScanEvent.barcodeScanned(String barcode) = BarcodeScanned; const factory ScanEvent.alertDialogDismissed() = AlertDialogDismissed; + const factory ScanEvent.torchSwitched() = TorchSwitched; } diff --git a/lib/pages/scan/scan_event.freezed.dart b/lib/pages/scan/scan_event.freezed.dart index b034447..1481e63 100644 --- a/lib/pages/scan/scan_event.freezed.dart +++ b/lib/pages/scan/scan_event.freezed.dart @@ -20,18 +20,21 @@ mixin _$ScanEvent { TResult when({ required TResult Function(String barcode) barcodeScanned, required TResult Function() alertDialogDismissed, + required TResult Function() torchSwitched, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ TResult? Function(String barcode)? barcodeScanned, TResult? Function()? alertDialogDismissed, + TResult? Function()? torchSwitched, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ TResult Function(String barcode)? barcodeScanned, TResult Function()? alertDialogDismissed, + TResult Function()? torchSwitched, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -39,18 +42,21 @@ mixin _$ScanEvent { TResult map({ required TResult Function(BarcodeScanned value) barcodeScanned, required TResult Function(AlertDialogDismissed value) alertDialogDismissed, + required TResult Function(TorchSwitched value) torchSwitched, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? mapOrNull({ TResult? Function(BarcodeScanned value)? barcodeScanned, TResult? Function(AlertDialogDismissed value)? alertDialogDismissed, + TResult? Function(TorchSwitched value)? torchSwitched, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeMap({ TResult Function(BarcodeScanned value)? barcodeScanned, TResult Function(AlertDialogDismissed value)? alertDialogDismissed, + TResult Function(TorchSwitched value)? torchSwitched, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -140,6 +146,7 @@ class _$BarcodeScannedImpl implements BarcodeScanned { TResult when({ required TResult Function(String barcode) barcodeScanned, required TResult Function() alertDialogDismissed, + required TResult Function() torchSwitched, }) { return barcodeScanned(barcode); } @@ -149,6 +156,7 @@ class _$BarcodeScannedImpl implements BarcodeScanned { TResult? whenOrNull({ TResult? Function(String barcode)? barcodeScanned, TResult? Function()? alertDialogDismissed, + TResult? Function()? torchSwitched, }) { return barcodeScanned?.call(barcode); } @@ -158,6 +166,7 @@ class _$BarcodeScannedImpl implements BarcodeScanned { TResult maybeWhen({ TResult Function(String barcode)? barcodeScanned, TResult Function()? alertDialogDismissed, + TResult Function()? torchSwitched, required TResult orElse(), }) { if (barcodeScanned != null) { @@ -171,6 +180,7 @@ class _$BarcodeScannedImpl implements BarcodeScanned { TResult map({ required TResult Function(BarcodeScanned value) barcodeScanned, required TResult Function(AlertDialogDismissed value) alertDialogDismissed, + required TResult Function(TorchSwitched value) torchSwitched, }) { return barcodeScanned(this); } @@ -180,6 +190,7 @@ class _$BarcodeScannedImpl implements BarcodeScanned { TResult? mapOrNull({ TResult? Function(BarcodeScanned value)? barcodeScanned, TResult? Function(AlertDialogDismissed value)? alertDialogDismissed, + TResult? Function(TorchSwitched value)? torchSwitched, }) { return barcodeScanned?.call(this); } @@ -189,6 +200,7 @@ class _$BarcodeScannedImpl implements BarcodeScanned { TResult maybeMap({ TResult Function(BarcodeScanned value)? barcodeScanned, TResult Function(AlertDialogDismissed value)? alertDialogDismissed, + TResult Function(TorchSwitched value)? torchSwitched, required TResult orElse(), }) { if (barcodeScanned != null) { @@ -248,6 +260,7 @@ class _$AlertDialogDismissedImpl implements AlertDialogDismissed { TResult when({ required TResult Function(String barcode) barcodeScanned, required TResult Function() alertDialogDismissed, + required TResult Function() torchSwitched, }) { return alertDialogDismissed(); } @@ -257,6 +270,7 @@ class _$AlertDialogDismissedImpl implements AlertDialogDismissed { TResult? whenOrNull({ TResult? Function(String barcode)? barcodeScanned, TResult? Function()? alertDialogDismissed, + TResult? Function()? torchSwitched, }) { return alertDialogDismissed?.call(); } @@ -266,6 +280,7 @@ class _$AlertDialogDismissedImpl implements AlertDialogDismissed { TResult maybeWhen({ TResult Function(String barcode)? barcodeScanned, TResult Function()? alertDialogDismissed, + TResult Function()? torchSwitched, required TResult orElse(), }) { if (alertDialogDismissed != null) { @@ -279,6 +294,7 @@ class _$AlertDialogDismissedImpl implements AlertDialogDismissed { TResult map({ required TResult Function(BarcodeScanned value) barcodeScanned, required TResult Function(AlertDialogDismissed value) alertDialogDismissed, + required TResult Function(TorchSwitched value) torchSwitched, }) { return alertDialogDismissed(this); } @@ -288,6 +304,7 @@ class _$AlertDialogDismissedImpl implements AlertDialogDismissed { TResult? mapOrNull({ TResult? Function(BarcodeScanned value)? barcodeScanned, TResult? Function(AlertDialogDismissed value)? alertDialogDismissed, + TResult? Function(TorchSwitched value)? torchSwitched, }) { return alertDialogDismissed?.call(this); } @@ -297,6 +314,7 @@ class _$AlertDialogDismissedImpl implements AlertDialogDismissed { TResult maybeMap({ TResult Function(BarcodeScanned value)? barcodeScanned, TResult Function(AlertDialogDismissed value)? alertDialogDismissed, + TResult Function(TorchSwitched value)? torchSwitched, required TResult orElse(), }) { if (alertDialogDismissed != null) { @@ -309,3 +327,111 @@ class _$AlertDialogDismissedImpl implements AlertDialogDismissed { abstract class AlertDialogDismissed implements ScanEvent { const factory AlertDialogDismissed() = _$AlertDialogDismissedImpl; } + +/// @nodoc +abstract class _$$TorchSwitchedImplCopyWith<$Res> { + factory _$$TorchSwitchedImplCopyWith( + _$TorchSwitchedImpl value, $Res Function(_$TorchSwitchedImpl) then) = + __$$TorchSwitchedImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$TorchSwitchedImplCopyWithImpl<$Res> + extends _$ScanEventCopyWithImpl<$Res, _$TorchSwitchedImpl> + implements _$$TorchSwitchedImplCopyWith<$Res> { + __$$TorchSwitchedImplCopyWithImpl( + _$TorchSwitchedImpl _value, $Res Function(_$TorchSwitchedImpl) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$TorchSwitchedImpl implements TorchSwitched { + const _$TorchSwitchedImpl(); + + @override + String toString() { + return 'ScanEvent.torchSwitched()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$TorchSwitchedImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String barcode) barcodeScanned, + required TResult Function() alertDialogDismissed, + required TResult Function() torchSwitched, + }) { + return torchSwitched(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String barcode)? barcodeScanned, + TResult? Function()? alertDialogDismissed, + TResult? Function()? torchSwitched, + }) { + return torchSwitched?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String barcode)? barcodeScanned, + TResult Function()? alertDialogDismissed, + TResult Function()? torchSwitched, + required TResult orElse(), + }) { + if (torchSwitched != null) { + return torchSwitched(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(BarcodeScanned value) barcodeScanned, + required TResult Function(AlertDialogDismissed value) alertDialogDismissed, + required TResult Function(TorchSwitched value) torchSwitched, + }) { + return torchSwitched(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(BarcodeScanned value)? barcodeScanned, + TResult? Function(AlertDialogDismissed value)? alertDialogDismissed, + TResult? Function(TorchSwitched value)? torchSwitched, + }) { + return torchSwitched?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(BarcodeScanned value)? barcodeScanned, + TResult Function(AlertDialogDismissed value)? alertDialogDismissed, + TResult Function(TorchSwitched value)? torchSwitched, + required TResult orElse(), + }) { + if (torchSwitched != null) { + return torchSwitched(this); + } + return orElse(); + } +} + +abstract class TorchSwitched implements ScanEvent { + const factory TorchSwitched() = _$TorchSwitchedImpl; +} diff --git a/lib/pages/scan/scan_search_button.dart b/lib/pages/scan/scan_search_button.dart new file mode 100644 index 0000000..32c9fd7 --- /dev/null +++ b/lib/pages/scan/scan_search_button.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:pola_flutter/theme/assets.gen.dart'; +import 'package:pola_flutter/theme/text_size.dart'; +import 'package:pola_flutter/i18n/strings.g.dart'; + +class ScanSearchButton extends StatelessWidget { + const ScanSearchButton({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Center( + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(25), + ), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Assets.search.svg(), + ), + Expanded( + child: Text( + t.scan.search, + style: TextStyle( + fontSize: TextSize.mediumTitle, + fontWeight: FontWeight.w400, + color: Colors.black, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/scan/scan_state.dart b/lib/pages/scan/scan_state.dart index 4cb571b..8656322 100644 --- a/lib/pages/scan/scan_state.dart +++ b/lib/pages/scan/scan_state.dart @@ -8,6 +8,7 @@ class ScanState with _$ScanState { const factory ScanState({ @Default([]) List list, @Default(false) bool isLoading, - @Default(false) bool isError + @Default(false) bool isError, + @Default(false) bool isTorchOn }) = Initial; } diff --git a/lib/pages/scan/scan_state.freezed.dart b/lib/pages/scan/scan_state.freezed.dart index 8d33333..8e212be 100644 --- a/lib/pages/scan/scan_state.freezed.dart +++ b/lib/pages/scan/scan_state.freezed.dart @@ -19,6 +19,7 @@ mixin _$ScanState { List get list => throw _privateConstructorUsedError; bool get isLoading => throw _privateConstructorUsedError; bool get isError => throw _privateConstructorUsedError; + bool get isTorchOn => throw _privateConstructorUsedError; @JsonKey(ignore: true) $ScanStateCopyWith get copyWith => @@ -30,7 +31,8 @@ abstract class $ScanStateCopyWith<$Res> { factory $ScanStateCopyWith(ScanState value, $Res Function(ScanState) then) = _$ScanStateCopyWithImpl<$Res, ScanState>; @useResult - $Res call({List list, bool isLoading, bool isError}); + $Res call( + {List list, bool isLoading, bool isError, bool isTorchOn}); } /// @nodoc @@ -49,6 +51,7 @@ class _$ScanStateCopyWithImpl<$Res, $Val extends ScanState> Object? list = null, Object? isLoading = null, Object? isError = null, + Object? isTorchOn = null, }) { return _then(_value.copyWith( list: null == list @@ -63,6 +66,10 @@ class _$ScanStateCopyWithImpl<$Res, $Val extends ScanState> ? _value.isError : isError // ignore: cast_nullable_to_non_nullable as bool, + isTorchOn: null == isTorchOn + ? _value.isTorchOn + : isTorchOn // ignore: cast_nullable_to_non_nullable + as bool, ) as $Val); } } @@ -75,7 +82,8 @@ abstract class _$$InitialImplCopyWith<$Res> __$$InitialImplCopyWithImpl<$Res>; @override @useResult - $Res call({List list, bool isLoading, bool isError}); + $Res call( + {List list, bool isLoading, bool isError, bool isTorchOn}); } /// @nodoc @@ -92,6 +100,7 @@ class __$$InitialImplCopyWithImpl<$Res> Object? list = null, Object? isLoading = null, Object? isError = null, + Object? isTorchOn = null, }) { return _then(_$InitialImpl( list: null == list @@ -106,6 +115,10 @@ class __$$InitialImplCopyWithImpl<$Res> ? _value.isError : isError // ignore: cast_nullable_to_non_nullable as bool, + isTorchOn: null == isTorchOn + ? _value.isTorchOn + : isTorchOn // ignore: cast_nullable_to_non_nullable + as bool, )); } } @@ -116,7 +129,8 @@ class _$InitialImpl implements Initial { const _$InitialImpl( {final List list = const [], this.isLoading = false, - this.isError = false}) + this.isError = false, + this.isTorchOn = false}) : _list = list; final List _list; @@ -134,10 +148,13 @@ class _$InitialImpl implements Initial { @override @JsonKey() final bool isError; + @override + @JsonKey() + final bool isTorchOn; @override String toString() { - return 'ScanState(list: $list, isLoading: $isLoading, isError: $isError)'; + return 'ScanState(list: $list, isLoading: $isLoading, isError: $isError, isTorchOn: $isTorchOn)'; } @override @@ -148,12 +165,18 @@ class _$InitialImpl implements Initial { const DeepCollectionEquality().equals(other._list, _list) && (identical(other.isLoading, isLoading) || other.isLoading == isLoading) && - (identical(other.isError, isError) || other.isError == isError)); + (identical(other.isError, isError) || other.isError == isError) && + (identical(other.isTorchOn, isTorchOn) || + other.isTorchOn == isTorchOn)); } @override - int get hashCode => Object.hash(runtimeType, - const DeepCollectionEquality().hash(_list), isLoading, isError); + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_list), + isLoading, + isError, + isTorchOn); @JsonKey(ignore: true) @override @@ -166,7 +189,8 @@ abstract class Initial implements ScanState { const factory Initial( {final List list, final bool isLoading, - final bool isError}) = _$InitialImpl; + final bool isError, + final bool isTorchOn}) = _$InitialImpl; @override List get list; @@ -175,6 +199,8 @@ abstract class Initial implements ScanState { @override bool get isError; @override + bool get isTorchOn; + @override @JsonKey(ignore: true) _$$InitialImplCopyWith<_$InitialImpl> get copyWith => throw _privateConstructorUsedError; diff --git a/lib/pages/scan/torch_controller.dart b/lib/pages/scan/torch_controller.dart new file mode 100644 index 0000000..6791fd9 --- /dev/null +++ b/lib/pages/scan/torch_controller.dart @@ -0,0 +1,17 @@ +import 'package:mobile_scanner/mobile_scanner.dart'; + +abstract class TorchController { + void toggleTorch(); +} + +class TorchControllerImpl implements TorchController { + final MobileScannerController _cameraController; + + TorchControllerImpl({required MobileScannerController cameraController}) + : _cameraController = cameraController; + + @override + void toggleTorch() { + _cameraController.toggleTorch(); + } +} diff --git a/lib/pages/web/web_view_page.dart b/lib/pages/web/web_view_page.dart index e7470e9..eac3cba 100644 --- a/lib/pages/web/web_view_page.dart +++ b/lib/pages/web/web_view_page.dart @@ -1,13 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:pola_flutter/theme/colors.dart'; import 'package:webview_flutter/webview_flutter.dart'; class WebViewPage extends StatefulWidget { - WebViewPage({Key? key, required this.title, required this.url, required this.showBackButton}) - : super(key: key); + WebViewPage({Key? key, required this.url}) : super(key: key); final String url; - final String title; - final bool showBackButton; @override _WebViewTabState createState() => _WebViewTabState(); @@ -24,24 +22,21 @@ class _WebViewTabState extends State { controller = WebViewController() ..setJavaScriptMode(JavaScriptMode.unrestricted) ..setNavigationDelegate( - NavigationDelegate( - onProgress: (int progress) { - setState(() { - loadingPercentage = progress; - }); - }, - onPageStarted: (String url) { - setState(() { - loadingPercentage = 0; - }); - }, - onPageFinished: (String url) { - setState(() { - loadingPercentage = 100; - }); - } - ), - ) + NavigationDelegate(onProgress: (int progress) { + setState(() { + loadingPercentage = progress; + }); + }, onPageStarted: (String url) { + setState(() { + loadingPercentage = 0; + }); + }, onPageFinished: (String url) { + setState(() { + loadingPercentage = 100; + }); + }), + ) + ..setBackgroundColor(AppColors.white) ..loadRequest(Uri.parse(widget.url)); } @@ -53,41 +48,14 @@ class _WebViewTabState extends State { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: Colors.white, - elevation: 0, - title: Text( - widget.title, - style: TextStyle( - color: Colors.black, - ) + return Stack( + children: [ + WebViewWidget(controller: controller), + if (loadingPercentage < 100) + LinearProgressIndicator( + value: loadingPercentage / 100.0, ), - leading: widget.showBackButton ? _BackButton() : null, - - ), - body: Stack( - children: [ - WebViewWidget(controller: controller), - if (loadingPercentage < 100) - LinearProgressIndicator( - value: loadingPercentage / 100.0, - ), - ], - ) - ); - } -} - -class _BackButton extends StatelessWidget { - @override - Widget build(BuildContext context) { - return IconButton( - icon: Icon( - Icons.arrow_back, - color: Colors.black, - ), - onPressed: () => Navigator.of(context).pop(), + ], ); } } diff --git a/lib/theme/assets.gen.dart b/lib/theme/assets.gen.dart new file mode 100644 index 0000000..b65dbf8 --- /dev/null +++ b/lib/theme/assets.gen.dart @@ -0,0 +1,272 @@ +/// GENERATED CODE - DO NOT MODIFY BY HAND +/// ***************************************************** +/// FlutterGen +/// ***************************************************** + +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use + +import 'package:flutter/widgets.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:vector_graphics/vector_graphics.dart'; + +class $AssetsCompanyGen { + const $AssetsCompanyGen(); + + /// File path: assets/company/heart.svg + SvgGenImage get heart => const SvgGenImage('assets/company/heart.svg'); + + /// File path: assets/company/info.svg + SvgGenImage get info => const SvgGenImage('assets/company/info.svg'); + + /// File path: assets/company/radio_button_unchecked.svg + SvgGenImage get radioButtonUnchecked => const SvgGenImage('assets/company/radio_button_unchecked.svg'); + + /// File path: assets/company/task_alt.svg + SvgGenImage get taskAlt => const SvgGenImage('assets/company/task_alt.svg'); + + /// File path: assets/company/unpublished.svg + SvgGenImage get unpublished => const SvgGenImage('assets/company/unpublished.svg'); + + /// List of all assets + List get values => [heart, info, radioButtonUnchecked, taskAlt, unpublished]; +} + +class $AssetsMenuPageGen { + const $AssetsMenuPageGen(); + + /// File path: assets/menuPage/diversity.svg + SvgGenImage get diversity => const SvgGenImage('assets/menuPage/diversity.svg'); + + /// File path: assets/menuPage/github.svg + SvgGenImage get github => const SvgGenImage('assets/menuPage/github.svg'); + + /// File path: assets/menuPage/groups.svg + SvgGenImage get groups => const SvgGenImage('assets/menuPage/groups.svg'); + + /// File path: assets/menuPage/handshake.svg + SvgGenImage get handshake => const SvgGenImage('assets/menuPage/handshake.svg'); + + /// File path: assets/menuPage/info.svg + SvgGenImage get info => const SvgGenImage('assets/menuPage/info.svg'); + + /// File path: assets/menuPage/menu.svg + SvgGenImage get menu => const SvgGenImage('assets/menuPage/menu.svg'); + + /// File path: assets/menuPage/rectangle.svg + SvgGenImage get rectangle => const SvgGenImage('assets/menuPage/rectangle.svg'); + + /// File path: assets/menuPage/star.svg + SvgGenImage get star => const SvgGenImage('assets/menuPage/star.svg'); + + /// File path: assets/menuPage/thumbs.svg + SvgGenImage get thumbs => const SvgGenImage('assets/menuPage/thumbs.svg'); + + /// List of all assets + List get values => [diversity, github, groups, handshake, info, menu, rectangle, star, thumbs]; +} + +class $AssetsNavigationGen { + const $AssetsNavigationGen(); + + /// File path: assets/navigation/close.svg + SvgGenImage get close => const SvgGenImage('assets/navigation/close.svg'); + + /// List of all assets + List get values => [close]; +} + +class $AssetsScanGen { + const $AssetsScanGen(); + + /// File path: assets/scan/flashlightOff.svg + SvgGenImage get flashlightOff => const SvgGenImage('assets/scan/flashlightOff.svg'); + + /// File path: assets/scan/flashlightOn.svg + SvgGenImage get flashlightOn => const SvgGenImage('assets/scan/flashlightOn.svg'); + + /// File path: assets/scan/showMore.svg + SvgGenImage get showMore => const SvgGenImage('assets/scan/showMore.svg'); + + /// List of all assets + List get values => [flashlightOff, flashlightOn, showMore]; +} + +class Assets { + Assets._(); + + static const $AssetsCompanyGen company = $AssetsCompanyGen(); + static const AssetGenImage icAddBlack24dp = AssetGenImage('assets/ic_add_black_24dp.png'); + static const AssetGenImage icBackspaceWhite36dp = AssetGenImage('assets/ic_backspace_white_36dp.png'); + static const AssetGenImage icDialpadWhite36dp = AssetGenImage('assets/ic_dialpad_white_36dp.png'); + static const AssetGenImage icDoneWhite36dp = AssetGenImage('assets/ic_done_white_36dp.png'); + static const AssetGenImage icLauncher = AssetGenImage('assets/ic_launcher.png'); + static const AssetGenImage menu = AssetGenImage('assets/menu.png'); + static const $AssetsMenuPageGen menuPage = $AssetsMenuPageGen(); + static const $AssetsNavigationGen navigation = $AssetsNavigationGen(); + static const $AssetsScanGen scan = $AssetsScanGen(); + static const SvgGenImage search = SvgGenImage('assets/search.svg'); + + /// List of all assets + static List get values => + [icAddBlack24dp, icBackspaceWhite36dp, icDialpadWhite36dp, icDoneWhite36dp, icLauncher, menu, search]; +} + +class AssetGenImage { + const AssetGenImage( + this._assetName, { + this.size, + this.flavors = const {}, + }); + + final String _assetName; + + final Size? size; + final Set flavors; + + Image image({ + Key? key, + AssetBundle? bundle, + ImageFrameBuilder? frameBuilder, + ImageErrorWidgetBuilder? errorBuilder, + String? semanticLabel, + bool excludeFromSemantics = false, + double? scale, + double? width, + double? height, + Color? color, + Animation? opacity, + BlendMode? colorBlendMode, + BoxFit? fit, + AlignmentGeometry alignment = Alignment.center, + ImageRepeat repeat = ImageRepeat.noRepeat, + Rect? centerSlice, + bool matchTextDirection = false, + bool gaplessPlayback = false, + bool isAntiAlias = false, + String? package, + FilterQuality filterQuality = FilterQuality.low, + int? cacheWidth, + int? cacheHeight, + }) { + return Image.asset( + _assetName, + key: key, + bundle: bundle, + frameBuilder: frameBuilder, + errorBuilder: errorBuilder, + semanticLabel: semanticLabel, + excludeFromSemantics: excludeFromSemantics, + scale: scale, + width: width, + height: height, + color: color, + opacity: opacity, + colorBlendMode: colorBlendMode, + fit: fit, + alignment: alignment, + repeat: repeat, + centerSlice: centerSlice, + matchTextDirection: matchTextDirection, + gaplessPlayback: gaplessPlayback, + isAntiAlias: isAntiAlias, + package: package, + filterQuality: filterQuality, + cacheWidth: cacheWidth, + cacheHeight: cacheHeight, + ); + } + + ImageProvider provider({ + AssetBundle? bundle, + String? package, + }) { + return AssetImage( + _assetName, + bundle: bundle, + package: package, + ); + } + + String get path => _assetName; + + String get keyName => _assetName; +} + +class SvgGenImage { + const SvgGenImage( + this._assetName, { + this.size, + this.flavors = const {}, + }) : _isVecFormat = false; + + const SvgGenImage.vec( + this._assetName, { + this.size, + this.flavors = const {}, + }) : _isVecFormat = true; + + final String _assetName; + final Size? size; + final Set flavors; + final bool _isVecFormat; + + SvgPicture svg({ + Key? key, + bool matchTextDirection = false, + AssetBundle? bundle, + String? package, + double? width, + double? height, + BoxFit fit = BoxFit.contain, + AlignmentGeometry alignment = Alignment.center, + bool allowDrawingOutsideViewBox = false, + WidgetBuilder? placeholderBuilder, + String? semanticsLabel, + bool excludeFromSemantics = false, + SvgTheme? theme, + ColorFilter? colorFilter, + Clip clipBehavior = Clip.hardEdge, + @deprecated Color? color, + @deprecated BlendMode colorBlendMode = BlendMode.srcIn, + @deprecated bool cacheColorFilter = false, + }) { + final BytesLoader loader; + if (_isVecFormat) { + loader = AssetBytesLoader( + _assetName, + assetBundle: bundle, + packageName: package, + ); + } else { + loader = SvgAssetLoader( + _assetName, + assetBundle: bundle, + packageName: package, + theme: theme, + ); + } + return SvgPicture( + loader, + key: key, + matchTextDirection: matchTextDirection, + width: width, + height: height, + fit: fit, + alignment: alignment, + allowDrawingOutsideViewBox: allowDrawingOutsideViewBox, + placeholderBuilder: placeholderBuilder, + semanticsLabel: semanticsLabel, + excludeFromSemantics: excludeFromSemantics, + colorFilter: colorFilter ?? (color == null ? null : ColorFilter.mode(color, colorBlendMode)), + clipBehavior: clipBehavior, + cacheColorFilter: cacheColorFilter, + ); + } + + String get path => _assetName; + + String get keyName => _assetName; +} diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart new file mode 100644 index 0000000..0302320 --- /dev/null +++ b/lib/theme/colors.dart @@ -0,0 +1,26 @@ +import 'package:flutter/widgets.dart'; + +class AppColors { + AppColors._(); + + /// Color: Default red + static const Color defaultRed = Color(0xFFE1203E); + + /// Color: Background for buttons and backgrounds + static const Color buttonBackground = Color(0xFFF5DEDD); + + /// Color: Text + static const Color text = Color(0xFF1C1B1F); + + /// Color: Inactive + static const Color inactive = Color(0xFF898989); + + /// Color: Dividers + static const Color divider = Color(0xFFF0F0F0); + + /// Color: Text fields + static const Color textField = Color(0xFFF8F8F8); + + /// Color: White + static const Color white = Color(0xFFFFFFFF); +} diff --git a/lib/theme/fonts.gen.dart b/lib/theme/fonts.gen.dart new file mode 100644 index 0000000..305560d --- /dev/null +++ b/lib/theme/fonts.gen.dart @@ -0,0 +1,18 @@ +/// GENERATED CODE - DO NOT MODIFY BY HAND +/// ***************************************************** +/// FlutterGen +/// ***************************************************** + +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use + +class FontFamily { + FontFamily._(); + + /// Font family: Lato + static const String lato = 'Lato'; + + /// Font family: Roboto + static const String roboto = 'Roboto'; +} diff --git a/lib/theme/text_size.dart b/lib/theme/text_size.dart new file mode 100644 index 0000000..2a89798 --- /dev/null +++ b/lib/theme/text_size.dart @@ -0,0 +1,18 @@ +class TextSize { + TextSize._(); + + /// Text size for Page title + static const double pageTitle = 24.0; + + /// Text size for News title + static const double newsTitle = 20.0; + + /// Text size for Medium title + static const double mediumTitle = 16.0; + + /// Text size for Small title + static const double smallTitle = 12.0; + + /// Text size for Description + static const double description = 11.0; +} diff --git a/lib/ui/list_item.dart b/lib/ui/list_item.dart index 7ed34e6..6d74364 100644 --- a/lib/ui/list_item.dart +++ b/lib/ui/list_item.dart @@ -1,41 +1,83 @@ import 'package:flutter/material.dart'; +import 'package:pola_flutter/i18n/strings.g.dart'; import 'package:pola_flutter/models/search_result.dart'; +import 'package:pola_flutter/theme/assets.gen.dart'; +import 'package:pola_flutter/theme/colors.dart'; +import 'package:pola_flutter/theme/fonts.gen.dart'; +import 'package:pola_flutter/theme/text_size.dart'; class ResultListItem extends StatelessWidget { ResultListItem(this.searchResult); final SearchResult searchResult; + static const double leftBoxSize = 40.0; @override Widget build(BuildContext context) { - final textStyle = TextStyle(fontWeight: FontWeight.normal, fontSize: 16.0); + final pointValueStyle = TextStyle( + height: 0, + fontWeight: FontWeight.w700, + fontFamily: FontFamily.roboto, + fontSize: TextSize.mediumTitle, + color: Colors.white, + ); + + final pointDescriptionStyle = TextStyle( + height: 0.1, + fontWeight: FontWeight.w700, + fontFamily: FontFamily.roboto, + fontSize: 9, + color: Colors.white, + ); + return _ListItem( - child: Column( - children: [ - Expanded( - child: Align( - alignment: Alignment.centerLeft, - child: Padding( - padding: EdgeInsets.all(4.0), - child: Text(searchResult.name!, style: textStyle,) - ) - ) - ), - Align( - alignment: Alignment.bottomCenter, - child: ClipRRect( - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(5), - bottomRight: Radius.circular(5) + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: leftBoxSize, + height: leftBoxSize, + decoration: BoxDecoration( + color: AppColors.defaultRed, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(leftBoxSize / 2), + bottomLeft: Radius.circular(leftBoxSize / 2), + ), + ), + alignment: Alignment.center, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '${searchResult.companies?.first.plScore ?? 0}', + style: pointValueStyle, + textAlign: TextAlign.center, + ), + Text( + t.scan.pkt, + style: pointDescriptionStyle, + textAlign: TextAlign.center, + ), + ], ), - child: LinearProgressIndicator( - value:(searchResult.companies?.first.plScore ?? 0) / 100.toDouble(), - backgroundColor: Colors.white, - semanticsLabel: 'Linear progress indicator', + ), + SizedBox(width: 8.0), + Expanded( + child: Container( + alignment: Alignment.centerLeft, + child: Text( + searchResult.name!, + style: TextStyle( + fontWeight: FontWeight.w400, + fontSize: TextSize.smallTitle, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), ), ), - )], - ) + ], + ), ); } } @@ -45,49 +87,71 @@ class LoadingListItem extends StatelessWidget { @override Widget build(BuildContext context) { - final textStyle = TextStyle(fontWeight: FontWeight.normal, fontSize: 16.0); return _ListItem( - child: Expanded( - child: Align( - alignment: Alignment.centerLeft, - child: Padding( - padding: EdgeInsets.all(4.0), - child: Row( - children: [ + child: Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: EdgeInsets.all(4.0), + child: Row( + children: [ CircularProgressIndicator(), Padding( - padding: EdgeInsets.only(left: 8.0), - child: Text("Ładowanie...",style: textStyle,) - )] - ) + padding: EdgeInsets.only(left: 8.0), + child: Text( + t.scan.wait, + style: TextStyle( + fontWeight: + FontWeight.w400, + fontSize: + TextSize.smallTitle, + ), + ), + ), + ], ), ), - ) + ), + showMore: false, ); } } class _ListItem extends StatelessWidget { final Widget child; + final bool showMore; - const _ListItem({required this.child}); + const _ListItem({ + required this.child, + this.showMore = true, + }); @override Widget build(BuildContext context) { return Padding( - padding: EdgeInsets.only(top: 4.0, left: 8.0, right: 8.0, bottom: 4.0), + padding: EdgeInsets.only(top: 4.0, left: 16.0, right: 8.0, bottom: 4.0), child: Container( - height: 50, + height: 40, child: DecoratedBox( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only( - topLeft: Radius.circular(5), - topRight: Radius.circular(5), - bottomRight: Radius.circular(5), - bottomLeft: Radius.circular(5)), + topLeft: Radius.circular(30), + topRight: Radius.circular(30), + bottomRight: Radius.circular(30), + bottomLeft: Radius.circular(30), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(child: child), + if (showMore) + Padding( + padding: EdgeInsets.only(right: 8.0), + child: Assets.scan.showMore.svg(), + ), + ], ), - child: child, ), ), ); diff --git a/lib/ui/menu_bottom_sheet.dart b/lib/ui/menu_bottom_sheet.dart deleted file mode 100644 index fa99322..0000000 --- a/lib/ui/menu_bottom_sheet.dart +++ /dev/null @@ -1,190 +0,0 @@ -import 'dart:io'; -import 'package:flutter/material.dart'; -import 'package:pola_flutter/analytics/analytics_about_row.dart'; -import 'package:pola_flutter/analytics/pola_analytics.dart'; -import 'package:url_launcher/url_launcher.dart'; - -class MenuBottomSheet extends StatelessWidget { - final PolaAnalytics analytics; - - const MenuBottomSheet({super.key, required this.analytics}); - - @override - Widget build(BuildContext context) { - return SingleChildScrollView( - child: Container( - decoration: BoxDecoration( - borderRadius: new BorderRadius.only( - topLeft: const Radius.circular(10.0), - topRight: const Radius.circular(10.0)), - color: Colors.white, - ), - child: Padding( - padding: - EdgeInsets.only(left: 8.0, top: 8.0, right: 8.0, bottom: 8.0), - child: Column( - children: [ - Padding( - padding: EdgeInsets.only( - left: 64.0, top: 8.0, right: 64.0, bottom: 8.0), - child: Divider( - height: 1, - thickness: 1, - indent: 64, - endIndent: 64, - color: Colors.black), - ), - _WebViewItem( - "O aplikacji Pola", - analytics, - AnalyticsAboutRow.aboutPola, - "https://www.pola-app.pl/m/about" - ), - _WebViewItem( - "Instrukcja obsługi", - analytics, - AnalyticsAboutRow.instructionSet, - "https://www.pola-app.pl/m/method" - ), - _WebViewItem( - "O Klubie Jagiellońskim", - analytics, - AnalyticsAboutRow.aboutKJ, - "https://klubjagiellonski.pl/o-klubie-jagiellonskim/" - ), - _WebViewItem( - "Zespół", - analytics, - AnalyticsAboutRow.team, - "https://www.pola-app.pl/m/team" - ), - _WebViewItem( - "Partnerzy", - analytics, - AnalyticsAboutRow.partners, - "https://www.pola-app.pl/m/partners" - ), - _WebViewItem( - "Przyjaciele Poli", - analytics, - AnalyticsAboutRow.polasFriends, - "https://www.pola-app.pl/m/friends" - ), - _ExternalUrlItem( - "GitHub", - analytics, - AnalyticsAboutRow.github, - "https://github.com/KlubJagiellonski/pola-flutter" - ), - _ExternalUrlItem( - "Oceń Polę", - analytics, - AnalyticsAboutRow.rateUs, - Platform.isIOS - ? "https://apps.apple.com/app/id1038401148" - : "https://play.google.com/store/apps/details?id=pl.pola_app" - ), - Row( - children: [ - Expanded( - child: _ExternalUrlItem( - "Facebook", - analytics, - AnalyticsAboutRow.facebook, - "https://www.facebook.com/app.pola" - ) - ), - Expanded( - child: _ExternalUrlItem( - "X", - analytics, - AnalyticsAboutRow.twitter, - "https://twitter.com/pola_app" - ) - ) - ], - ), - Padding( - padding: EdgeInsets.all(6.0), - child: Align( - alignment: Alignment.centerLeft, - child: Text("Aplikacja Pola \n©Klub Jagielloński"), - )) - ], - ), - ), - ), - ); - } -} - -class _ExternalUrlItem extends StatelessWidget { - _ExternalUrlItem(this.text, this.analytics, this.analyticsRow, this.url); - - final String text; - final PolaAnalytics analytics; - final AnalyticsAboutRow analyticsRow; - final String url; - - @override - Widget build(BuildContext context) { - return _MenuBottomItem(text, () { - analytics.aboutOpened(analyticsRow); - launchUrl( - Uri.parse(url), - mode: LaunchMode.externalApplication, - ); - }); - } -} - -class _WebViewItem extends StatelessWidget { - _WebViewItem(this.text, this.analytics, this.analyticsRow, this.url); - - final String text; - final PolaAnalytics analytics; - final AnalyticsAboutRow analyticsRow; - final String url; - - @override - Widget build(BuildContext context) { - return _MenuBottomItem(text, () { - analytics.aboutOpened(analyticsRow); - Navigator.pushNamed(context, '/web', arguments: url); - }); - } -} - -class _MenuBottomItem extends StatelessWidget { - _MenuBottomItem(this.text, this.onClick); - - final String text; - final Function onClick; - - final textStyle = TextStyle(fontWeight: FontWeight.w600); - - @override - Widget build(BuildContext context) { - return Padding( - padding: EdgeInsets.all(8.0), - child: Container( - width: double.infinity, - decoration: new BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(4.0)), - color: Colors.white, - boxShadow: [ - BoxShadow(color: Colors.black12, spreadRadius: 1), - ], - ), - child: GestureDetector( - onTap: () { - onClick.call(); - }, - child: Padding( - padding: EdgeInsets.all(10.0), - child: Text(text, style: textStyle)), - ), - ), - ); - } -} diff --git a/lib/ui/menu_icon_button.dart b/lib/ui/menu_icon_button.dart new file mode 100644 index 0000000..e9e9816 --- /dev/null +++ b/lib/ui/menu_icon_button.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:pola_flutter/analytics/analytics_about_row.dart'; +import 'package:pola_flutter/analytics/pola_analytics.dart'; +import 'package:pola_flutter/pages/menu/menu_bottom_sheet.dart'; +import 'package:pola_flutter/theme/assets.gen.dart'; + +class MenuIconButton extends StatelessWidget { + final PolaAnalytics _analytics = PolaAnalytics.instance(); + final Color color; + + MenuIconButton({super.key, required this.color}); + + @override + Widget build(BuildContext context) { + return IconButton( + onPressed: () { + _analytics.aboutOpened(AnalyticsAboutRow.menu); + showModalBottomSheet( + backgroundColor: Colors.transparent, + isScrollControlled: true, + context: context, + builder: (BuildContext context) { + return MenuBottomSheet(analytics: _analytics); + } + ); + }, + icon: Assets.menuPage.menu.svg( + colorFilter: ColorFilter.mode(color, BlendMode.srcIn) + ), + ); + } +} diff --git a/lib/ui/web_view_dialog.dart b/lib/ui/web_view_dialog.dart new file mode 100644 index 0000000..3da6a36 --- /dev/null +++ b/lib/ui/web_view_dialog.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:pola_flutter/pages/web/web_view_page.dart'; +import 'package:pola_flutter/theme/assets.gen.dart'; +import 'package:pola_flutter/theme/colors.dart'; +import 'package:pola_flutter/theme/text_size.dart'; + +showWebViewDialog({required BuildContext context, required String url, required String title}) { + showDialog( + context: context, + useSafeArea: false, + builder: (context) { + return _WebViewDialog(url: url, title: title); + }, + ); +} + +class _WebViewDialog extends StatelessWidget { + final String url; + final String title; + + const _WebViewDialog({Key? key, required this.url, required this.title}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return DraggableScrollableSheet( + initialChildSize: 0.8, + minChildSize: 0.3, + maxChildSize: 1.0, + builder: (BuildContext context, ScrollController scrollController) { + return Container( + decoration: BoxDecoration( + color: AppColors.white, + borderRadius: BorderRadius.vertical(top: Radius.circular(16.0)), + ), + child: Column( + children: [ + Padding( + padding: + const EdgeInsets.only(left: 19.0, right: 17.0, top: 19.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Assets.icLauncher.image(width: 35, height: 35), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 14.0), + child: Text( + title, + style: TextStyle( + fontSize: TextSize.newsTitle, + fontWeight: FontWeight.bold, + color: AppColors.text, + ), + ), + ), + ), + IconButton( + icon: Assets.navigation.close.svg(), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ), + ), + Expanded( + child: WebViewPage(url: url), + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/ui/web_view_tab.dart b/lib/ui/web_view_tab.dart new file mode 100644 index 0000000..e898d86 --- /dev/null +++ b/lib/ui/web_view_tab.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:pola_flutter/pages/web/web_view_page.dart'; +import 'package:pola_flutter/theme/colors.dart'; +import 'package:pola_flutter/theme/fonts.gen.dart'; + +class WebViewTab extends StatelessWidget { + final String title; + final String url; + + WebViewTab({Key? key, required this.title, required this.url}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text( + title, + style: TextStyle( + fontWeight: FontWeight.w700, + fontFamily: FontFamily.lato, + color: AppColors.text, + ), + ), + ), + body: Center( + child: WebViewPage(url: url), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index b572f98..e79b1fe 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -181,10 +181,18 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" + color: + dependency: transitive + description: + name: color + sha256: ddcdf1b3badd7008233f5acffaf20ca9f5dc2cd0172b75f68f24526a5f5725cb + url: "https://pub.dev" + source: hosted + version: "3.0.0" connectivity: dependency: "direct main" description: @@ -241,6 +249,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + csv: + dependency: transitive + description: + name: csv + sha256: c6aa2679b2a18cb57652920f674488d89712efaf4d3fdf2e537215b35fc19d6c + url: "https://pub.dev" + source: hosted + version: "6.0.0" cupertino_icons: dependency: "direct main" description: @@ -257,6 +273,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.6" + dartx: + dependency: transitive + description: + name: dartx + sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244" + url: "https://pub.dev" + source: hosted + version: "1.2.0" device_info_plus: dependency: transitive description: @@ -422,6 +446,30 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.6" + flutter_gen_core: + dependency: transitive + description: + name: flutter_gen_core + sha256: d8e828ad015a8511624491b78ad8e3f86edb7993528b1613aefbb4ad95947795 + url: "https://pub.dev" + source: hosted + version: "5.6.0" + flutter_gen_runner: + dependency: "direct dev" + description: + name: flutter_gen_runner + sha256: "931b03f77c164df0a4815aac0efc619a6ac8ec4cada55025119fca4894dada90" + url: "https://pub.dev" + source: hosted + version: "5.6.0" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" + url: "https://pub.dev" + source: hosted + version: "2.0.10+1" flutter_test: dependency: "direct dev" description: flutter @@ -472,6 +520,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.1" + hashcodes: + dependency: transitive + description: + name: hashcodes + sha256: "80f9410a5b3c8e110c4b7604546034749259f5d6dcca63e0d3c17c9258f1a651" + url: "https://pub.dev" + source: hosted + version: "2.0.0" http: dependency: "direct main" description: @@ -496,6 +552,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + image_size_getter: + dependency: transitive + description: + name: image_size_getter + sha256: f98c4246144e9b968899d2dfde69091e22a539bb64bc9b0bea51505fbb490e57 + url: "https://pub.dev" + source: hosted + version: "2.1.3" in_app_review: dependency: "direct main" description: @@ -512,6 +576,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.5" + intl: + dependency: transitive + description: + name: intl + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + url: "https://pub.dev" + source: hosted + version: "0.19.0" io: dependency: transitive description: @@ -524,10 +596,18 @@ packages: dependency: transitive description: name: js - sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json2yaml: + dependency: transitive + description: + name: json2yaml + sha256: da94630fbc56079426fdd167ae58373286f603371075b69bf46d848d63ba3e51 url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "3.0.1" json_annotation: dependency: "direct main" description: @@ -548,18 +628,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.7" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.8" leak_tracker_testing: dependency: transitive description: @@ -596,18 +676,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: @@ -664,6 +744,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918 + url: "https://pub.dev" + source: hosted + version: "8.0.2" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 + url: "https://pub.dev" + source: hosted + version: "3.0.1" path: dependency: transitive description: @@ -672,6 +768,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + url: "https://pub.dev" + source: hosted + version: "1.0.1" path_provider_linux: dependency: transitive description: @@ -696,6 +800,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" + source: hosted + version: "6.0.2" platform: dependency: transitive description: @@ -860,7 +972,23 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" + slang: + dependency: "direct main" + description: + name: slang + sha256: f68f6d6709890f85efabfb0318e9d694be2ebdd333e57fe5cb50eee449e4e3ab + url: "https://pub.dev" + source: hosted + version: "3.31.1" + slang_flutter: + dependency: "direct main" + description: + name: slang_flutter + sha256: f8400292be49c11697d94af58d7f7d054c91af759f41ffe71e4e5413871ffc62 + url: "https://pub.dev" + source: hosted + version: "3.31.0" source_gen: dependency: transitive description: @@ -913,10 +1041,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" stream_channel: dependency: transitive description: @@ -937,10 +1065,26 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" + syncfusion_flutter_core: + dependency: transitive + description: + name: syncfusion_flutter_core + sha256: fd4d2cdbf8d0d1e3441817cb8a03f896566fad5187788957e78492fe16800388 + url: "https://pub.dev" + source: hosted + version: "26.2.7" + syncfusion_flutter_gauges: + dependency: "direct main" + description: + name: syncfusion_flutter_gauges + sha256: "009f85edcb59dac20f1c5b445170de74eb65be774c6fdd4f8dc7bbc00e261f32" + url: "https://pub.dev" + source: hosted + version: "26.2.7" term_glyph: dependency: transitive description: @@ -953,26 +1097,34 @@ packages: dependency: "direct dev" description: name: test - sha256: "7ee446762c2c50b3bd4ea96fe13ffac69919352bd3b4b17bac3f3465edc58073" + sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" url: "https://pub.dev" source: hosted - version: "1.25.2" + version: "1.25.8" test_api: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.3" test_core: dependency: transitive description: name: test_core - sha256: "2bc4b4ecddd75309300d8096f781c0e3280ca1ef85beda558d33fcbedc2eead4" + sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.5" + time: + dependency: transitive + description: + name: time + sha256: ad8e018a6c9db36cb917a031853a1aae49467a93e0d464683e029537d848c221 + url: "https://pub.dev" + source: hosted + version: "2.1.4" timing: dependency: transitive description: @@ -1061,6 +1213,30 @@ packages: url: "https://pub.dev" source: hosted version: "4.4.0" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" vector_math: dependency: transitive description: @@ -1089,10 +1265,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.3.0" watcher: dependency: transitive description: @@ -1197,6 +1373,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" yaml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c07cdb3..ab1b19b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -32,11 +32,17 @@ dependencies: firebase_analytics: ^11.1.0 freezed: ^2.5.2 freezed_annotation: ^2.4.1 + slang: ^3.31.1 + slang_flutter: ^3.31.0 + syncfusion_flutter_gauges: ^26.1.42 + flutter_svg: ^2.0.9 + package_info_plus: ^8.0.2 dev_dependencies: flutter_test: sdk: flutter build_runner: ^2.0.6 + flutter_gen_runner: ^5.6.0 json_serializable: any chopper_generator: ^8.0.1 test: any @@ -48,3 +54,29 @@ flutter: assets: - assets/ + - assets/menuPage/ + - assets/fonts/ + - assets/company/ + - assets/navigation/ + - assets/scan/ + + fonts: + - family: Roboto + fonts: + - asset: assets/fonts/Roboto/Roboto-Regular.ttf + - asset: assets/fonts/Roboto/Roboto-Bold.ttf + weight: 700 + - family: Lato + fonts: + - asset: assets/fonts/Lato/Lato-Regular.ttf + - asset: assets/fonts/Lato/Lato-Bold.ttf + weight: 700 + - asset: assets/fonts/Lato/Lato-SemiBold.ttf + weight: 600 + +flutter_gen: + output: lib/theme/ + line_length: 120 + + integrations: + flutter_svg: true diff --git a/test/scan_bloc_test.dart b/test/scan_bloc_test.dart index e479f88..1784975 100644 --- a/test/scan_bloc_test.dart +++ b/test/scan_bloc_test.dart @@ -7,6 +7,7 @@ import 'package:pola_flutter/pages/scan/scan_bloc.dart'; import 'package:pola_flutter/pages/scan/scan_event.dart'; import 'package:pola_flutter/pages/scan/scan_state.dart'; import 'package:pola_flutter/pages/scan/scan_vibration.dart'; +import 'package:pola_flutter/pages/scan/torch_controller.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:test/test.dart'; @@ -20,6 +21,15 @@ void main() { expect(_scanBloc().state, ScanState()); }); + blocTest( + 'toggle torch', + build: () => _scanBloc(), + act: (bloc) => bloc.add(ScanEvent.torchSwitched()), + expect: () => [ + ScanState(isTorchOn: true), + ], + ); + blocTest( 'emits ScanLoaded([searchResult1]) when barcodeScanned(5900311000360) is added', build: () => _scanBloc(), @@ -27,7 +37,7 @@ void main() { expect: () => [ ScanState(isLoading: true), ScanState(list: [_testSearchResult], isLoading: false) - ], + ], ); blocTest( @@ -37,26 +47,22 @@ void main() { expect: () => [ ScanState(isLoading: true), ScanState(isLoading: false, isError: true) - ], + ], ); blocTest( 'emits state with no error when alert dialog dismissed', build: () => _scanBloc(state: ScanState(isError: true)), act: (bloc) => bloc.add(ScanEvent.alertDialogDismissed()), - expect: () => [ - ScanState(isError: false) - ], + expect: () => [ScanState(isError: false)], ); }); } ScanBloc _scanBloc({ScanState state = const ScanState()}) { - return ScanBloc( - _MockPolaApi(), - _MockScanVibration(), - PolaAnalytics(provider: MockAnalyticsProvider()), - state: state); + return ScanBloc(_MockPolaApi(), _MockScanVibration(), + PolaAnalytics(provider: MockAnalyticsProvider()), _MockTorchController(), + state: state); } var _testSearchResult = SearchResult( @@ -82,6 +88,10 @@ class _MockPolaApi extends PolaApi { class _MockScanVibration extends ScanVibration { @override - void vibrate() { - } + void vibrate() {} +} + +class _MockTorchController extends TorchController { + @override + void toggleTorch() {} }