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() {}
}