Khoren Markosyan
3 years ago
26 changed files with 1701 additions and 25 deletions
@ -0,0 +1,62 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:mobx/mobx.dart'; |
||||
import 'package:nb_utils/nb_utils.dart'; |
||||
|
||||
part 'app_store.g.dart'; |
||||
|
||||
AppStore appStore = AppStore(); |
||||
|
||||
class AppStore = AppStoreBase with _$AppStore; |
||||
|
||||
const themeModePref = 'themeModePref'; |
||||
const colorSchemeIndexPref = 'colorSchemeIndexPref'; |
||||
const isSoundOnPref = 'isSoundOnPref'; |
||||
const isVibrationOnPref = 'isVibrationOnPref'; |
||||
const languagePref = 'languagePref'; |
||||
|
||||
abstract class AppStoreBase with Store { |
||||
@observable |
||||
ThemeMode themeMode = ThemeMode.system; |
||||
|
||||
@observable |
||||
int colorSchemeIndex = 4; |
||||
|
||||
@observable |
||||
bool isSoundOn = true; |
||||
|
||||
@observable |
||||
bool isVibrationOn = true; |
||||
|
||||
@observable |
||||
String selectedLanguage = 'en_US'; |
||||
|
||||
@action |
||||
Future<void> setThemeMode(ThemeMode value) async { |
||||
themeMode = value; |
||||
setValue(themeModePref, themeMode.toString()); |
||||
} |
||||
|
||||
@action |
||||
Future<void> setColorSchemeIndex(int value) async { |
||||
colorSchemeIndex = value; |
||||
await setValue(colorSchemeIndexPref, colorSchemeIndex); |
||||
} |
||||
|
||||
@action |
||||
Future<void> toggleSoundMode({bool? value}) async { |
||||
isSoundOn = value ?? !isSoundOn; |
||||
setValue(isSoundOnPref, isSoundOn); |
||||
} |
||||
|
||||
@action |
||||
Future<void> toggleVibrationMode({bool? value}) async { |
||||
isVibrationOn = value ?? !isVibrationOn; |
||||
setValue(isVibrationOnPref, isVibrationOn); |
||||
} |
||||
|
||||
@action |
||||
Future<void> setLanguage(String aLanguage) async { |
||||
selectedLanguage = aLanguage; |
||||
await setValue(languagePref, aLanguage); |
||||
} |
||||
} |
@ -0,0 +1,138 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND |
||||
|
||||
part of 'app_store.dart'; |
||||
|
||||
// ************************************************************************** |
||||
// StoreGenerator |
||||
// ************************************************************************** |
||||
|
||||
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic |
||||
|
||||
mixin _$AppStore on AppStoreBase, Store { |
||||
final _$themeModeAtom = Atom(name: 'AppStoreBase.themeMode'); |
||||
|
||||
@override |
||||
ThemeMode get themeMode { |
||||
_$themeModeAtom.reportRead(); |
||||
return super.themeMode; |
||||
} |
||||
|
||||
@override |
||||
set themeMode(ThemeMode value) { |
||||
_$themeModeAtom.reportWrite(value, super.themeMode, () { |
||||
super.themeMode = value; |
||||
}); |
||||
} |
||||
|
||||
final _$colorSchemeIndexAtom = Atom(name: 'AppStoreBase.colorSchemeIndex'); |
||||
|
||||
@override |
||||
int get colorSchemeIndex { |
||||
_$colorSchemeIndexAtom.reportRead(); |
||||
return super.colorSchemeIndex; |
||||
} |
||||
|
||||
@override |
||||
set colorSchemeIndex(int value) { |
||||
_$colorSchemeIndexAtom.reportWrite(value, super.colorSchemeIndex, () { |
||||
super.colorSchemeIndex = value; |
||||
}); |
||||
} |
||||
|
||||
final _$isSoundOnAtom = Atom(name: 'AppStoreBase.isSoundOn'); |
||||
|
||||
@override |
||||
bool get isSoundOn { |
||||
_$isSoundOnAtom.reportRead(); |
||||
return super.isSoundOn; |
||||
} |
||||
|
||||
@override |
||||
set isSoundOn(bool value) { |
||||
_$isSoundOnAtom.reportWrite(value, super.isSoundOn, () { |
||||
super.isSoundOn = value; |
||||
}); |
||||
} |
||||
|
||||
final _$isVibrationOnAtom = Atom(name: 'AppStoreBase.isVibrationOn'); |
||||
|
||||
@override |
||||
bool get isVibrationOn { |
||||
_$isVibrationOnAtom.reportRead(); |
||||
return super.isVibrationOn; |
||||
} |
||||
|
||||
@override |
||||
set isVibrationOn(bool value) { |
||||
_$isVibrationOnAtom.reportWrite(value, super.isVibrationOn, () { |
||||
super.isVibrationOn = value; |
||||
}); |
||||
} |
||||
|
||||
final _$selectedLanguageAtom = Atom(name: 'AppStoreBase.selectedLanguage'); |
||||
|
||||
@override |
||||
String get selectedLanguage { |
||||
_$selectedLanguageAtom.reportRead(); |
||||
return super.selectedLanguage; |
||||
} |
||||
|
||||
@override |
||||
set selectedLanguage(String value) { |
||||
_$selectedLanguageAtom.reportWrite(value, super.selectedLanguage, () { |
||||
super.selectedLanguage = value; |
||||
}); |
||||
} |
||||
|
||||
final _$setThemeModeAsyncAction = AsyncAction('AppStoreBase.setThemeMode'); |
||||
|
||||
@override |
||||
Future<void> setThemeMode(ThemeMode value) { |
||||
return _$setThemeModeAsyncAction.run(() => super.setThemeMode(value)); |
||||
} |
||||
|
||||
final _$setColorSchemeIndexAsyncAction = |
||||
AsyncAction('AppStoreBase.setColorSchemeIndex'); |
||||
|
||||
@override |
||||
Future<void> setColorSchemeIndex(int value) { |
||||
return _$setColorSchemeIndexAsyncAction |
||||
.run(() => super.setColorSchemeIndex(value)); |
||||
} |
||||
|
||||
final _$toggleSoundModeAsyncAction = |
||||
AsyncAction('AppStoreBase.toggleSoundMode'); |
||||
|
||||
@override |
||||
Future<void> toggleSoundMode({bool? value}) { |
||||
return _$toggleSoundModeAsyncAction |
||||
.run(() => super.toggleSoundMode(value: value)); |
||||
} |
||||
|
||||
final _$toggleVibrationModeAsyncAction = |
||||
AsyncAction('AppStoreBase.toggleVibrationMode'); |
||||
|
||||
@override |
||||
Future<void> toggleVibrationMode({bool? value}) { |
||||
return _$toggleVibrationModeAsyncAction |
||||
.run(() => super.toggleVibrationMode(value: value)); |
||||
} |
||||
|
||||
final _$setLanguageAsyncAction = AsyncAction('AppStoreBase.setLanguage'); |
||||
|
||||
@override |
||||
Future<void> setLanguage(String aLanguage) { |
||||
return _$setLanguageAsyncAction.run(() => super.setLanguage(aLanguage)); |
||||
} |
||||
|
||||
@override |
||||
String toString() { |
||||
return ''' |
||||
themeMode: ${themeMode}, |
||||
colorSchemeIndex: ${colorSchemeIndex}, |
||||
isSoundOn: ${isSoundOn}, |
||||
isVibrationOn: ${isVibrationOn}, |
||||
selectedLanguage: ${selectedLanguage} |
||||
'''; |
||||
} |
||||
} |
@ -0,0 +1,22 @@
|
||||
import 'package:flex_color_scheme/flex_color_scheme.dart'; |
||||
import 'package:flutter/material.dart'; |
||||
|
||||
import 'app_store.dart'; |
||||
|
||||
class AppTheme { |
||||
AppTheme._(); |
||||
|
||||
static ThemeData flexLightTheme() => FlexThemeData.light( |
||||
colors: FlexColor.schemesList[appStore.colorSchemeIndex].light, |
||||
tabBarStyle: FlexTabBarStyle.forAppBar, |
||||
surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, |
||||
blendLevel: 12, |
||||
); |
||||
|
||||
static ThemeData flexDarkTheme() => FlexThemeData.dark( |
||||
colors: FlexColor.schemesList[appStore.colorSchemeIndex].dark, |
||||
tabBarStyle: FlexTabBarStyle.forAppBar, |
||||
surfaceMode: FlexSurfaceMode.highScaffoldLevelSurface, |
||||
blendLevel: 6, |
||||
); |
||||
} |
@ -0,0 +1,13 @@
|
||||
const String appName = 'ZxScanner'; |
||||
|
||||
/// space between widgets |
||||
const spaceSmall = 2.0; |
||||
const spaceSmall2 = 4.0; |
||||
const spaceMedium = 8.0; |
||||
const spaceDefault = 16.0; |
||||
const spaceLarge = 24.0; |
||||
const spaceLarge2 = 32.0; |
||||
const spaceLarge3 = 40.0; |
||||
const spaceLarge4 = 96.0; |
||||
|
||||
const spaceMaxWidth = 736.0; |
@ -0,0 +1,62 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart |
||||
// This is a library that looks up messages for specific locales by |
||||
// delegating to the appropriate library. |
||||
|
||||
// Ignore issues from commonly used lints in this file. |
||||
// ignore_for_file:implementation_imports, file_names, unnecessary_new |
||||
// ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering |
||||
// ignore_for_file:argument_type_not_assignable, invalid_assignment |
||||
// ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases |
||||
// ignore_for_file:comment_references |
||||
|
||||
import 'dart:async'; |
||||
|
||||
import 'package:intl/intl.dart'; |
||||
import 'package:intl/message_lookup_by_library.dart'; |
||||
import 'package:intl/src/intl_helpers.dart'; |
||||
|
||||
import 'messages_en_US.dart' as messages_en_us; |
||||
|
||||
typedef Future<dynamic> LibraryLoader(); |
||||
Map<String, LibraryLoader> _deferredLibraries = { |
||||
'en_US': () => new Future.value(null), |
||||
}; |
||||
|
||||
MessageLookupByLibrary? _findExact(String localeName) { |
||||
switch (localeName) { |
||||
case 'en_US': |
||||
return messages_en_us.messages; |
||||
default: |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
/// User programs should call this before using [localeName] for messages. |
||||
Future<bool> initializeMessages(String localeName) async { |
||||
var availableLocale = Intl.verifiedLocale( |
||||
localeName, (locale) => _deferredLibraries[locale] != null, |
||||
onFailure: (_) => null); |
||||
if (availableLocale == null) { |
||||
return new Future.value(false); |
||||
} |
||||
var lib = _deferredLibraries[availableLocale]; |
||||
await (lib == null ? new Future.value(false) : lib()); |
||||
initializeInternalMessageLookup(() => new CompositeMessageLookup()); |
||||
messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor); |
||||
return new Future.value(true); |
||||
} |
||||
|
||||
bool _messagesExistFor(String locale) { |
||||
try { |
||||
return _findExact(locale) != null; |
||||
} catch (e) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) { |
||||
var actualLocale = |
||||
Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null); |
||||
if (actualLocale == null) return null; |
||||
return _findExact(actualLocale); |
||||
} |
@ -0,0 +1,30 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart |
||||
// This is a library that provides messages for a en_US locale. All the |
||||
// messages from the main program should be duplicated here with the same |
||||
// function name. |
||||
|
||||
// Ignore issues from commonly used lints in this file. |
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new |
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering |
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases |
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes |
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes |
||||
|
||||
import 'package:intl/intl.dart'; |
||||
import 'package:intl/message_lookup_by_library.dart'; |
||||
|
||||
final messages = new MessageLookup(); |
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args); |
||||
|
||||
class MessageLookup extends MessageLookupByLibrary { |
||||
String get localeName => 'en_US'; |
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages); |
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{ |
||||
"settingsAppBarTitle": MessageLookupByLibrary.simpleMessage("Settings"), |
||||
"settingsLanguageTitle": |
||||
MessageLookupByLibrary.simpleMessage("Language"), |
||||
"settingsThemeModeTitle": MessageLookupByLibrary.simpleMessage("Theme") |
||||
}; |
||||
} |
@ -0,0 +1,108 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND |
||||
import 'package:flutter/material.dart'; |
||||
import 'package:intl/intl.dart'; |
||||
import 'intl/messages_all.dart'; |
||||
|
||||
// ************************************************************************** |
||||
// Generator: Flutter Intl IDE plugin |
||||
// Made by Localizely |
||||
// ************************************************************************** |
||||
|
||||
// ignore_for_file: non_constant_identifier_names, lines_longer_than_80_chars |
||||
// ignore_for_file: join_return_with_assignment, prefer_final_in_for_each |
||||
// ignore_for_file: avoid_redundant_argument_values, avoid_escaping_inner_quotes |
||||
|
||||
class S { |
||||
S(); |
||||
|
||||
static S? _current; |
||||
|
||||
static S get current { |
||||
assert(_current != null, |
||||
'No instance of S was loaded. Try to initialize the S delegate before accessing S.current.'); |
||||
return _current!; |
||||
} |
||||
|
||||
static const AppLocalizationDelegate delegate = AppLocalizationDelegate(); |
||||
|
||||
static Future<S> load(Locale locale) { |
||||
final name = (locale.countryCode?.isEmpty ?? false) |
||||
? locale.languageCode |
||||
: locale.toString(); |
||||
final localeName = Intl.canonicalizedLocale(name); |
||||
return initializeMessages(localeName).then((_) { |
||||
Intl.defaultLocale = localeName; |
||||
final instance = S(); |
||||
S._current = instance; |
||||
|
||||
return instance; |
||||
}); |
||||
} |
||||
|
||||
static S of(BuildContext context) { |
||||
final instance = S.maybeOf(context); |
||||
assert(instance != null, |
||||
'No instance of S present in the widget tree. Did you add S.delegate in localizationsDelegates?'); |
||||
return instance!; |
||||
} |
||||
|
||||
static S? maybeOf(BuildContext context) { |
||||
return Localizations.of<S>(context, S); |
||||
} |
||||
|
||||
/// `Settings` |
||||
String get settingsAppBarTitle { |
||||
return Intl.message( |
||||
'Settings', |
||||
name: 'settingsAppBarTitle', |
||||
desc: '', |
||||
args: [], |
||||
); |
||||
} |
||||
|
||||
/// `Theme` |
||||
String get settingsThemeModeTitle { |
||||
return Intl.message( |
||||
'Theme', |
||||
name: 'settingsThemeModeTitle', |
||||
desc: '', |
||||
args: [], |
||||
); |
||||
} |
||||
|
||||
/// `Language` |
||||
String get settingsLanguageTitle { |
||||
return Intl.message( |
||||
'Language', |
||||
name: 'settingsLanguageTitle', |
||||
desc: '', |
||||
args: [], |
||||
); |
||||
} |
||||
} |
||||
|
||||
class AppLocalizationDelegate extends LocalizationsDelegate<S> { |
||||
const AppLocalizationDelegate(); |
||||
|
||||
List<Locale> get supportedLocales { |
||||
return const <Locale>[ |
||||
Locale.fromSubtags(languageCode: 'en', countryCode: 'US'), |
||||
]; |
||||
} |
||||
|
||||
@override |
||||
bool isSupported(Locale locale) => _isSupported(locale); |
||||
@override |
||||
Future<S> load(Locale locale) => S.load(locale); |
||||
@override |
||||
bool shouldReload(AppLocalizationDelegate old) => false; |
||||
|
||||
bool _isSupported(Locale locale) { |
||||
for (var supportedLocale in supportedLocales) { |
||||
if (supportedLocale.languageCode == locale.languageCode) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
} |
@ -0,0 +1,6 @@
|
||||
{ |
||||
"@@locale": "en_US", |
||||
"settingsAppBarTitle": "Settings", |
||||
"settingsThemeModeTitle": "Theme", |
||||
"settingsLanguageTitle": "Language" |
||||
} |
@ -0,0 +1,200 @@
|
||||
import 'dart:io'; |
||||
import 'dart:typed_data'; |
||||
|
||||
import 'package:camera/camera.dart'; |
||||
import 'package:flutter/material.dart'; |
||||
import 'package:flutter/services.dart'; |
||||
import 'package:flutter_zxing/flutter_zxing.dart'; |
||||
import 'package:flutter_zxing/generated_bindings.dart'; |
||||
import 'package:flutter_zxing/zxing_reader_widget.dart'; |
||||
import 'package:flutter_zxing/zxing_writer_widget.dart'; |
||||
import 'package:path_provider/path_provider.dart'; |
||||
import 'package:share_plus/share_plus.dart'; |
||||
|
||||
late Directory tempDir; |
||||
String get tempPath => '${tempDir.path}/zxing.jpg'; |
||||
|
||||
class CreatorPage extends StatefulWidget { |
||||
const CreatorPage({ |
||||
Key? key, |
||||
}) : super(key: key); |
||||
|
||||
@override |
||||
State<CreatorPage> createState() => _CreatorPageState(); |
||||
} |
||||
|
||||
class _CreatorPageState extends State<CreatorPage> |
||||
with TickerProviderStateMixin { |
||||
CameraController? controller; |
||||
TabController? _tabController; |
||||
|
||||
bool isAndroid() => Theme.of(context).platform == TargetPlatform.android; |
||||
|
||||
// Scan result queue |
||||
final _resultQueue = <CodeResult>[]; |
||||
|
||||
// Write result |
||||
Uint8List? writeResult; |
||||
|
||||
// true when the camera is active |
||||
bool _isScanning = true; |
||||
|
||||
@override |
||||
void initState() { |
||||
super.initState(); |
||||
|
||||
initStateAsync(); |
||||
} |
||||
|
||||
void initStateAsync() async { |
||||
_tabController = TabController(length: 3, vsync: this); |
||||
_tabController?.addListener(() { |
||||
_isScanning = _tabController?.index == 0; |
||||
if (_isScanning) { |
||||
controller?.resumePreview(); |
||||
} else { |
||||
controller?.pausePreview(); |
||||
} |
||||
}); |
||||
getTemporaryDirectory().then((value) { |
||||
tempDir = value; |
||||
}); |
||||
} |
||||
|
||||
@override |
||||
void dispose() { |
||||
super.dispose(); |
||||
controller?.dispose(); |
||||
} |
||||
|
||||
void showInSnackBar(String message) {} |
||||
|
||||
void logError(String code, String? message) { |
||||
if (message != null) { |
||||
debugPrint('Error: $code\nError Message: $message'); |
||||
} else { |
||||
debugPrint('Error: $code'); |
||||
} |
||||
} |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Scaffold( |
||||
appBar: AppBar( |
||||
title: const Text('Creator'), |
||||
bottom: TabBar( |
||||
controller: _tabController, |
||||
tabs: const [ |
||||
Tab(text: 'Scanner'), |
||||
Tab(text: 'Result'), |
||||
Tab(text: 'Writer'), |
||||
], |
||||
), |
||||
), |
||||
body: TabBarView( |
||||
controller: _tabController, |
||||
children: [ |
||||
// Scanner |
||||
ZxingReaderWidget(onScan: (result) async { |
||||
_resultQueue.insert(0, result); |
||||
_tabController?.index = 1; |
||||
await Future.delayed(const Duration(milliseconds: 500)); |
||||
setState(() {}); |
||||
}), |
||||
// Result |
||||
_buildResultList(), |
||||
// Writer |
||||
SingleChildScrollView( |
||||
child: Column( |
||||
children: [ |
||||
ZxingWriterWidget( |
||||
onSuccess: (result) { |
||||
setState(() { |
||||
writeResult = result; |
||||
}); |
||||
}, |
||||
onError: (error) { |
||||
setState(() { |
||||
writeResult = null; |
||||
}); |
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar(); |
||||
ScaffoldMessenger.of(context).showSnackBar( |
||||
SnackBar( |
||||
content: Text( |
||||
error, |
||||
textAlign: TextAlign.center, |
||||
), |
||||
), |
||||
); |
||||
}, |
||||
), |
||||
if (writeResult != null) buildWriteResult(), |
||||
], |
||||
), |
||||
), |
||||
], |
||||
), |
||||
); |
||||
} |
||||
|
||||
Column buildWriteResult() { |
||||
return Column( |
||||
children: [ |
||||
// Barcode image |
||||
Image.memory(writeResult ?? Uint8List(0)), |
||||
// Share button |
||||
ElevatedButton( |
||||
onPressed: () { |
||||
// Save image to device |
||||
final file = File(tempPath); |
||||
file.writeAsBytesSync(writeResult ?? Uint8List(0)); |
||||
final path = file.path; |
||||
// Share image |
||||
Share.shareFiles([path]); |
||||
}, |
||||
child: const Text('Share'), |
||||
), |
||||
], |
||||
); |
||||
} |
||||
|
||||
_buildResultList() { |
||||
return _resultQueue.isEmpty |
||||
? const Center( |
||||
child: Text( |
||||
'No Results', |
||||
style: TextStyle(fontSize: 24), |
||||
)) |
||||
: ListView.builder( |
||||
itemCount: _resultQueue.length, |
||||
itemBuilder: (context, index) { |
||||
final result = _resultQueue[index]; |
||||
return ListTile( |
||||
title: Text(result.textString), |
||||
subtitle: Text(result.formatString), |
||||
trailing: ButtonBar( |
||||
mainAxisSize: MainAxisSize.min, |
||||
children: [ |
||||
// Copy button |
||||
TextButton( |
||||
child: const Text('Copy'), |
||||
onPressed: () { |
||||
Clipboard.setData( |
||||
ClipboardData(text: result.textString)); |
||||
}, |
||||
), |
||||
// Remove button |
||||
IconButton( |
||||
icon: const Icon(Icons.delete, color: Colors.red), |
||||
onPressed: () { |
||||
_resultQueue.removeAt(index); |
||||
setState(() {}); |
||||
}, |
||||
), |
||||
], |
||||
), |
||||
); |
||||
}, |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,60 @@
|
||||
import 'package:convex_bottom_bar/convex_bottom_bar.dart'; |
||||
import 'package:flutter/material.dart'; |
||||
import 'package:flutter_zxing_example/pages/creator_page.dart'; |
||||
import 'package:flutter_zxing_example/pages/history_page.dart'; |
||||
import 'package:flutter_zxing_example/pages/scanner_page.dart'; |
||||
import 'package:flutter_zxing_example/pages/settings_page.dart'; |
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; |
||||
|
||||
class HomePage extends StatefulWidget { |
||||
const HomePage({Key? key}) : super(key: key); |
||||
|
||||
@override |
||||
State<HomePage> createState() => _HomePageState(); |
||||
} |
||||
|
||||
class _HomePageState extends State<HomePage> { |
||||
var selectedIndex = 2; |
||||
|
||||
final creatorPage = const CreatorPage(); |
||||
final historyPage = const HistoryPage(); |
||||
final scannerPage = const ScannerPage(); |
||||
final notificationsPage = const ScannerPage(); |
||||
final settingsPage = const SettingsPage(); |
||||
|
||||
dynamic pages() => [ |
||||
creatorPage, |
||||
historyPage, |
||||
scannerPage, |
||||
notificationsPage, |
||||
settingsPage, |
||||
]; |
||||
|
||||
dynamic tabItems() => [ |
||||
const TabItem(icon: FontAwesomeIcons.barcode), |
||||
const TabItem(icon: FontAwesomeIcons.clockRotateLeft), |
||||
const TabItem(icon: Icons.qr_code_scanner), |
||||
const TabItem(icon: FontAwesomeIcons.solidBell), |
||||
const TabItem(icon: FontAwesomeIcons.gear), |
||||
]; |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Scaffold( |
||||
body: pages()[selectedIndex], |
||||
bottomNavigationBar: ConvexAppBar( |
||||
style: TabStyle.fixedCircle, |
||||
backgroundColor: Theme.of(context).bottomAppBarColor, |
||||
color: Theme.of(context).unselectedWidgetColor, |
||||
activeColor: Theme.of(context).colorScheme.primary, |
||||
items: tabItems(), |
||||
initialActiveIndex: selectedIndex, |
||||
onTap: (index) { |
||||
setState(() { |
||||
selectedIndex = index; |
||||
}); |
||||
}, |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,70 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:flutter_zxing/zxing_reader_widget.dart'; |
||||
|
||||
class ScannerPage extends StatefulWidget { |
||||
const ScannerPage({ |
||||
Key? key, |
||||
}) : super(key: key); |
||||
|
||||
@override |
||||
State<ScannerPage> createState() => _ScannerPageState(); |
||||
} |
||||
|
||||
class _ScannerPageState extends State<ScannerPage> |
||||
with TickerProviderStateMixin { |
||||
TabController? _tabController; |
||||
|
||||
@override |
||||
void initState() { |
||||
super.initState(); |
||||
|
||||
initStateAsync(); |
||||
} |
||||
|
||||
void initStateAsync() async { |
||||
_tabController = TabController(length: 3, vsync: this); |
||||
} |
||||
|
||||
@override |
||||
void dispose() { |
||||
super.dispose(); |
||||
_tabController?.dispose(); |
||||
} |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Scaffold( |
||||
appBar: AppBar( |
||||
title: const Text('Scanner'), |
||||
bottom: TabBar( |
||||
controller: _tabController, |
||||
tabs: const [ |
||||
Tab(text: 'Single code'), |
||||
Tab(text: 'Multi code'), |
||||
Tab(text: 'Image'), |
||||
], |
||||
), |
||||
), |
||||
body: TabBarView( |
||||
controller: _tabController, |
||||
children: [ |
||||
// Single code scanner |
||||
ZxingReaderWidget(onScan: (result) async { |
||||
// _resultQueue.insert(0, result); |
||||
// await Future.delayed(const Duration(milliseconds: 500)); |
||||
// setState(() {}); |
||||
}), |
||||
// Multi code scanner |
||||
Container(), |
||||
// ZxingReaderWidget(onScan: (result) async { |
||||
// // _resultQueue.insert(0, result); |
||||
// // await Future.delayed(const Duration(milliseconds: 500)); |
||||
// // setState(() {}); |
||||
// }), |
||||
// Image scanner |
||||
Container(), |
||||
], |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,65 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:flutter_zxing_example/configs/app_store.dart'; |
||||
import 'package:flutter_zxing_example/configs/constants.dart'; |
||||
import 'package:flutter_zxing_example/generated/l10n.dart'; |
||||
import 'package:flutter_zxing_example/widgets/common_widgets.dart'; |
||||
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; |
||||
|
||||
import '../widgets/setting_tile.dart'; |
||||
import '../widgets/language_widget.dart'; |
||||
import '../widgets/theme_mode_switch.dart'; |
||||
import '../widgets/theme_selector.dart'; |
||||
|
||||
class SettingsPage extends StatefulWidget { |
||||
const SettingsPage({Key? key}) : super(key: key); |
||||
|
||||
@override |
||||
State<SettingsPage> createState() => _SettingsPageState(); |
||||
} |
||||
|
||||
class _SettingsPageState extends State<SettingsPage> { |
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Scaffold( |
||||
appBar: AppBar( |
||||
title: Text(S.current.settingsAppBarTitle), |
||||
), |
||||
body: SingleChildScrollView( |
||||
child: ContainerX( |
||||
child: Column( |
||||
children: [ |
||||
const SizedBox(height: spaceDefault), |
||||
const Padding( |
||||
padding: EdgeInsets.symmetric(horizontal: spaceDefault), |
||||
child: ThemeSelector(), |
||||
), |
||||
SettingTile( |
||||
context, |
||||
leading: Icons.brightness_4, |
||||
title: S.current.settingsThemeModeTitle, |
||||
trailing: ThemeModeSwitch( |
||||
themeMode: appStore.themeMode, |
||||
onChanged: (mode) { |
||||
appStore.setThemeMode(mode); |
||||
setState(() {}); |
||||
}, |
||||
), |
||||
), |
||||
SettingTile( |
||||
context, |
||||
leading: FontAwesomeIcons.globe, |
||||
title: S.current.settingsLanguageTitle, |
||||
trailing: LanguageWidget( |
||||
onChanged: (value) { |
||||
setState(() {}); |
||||
}, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
), |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,48 @@
|
||||
import 'dart:ui'; |
||||
|
||||
extension LocaleParsing on String { |
||||
Locale parseLocale() { |
||||
assert(contains('_') == true); |
||||
var languageCode = split('_').first; |
||||
var countryCode = split('_').last; |
||||
return Locale.fromSubtags( |
||||
languageCode: languageCode, countryCode: countryCode); |
||||
} |
||||
|
||||
String toLangIcon() { |
||||
assert(length == 2); |
||||
switch (toLowerCase()) { |
||||
case 'us': |
||||
case 'en': |
||||
return '🇺🇸'; |
||||
case 'ru': |
||||
return '🇷🇺'; |
||||
case 'am': |
||||
case 'hy': |
||||
return '🇦🇲'; |
||||
default: |
||||
return '🇺🇸'; |
||||
} |
||||
} |
||||
|
||||
String toLangName() { |
||||
assert(length == 2); |
||||
switch (toLowerCase()) { |
||||
case 'us': |
||||
case 'en': |
||||
return toLangIcon() + ' English'; |
||||
case 'ru': |
||||
return toLangIcon() + ' Русский'; |
||||
case 'am': |
||||
case 'hy': |
||||
return toLangIcon() + ' Հայերեն'; |
||||
default: |
||||
return toLangIcon() + ' English'; |
||||
} |
||||
} |
||||
|
||||
String toLangCode() { |
||||
assert(contains('_') == true); |
||||
return split('_').first; |
||||
} |
||||
} |
@ -0,0 +1,45 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:flutter_zxing_example/pages/creator_page.dart'; |
||||
import 'package:flutter_zxing_example/pages/history_page.dart'; |
||||
import 'package:flutter_zxing_example/pages/home_page.dart'; |
||||
import 'package:flutter_zxing_example/pages/scanner_page.dart'; |
||||
import 'package:flutter_zxing_example/pages/settings_page.dart'; |
||||
|
||||
abstract class AppRoutes { |
||||
static const creator = '/creator'; |
||||
static const history = '/history'; |
||||
static const home = '/'; |
||||
static const scanner = '/scanner'; |
||||
static const settings = '/settings'; |
||||
} |
||||
|
||||
class AppRouter { |
||||
Route onGenerateRoute(RouteSettings settings) { |
||||
switch (settings.name) { |
||||
case AppRoutes.creator: |
||||
return MaterialPageRoute( |
||||
builder: (_) => const CreatorPage(), |
||||
); |
||||
case AppRoutes.history: |
||||
return MaterialPageRoute( |
||||
builder: (_) => const HistoryPage(), |
||||
); |
||||
case AppRoutes.home: |
||||
return MaterialPageRoute( |
||||
builder: (_) => const HomePage(), |
||||
); |
||||
case AppRoutes.scanner: |
||||
return MaterialPageRoute( |
||||
builder: (_) => const ScannerPage(), |
||||
); |
||||
case AppRoutes.settings: |
||||
return MaterialPageRoute( |
||||
builder: (_) => const SettingsPage(), |
||||
); |
||||
default: |
||||
return MaterialPageRoute( |
||||
builder: (_) => Container(), |
||||
); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,10 @@
|
||||
import 'package:flutter/gestures.dart'; |
||||
import 'package:flutter/material.dart'; |
||||
|
||||
class MyCustomScrollBehavior extends MaterialScrollBehavior { |
||||
@override |
||||
Set<PointerDeviceKind> get dragDevices => { |
||||
PointerDeviceKind.touch, |
||||
PointerDeviceKind.mouse, |
||||
}; |
||||
} |
@ -0,0 +1,18 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:flutter_zxing_example/configs/constants.dart'; |
||||
|
||||
class ContainerX extends StatelessWidget { |
||||
const ContainerX({Key? key, this.child}) : super(key: key); |
||||
|
||||
final Widget? child; |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Center( |
||||
child: Container( |
||||
constraints: const BoxConstraints(maxWidth: spaceMaxWidth), |
||||
child: child, |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,48 @@
|
||||
import 'package:flutter/material.dart'; |
||||
import 'package:flutter_zxing_example/configs/app_store.dart'; |
||||
import 'package:flutter_zxing_example/generated/l10n.dart'; |
||||
import 'package:flutter_zxing_example/utils/extensions.dart'; |
||||
|
||||
class LanguageWidget extends StatelessWidget { |
||||
const LanguageWidget({Key? key, required this.onChanged}) : super(key: key); |
||||
|
||||
final Function(Locale) onChanged; |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return DropdownButton<Locale>( |
||||
value: appStore.selectedLanguage.parseLocale(), |
||||
isExpanded: false, |
||||
isDense: false, |
||||
underline: const SizedBox(), |
||||
onChanged: (value) async { |
||||
if (value != null) { |
||||
await S.load(value); |
||||
appStore.setLanguage(value.toString()); |
||||
onChanged(value); |
||||
} |
||||
}, |
||||
selectedItemBuilder: (context) { |
||||
return S.delegate.supportedLocales |
||||
.map((e) => DropdownMenuItem<Locale>( |
||||
value: e, |
||||
child: SizedBox( |
||||
width: 80, |
||||
child: Text( |
||||
e.countryCode?.toLangIcon() ?? '', |
||||
style: Theme.of(context).textTheme.headline5, |
||||
textAlign: TextAlign.right, |
||||
), |
||||
), |
||||
)) |
||||
.toList(); |
||||
}, |
||||
items: S.delegate.supportedLocales |
||||
.map((e) => DropdownMenuItem( |
||||
value: e, |
||||
child: Text(e.countryCode?.toLangName() ?? ''), |
||||
)) |
||||
.toList(), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,35 @@
|
||||
import 'package:flutter/material.dart'; |
||||
|
||||
class SettingTile extends StatelessWidget { |
||||
const SettingTile( |
||||
this.context, { |
||||
Key? key, |
||||
this.leading, |
||||
this.title, |
||||
this.trailing, |
||||
this.onTap, |
||||
}) : super(key: key); |
||||
|
||||
final BuildContext context; |
||||
final IconData? leading; |
||||
final String? title; |
||||
final Widget? trailing; |
||||
final Function()? onTap; |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
return Card( |
||||
child: ListTile( |
||||
leading: leading != null |
||||
? Icon( |
||||
leading, |
||||
color: Theme.of(context).colorScheme.secondary, |
||||
) |
||||
: null, |
||||
title: Text(title ?? ''), |
||||
trailing: trailing, |
||||
onTap: onTap, |
||||
), |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,42 @@
|
||||
import 'package:flutter/material.dart'; |
||||
|
||||
/// Widget using [ToggleButtons) that can be used to toggle the theme mode |
||||
/// of an application. |
||||
/// |
||||
/// This is a simple Flutter "Universal" Widget that only depends on the SDK and |
||||
/// can be dropped into any application. |
||||
class ThemeModeSwitch extends StatelessWidget { |
||||
const ThemeModeSwitch({ |
||||
Key? key, |
||||
required this.themeMode, |
||||
required this.onChanged, |
||||
}) : super(key: key); |
||||
final ThemeMode themeMode; |
||||
final ValueChanged<ThemeMode> onChanged; |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
final List<bool> isSelected = <bool>[ |
||||
themeMode == ThemeMode.light, |
||||
themeMode == ThemeMode.system, |
||||
themeMode == ThemeMode.dark, |
||||
]; |
||||
return ToggleButtons( |
||||
isSelected: isSelected, |
||||
onPressed: (int newIndex) { |
||||
if (newIndex == 0) { |
||||
onChanged(ThemeMode.light); |
||||
} else if (newIndex == 1) { |
||||
onChanged(ThemeMode.system); |
||||
} else { |
||||
onChanged(ThemeMode.dark); |
||||
} |
||||
}, |
||||
children: const <Widget>[ |
||||
Icon(Icons.wb_sunny), |
||||
Icon(Icons.phone_iphone), |
||||
Icon(Icons.bedtime), |
||||
], |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,106 @@
|
||||
import 'package:flex_color_scheme/flex_color_scheme.dart'; |
||||
import 'package:flutter/material.dart'; |
||||
import 'package:flutter_zxing_example/configs/app_store.dart'; |
||||
|
||||
// The width size of the scrolling button. |
||||
const double _kWidthOfScrollItem = 71.6; |
||||
|
||||
// Horizontal theme selector of themes offered in our [AppColor.schemes]. |
||||
// |
||||
// This example uses a StatefulWidget for the scroll controller and |
||||
// index to keep track of previously selected color scheme, so we can animate |
||||
// to the new selection, also done when the themeController schemeIndex is |
||||
// changed via other UI controls, like in the drop box that is also used in |
||||
// this demo as another way to change the color scheme. |
||||
// |
||||
// The theme is controlled via the passed in ThemeController. |
||||
class ThemeSelector extends StatefulWidget { |
||||
const ThemeSelector({ |
||||
Key? key, |
||||
}) : super(key: key); |
||||
|
||||
@override |
||||
_ThemeSelectorState createState() => _ThemeSelectorState(); |
||||
} |
||||
|
||||
class _ThemeSelectorState extends State<ThemeSelector> { |
||||
late ScrollController scrollController; |
||||
late int schemeIndex; |
||||
|
||||
@override |
||||
void initState() { |
||||
super.initState(); |
||||
schemeIndex = appStore.colorSchemeIndex; |
||||
scrollController = ScrollController( |
||||
keepScrollOffset: true, |
||||
initialScrollOffset: _kWidthOfScrollItem * schemeIndex, |
||||
); |
||||
} |
||||
|
||||
@override |
||||
void dispose() { |
||||
scrollController.dispose(); |
||||
super.dispose(); |
||||
} |
||||
|
||||
@override |
||||
void didChangeDependencies() { |
||||
// Index got updated in popup and deps changed, animate it to new index. |
||||
if (appStore.colorSchemeIndex != schemeIndex) { |
||||
schemeIndex = appStore.colorSchemeIndex; |
||||
scrollController.animateTo(_kWidthOfScrollItem * schemeIndex, |
||||
duration: const Duration(milliseconds: 350), |
||||
curve: Curves.easeOutCubic); |
||||
} |
||||
super.didChangeDependencies(); |
||||
} |
||||
|
||||
@override |
||||
Widget build(BuildContext context) { |
||||
final bool isLight = Theme.of(context).brightness == Brightness.light; |
||||
return SizedBox( |
||||
height: 76, |
||||
child: Row( |
||||
children: <Widget>[ |
||||
Expanded( |
||||
child: ListView.builder( |
||||
controller: scrollController, |
||||
physics: const ClampingScrollPhysics(), |
||||
scrollDirection: Axis.horizontal, |
||||
itemCount: FlexColor.schemesList.length, |
||||
itemBuilder: (BuildContext context, int index) { |
||||
return FlexThemeModeOptionButton( |
||||
optionButtonBorderRadius: 12, |
||||
height: 32, |
||||
width: 32, |
||||
padding: const EdgeInsets.all(0.4), |
||||
optionButtonMargin: EdgeInsets.zero, |
||||
borderRadius: 0, |
||||
unselectedBorder: BorderSide.none, |
||||
selectedBorder: BorderSide( |
||||
color: Theme.of(context).primaryColorLight, |
||||
width: 5, |
||||
), |
||||
onSelect: () { |
||||
scrollController.animateTo( |
||||
_kWidthOfScrollItem * index, |
||||
duration: const Duration(milliseconds: 350), |
||||
curve: Curves.easeOutCubic, |
||||
); |
||||
schemeIndex = index; |
||||
appStore.setColorSchemeIndex(index); |
||||
}, |
||||
selected: appStore.colorSchemeIndex == index, |
||||
backgroundColor: Theme.of(context).colorScheme.surface, |
||||
flexSchemeColor: isLight |
||||
? FlexColor.schemesList[index].light |
||||
: FlexColor.schemesList[index].dark, |
||||
); |
||||
}, |
||||
), |
||||
), |
||||
], |
||||
), |
||||
); |
||||
} |
||||
} |
Loading…
Reference in new issue