├── .metadata ├── CHANGELOG.md ├── LICENSE ├── example └── main.dart ├── .gitignore ├── pubspec.yaml ├── README.md ├── pubspec.lock └── lib └── stream_language.dart /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 2d2a1ffec95cc70a3218872a2cd3f8de4933c42f 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.1.0] - Null Safety. 2 | 3 | ## [1.0.1+5] - Updated dependencies. 4 | 5 | ## [1.0.1+3] - Update `firebase_database` 6 | 7 | ## [1.0.1+2] - Format 8 | 9 | ## [1.0.1+1] - Fix bugs 10 | 11 | ## [1.0.1] - Fix dependencies and web version 12 | 13 | ## [1.0.0] - Fix bugs and texts 14 | 15 | ## [0.1.2+1] - Fix dependencies. 16 | 17 | ## [0.1.2] 18 | 19 | * TODO: Added function "onChange" in StreamLanguage, it called when current language is changed 20 | 21 | ## [0.1.0] - TODO: Add release date. 22 | 23 | * Fixing bugs 24 | 25 | ## [0.0.1] - TODO: Add release date. 26 | 27 | * TODO: Describe initial release. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Clean Code 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /example/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:stream_language/stream_language.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | void main() => runApp(MyApp()); 5 | 6 | class MyApp extends StatelessWidget { 7 | // This widget is the root of your application. 8 | @override 9 | Widget build(BuildContext context) { 10 | return MaterialApp( 11 | title: 'Flutter Demo', 12 | theme: ThemeData(primarySwatch: Colors.blue), 13 | home: MyHomePage()); 14 | } 15 | } 16 | 17 | class MyHomePage extends StatefulWidget { 18 | @override 19 | _MyHomePageState createState() => _MyHomePageState(); 20 | } 21 | 22 | class _MyHomePageState extends State { 23 | final language = LanguageController( 24 | child: 'languages', defaultPrefix: 'pt_BR', commonRoute: 'default'); 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return FirstLanguageStart( 29 | control: language, 30 | builder: (c) { 31 | return StreamLanguage( 32 | screenRoute: ['screen-1'], 33 | builder: (data, route, def) => Scaffold( 34 | appBar: AppBar(title: Text(route['title'])), 35 | body: Center( 36 | child: ElevatedButton( 37 | child: Text(route['btn']), 38 | onPressed: () => language.showAlertChangeLanguage( 39 | context: context, 40 | title: def['change-language']['title'], 41 | btnNegative: def['change-language'][ 42 | 'btn-negative']))) // This trailing comma makes auto-formatting nicer for build methods. 43 | )); 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Generated.xcconfig 61 | **/ios/Flutter/app.flx 62 | **/ios/Flutter/app.zip 63 | **/ios/Flutter/flutter_assets/ 64 | **/ios/Flutter/flutter_export_environment.sh 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: stream_language 2 | description: A simple way to support your Flutter application, which from firebase with realtime database supports multiple languages! 3 | version: 1.1.0 4 | homepage: https://github.com/Bestfastfire/stream_language 5 | 6 | environment: 7 | sdk: ">=2.13.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | firebase_database: ^9.0.6 13 | country_pickers: ^2.0.0 14 | devicelocale: ^0.5.0 15 | rxdart: ^0.27.3 16 | 17 | dev_dependencies: 18 | flutter_test: 19 | sdk: flutter 20 | 21 | # For information on the generic Dart part of this file, see the 22 | # following page: https://dart.dev/tools/pub/pubspec 23 | 24 | # The following section is specific to Flutter. 25 | flutter: 26 | 27 | # To add assets to your package, add an assets section, like this: 28 | # assets: 29 | # - images/a_dot_burr.jpeg 30 | # - images/a_dot_ham.jpeg 31 | # 32 | # For details regarding assets in packages, see 33 | # https://flutter.dev/assets-and-images/#from-packages 34 | # 35 | # An image asset can refer to one or more resolution-specific "variants", see 36 | # https://flutter.dev/assets-and-images/#resolution-aware. 37 | 38 | # To add custom fonts to your package, add a fonts section here, 39 | # in this "flutter" section. Each entry in this list should have a 40 | # "family" key with the font family name, and a "fonts" key with a 41 | # list giving the asset and other descriptors for the font. For 42 | # example: 43 | # fonts: 44 | # - family: Schyler 45 | # fonts: 46 | # - asset: fonts/Schyler-Regular.ttf 47 | # - asset: fonts/Schyler-Italic.ttf 48 | # style: italic 49 | # - family: Trajan Pro 50 | # fonts: 51 | # - asset: fonts/TrajanPro.ttf 52 | # - asset: fonts/TrajanPro_Bold.ttf 53 | # weight: 700 54 | # 55 | # For details regarding fonts in packages, see 56 | # https://flutter.dev/custom-fonts/#from-packages 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stream Language 2 | Check it out at [Pub.Dev](https://pub.dev/packages/stream_language) 3 | 4 | A simple way to support your Flutter application, which from firebase with realtime database supports multiple languages! 5 | 6 | ![ezgif com-video-to-gif (1)](https://user-images.githubusercontent.com/22732544/65823906-9b68ee00-e235-11e9-989c-1c05a845b832.gif) 7 | 8 | ## Help Maintenance 9 | 10 | I've been maintaining quite many repos these days and burning out slowly. If you could help me cheer up, buying me a cup of coffee will make my life really happy and get much energy out of it. 11 | 12 | Buy Me A Coffee 13 | 14 | **Note:** 15 | If you want to do the control locally with jsons files, use this lib: [multi_language_json](https://pub.dev/packages/multi_language_json) 16 | 17 | ## Getting Started 18 | You must first create an object with the following attributes: 19 | 20 | var language = LanguageController( 21 | child: 'languages', 22 | defaultPrefix: 'pt_BR', 23 | commonRoute: 'default' 24 | ); 25 | 26 | LanguageController is a singleton, after the first start, it will have the same attributes. 27 | 28 | ### Child: 29 | The child in your realtime database that contains the app language. 30 | In app example its likes this: 31 | 32 | ![Captura de Tela (101)](https://user-images.githubusercontent.com/22732544/65823660-d87eb180-e230-11e9-802f-0edb5a91f0f5.png) 33 | 34 | Each child of this node must be named in the language and `iso_code` of the country as shown in the screenshot. 35 | 36 | ### DefaultPrefix 37 | Here will be informed the default home language when connecting the language of the user device does not have in the database. 38 | 39 | ### CommonRoute 40 | Here you enter the node within the language that contains words that can be used on more than one screen as in the example below: 41 | 42 | ![Captura de Tela (102)](https://user-images.githubusercontent.com/22732544/65823703-b0438280-e231-11e9-846b-f94d1b6e1f10.png) 43 | 44 | The first time you use firebase language you should do this: 45 | 46 | final language = LanguageController( 47 | child: 'languages', 48 | defaultPrefix: 'pt_BR', 49 | commonRoute: 'default' 50 | ); 51 | 52 | @override 53 | Widget build(BuildContext context) { 54 | return FirstLanguageStart( 55 | future: language.init(), 56 | builder: (c) => StreamLanguage( 57 | screenRoute: ['screen-1'], 58 | builder: (data, route, def) => Scaffold( 59 | appBar: AppBar( 60 | title: Text(route['title']), 61 | ), 62 | body: Center( 63 | child: RaisedButton( 64 | child: Text(route['btn']), 65 | onPressed: () => language.showAlertChangeLanguage( 66 | context: context, 67 | title: def['change-language']['title'], 68 | btnNegative: def['change-language']['btn-negative'] 69 | ) 70 | ) 71 | ) 72 | ) 73 | ) 74 | ); 75 | } 76 | 77 | From the next you start using only the `StreamLanguage` widget, the first one is needed because the first app should download all language and start the default language from the user's mobile language. 78 | 79 | ## Widget StreamLanguage 80 | 81 | ### ScreenRoute 82 | This is where the magic happens, as a parameter it receives the screen route within the language node, see that in the code above is as: 83 | `screenRoute: ['screen-1']`, in firebase it looks like this: 84 | 85 | ![Captura de Tela (103)](https://user-images.githubusercontent.com/22732544/65823751-d74e8400-e232-11e9-8930-998e642da9f5.png) 86 | 87 | If the route were a node within 'screen-1' you would go something like this: `screenRoute: ['screen-1', 'route_inside']` 88 | 89 | ### Builder 90 | The builder receives as parameter 3 fields: **data**, **route** and **def** 91 | 92 | #### Data 93 | Data contains all node of current language. 94 | 95 | #### Route 96 | Contains all node passed by ScreenRoute. 97 | 98 | #### Def 99 | Contains all node passed as parameter in **LanguageController** constructor in **commonRoute** 100 | 101 | # Changing Language 102 | For this, every language node must have a child named config with the following attributes: 103 | ![Captura de Tela (104)](https://user-images.githubusercontent.com/22732544/65823821-c5211580-e233-11e9-8df3-666120569cbf.png) 104 | 105 | After that you can call the method: 106 | 107 | language.showAlertChangeLanguage( 108 | context: context, 109 | title: def['change-language']['title'], 110 | btnNegative: def['change-language']['btn-negative'] 111 | ) 112 | 113 | This will show an alert dialog like this (Language and flag listing is done automatically from the data passed in the **config** node): 114 | 115 | ![Captura de Tela (105)](https://user-images.githubusercontent.com/22732544/65823835-116c5580-e234-11e9-8e4c-059f2fc163c7.png) 116 | 117 | To change the language programmatically, just call this method passing as the language prefix ex: 118 | 119 | LanguageController.changeLanguage('pt_BR'); -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.8.2" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.2.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.3.1" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.15.0" 46 | country_pickers: 47 | dependency: "direct main" 48 | description: 49 | name: country_pickers 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.0.0" 53 | devicelocale: 54 | dependency: "direct main" 55 | description: 56 | name: devicelocale 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "0.5.0" 60 | fake_async: 61 | dependency: transitive 62 | description: 63 | name: fake_async 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "1.2.0" 67 | firebase_core: 68 | dependency: transitive 69 | description: 70 | name: firebase_core 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "1.12.0" 74 | firebase_core_platform_interface: 75 | dependency: transitive 76 | description: 77 | name: firebase_core_platform_interface 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "4.2.4" 81 | firebase_core_web: 82 | dependency: transitive 83 | description: 84 | name: firebase_core_web 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "1.5.4" 88 | firebase_database: 89 | dependency: "direct main" 90 | description: 91 | name: firebase_database 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "9.0.6" 95 | firebase_database_platform_interface: 96 | dependency: transitive 97 | description: 98 | name: firebase_database_platform_interface 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "0.2.0+5" 102 | firebase_database_web: 103 | dependency: transitive 104 | description: 105 | name: firebase_database_web 106 | url: "https://pub.dartlang.org" 107 | source: hosted 108 | version: "0.2.0+5" 109 | flutter: 110 | dependency: "direct main" 111 | description: flutter 112 | source: sdk 113 | version: "0.0.0" 114 | flutter_test: 115 | dependency: "direct dev" 116 | description: flutter 117 | source: sdk 118 | version: "0.0.0" 119 | flutter_web_plugins: 120 | dependency: transitive 121 | description: flutter 122 | source: sdk 123 | version: "0.0.0" 124 | js: 125 | dependency: transitive 126 | description: 127 | name: js 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "0.6.3" 131 | matcher: 132 | dependency: transitive 133 | description: 134 | name: matcher 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "0.12.11" 138 | material_color_utilities: 139 | dependency: transitive 140 | description: 141 | name: material_color_utilities 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "0.1.3" 145 | meta: 146 | dependency: transitive 147 | description: 148 | name: meta 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "1.7.0" 152 | path: 153 | dependency: transitive 154 | description: 155 | name: path 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "1.8.0" 159 | plugin_platform_interface: 160 | dependency: transitive 161 | description: 162 | name: plugin_platform_interface 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "2.0.0" 166 | rxdart: 167 | dependency: "direct main" 168 | description: 169 | name: rxdart 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "0.27.3" 173 | sky_engine: 174 | dependency: transitive 175 | description: flutter 176 | source: sdk 177 | version: "0.0.99" 178 | source_span: 179 | dependency: transitive 180 | description: 181 | name: source_span 182 | url: "https://pub.dartlang.org" 183 | source: hosted 184 | version: "1.8.1" 185 | stack_trace: 186 | dependency: transitive 187 | description: 188 | name: stack_trace 189 | url: "https://pub.dartlang.org" 190 | source: hosted 191 | version: "1.10.0" 192 | stream_channel: 193 | dependency: transitive 194 | description: 195 | name: stream_channel 196 | url: "https://pub.dartlang.org" 197 | source: hosted 198 | version: "2.1.0" 199 | string_scanner: 200 | dependency: transitive 201 | description: 202 | name: string_scanner 203 | url: "https://pub.dartlang.org" 204 | source: hosted 205 | version: "1.1.0" 206 | term_glyph: 207 | dependency: transitive 208 | description: 209 | name: term_glyph 210 | url: "https://pub.dartlang.org" 211 | source: hosted 212 | version: "1.2.0" 213 | test_api: 214 | dependency: transitive 215 | description: 216 | name: test_api 217 | url: "https://pub.dartlang.org" 218 | source: hosted 219 | version: "0.4.8" 220 | typed_data: 221 | dependency: transitive 222 | description: 223 | name: typed_data 224 | url: "https://pub.dartlang.org" 225 | source: hosted 226 | version: "1.3.0" 227 | vector_math: 228 | dependency: transitive 229 | description: 230 | name: vector_math 231 | url: "https://pub.dartlang.org" 232 | source: hosted 233 | version: "2.1.1" 234 | sdks: 235 | dart: ">=2.14.0 <3.0.0" 236 | flutter: ">=1.20.0" 237 | -------------------------------------------------------------------------------- /lib/stream_language.dart: -------------------------------------------------------------------------------- 1 | library stream_language; 2 | 3 | import 'package:firebase_database/firebase_database.dart'; 4 | import 'package:country_pickers/country_pickers.dart'; 5 | import 'package:flutter/foundation.dart' show kIsWeb; 6 | import 'package:devicelocale/devicelocale.dart'; 7 | import 'package:flutter/material.dart'; 8 | import 'package:rxdart/rxdart.dart'; 9 | 10 | class StreamLanguage extends StatelessWidget { 11 | /// On change language 12 | final Function? onChange; 13 | 14 | /// route in json, ex: 15 | /// { 16 | /// "a" : { 17 | /// "b" : "value" 18 | /// } 19 | /// } 20 | /// 21 | /// to get only "b" I pass: ['a', 'b'] 22 | final List screenRoute; 23 | 24 | /// Controller 25 | final control = LanguageController(defaultPrefix: '', child: ''); 26 | 27 | /// Builder widget 28 | final Widget Function(dynamic data, dynamic route, dynamic def) builder; 29 | 30 | StreamLanguage( 31 | {required this.builder, this.screenRoute = const [], this.onChange}) { 32 | if (onChange != null) { 33 | control.outStreamList.listen((v) => onChange!()); 34 | } 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | return StreamBuilder( 40 | initialData: control.currentValue, 41 | stream: control.outStreamList, 42 | builder: (context, snapshot) { 43 | dynamic screenRoute = snapshot.data as dynamic; 44 | this.screenRoute.forEach((v) => screenRoute = screenRoute[v]); 45 | 46 | return builder( 47 | snapshot.data, 48 | screenRoute, 49 | control.commonRoute != null 50 | ? (snapshot.data as dynamic)[control.commonRoute] 51 | : []); 52 | }, 53 | ); 54 | } 55 | } 56 | 57 | abstract class _BlocBase { 58 | void dispose(); 59 | } 60 | 61 | class LanguageController implements _BlocBase { 62 | /// Last prefix selected 63 | String? lastPrefix; 64 | 65 | /// Child in RT Database 66 | final String child; 67 | 68 | /// Route common in to screens 69 | final String? commonRoute; 70 | 71 | /// Default prefix 72 | final String defaultPrefix; 73 | 74 | final BehaviorSubject> _languageDb = 75 | BehaviorSubject>(); 76 | 77 | final BehaviorSubject> _languageList = 78 | BehaviorSubject>(); 79 | 80 | /// Stream of languages 81 | Stream get outStreamList => _languageList.stream; 82 | 83 | /// Current selected language 84 | Map get currentValue => _languageList.value; 85 | 86 | /// Current common 87 | Map get currentCommon => 88 | commonRoute != null ? currentValue[commonRoute] : []; 89 | 90 | static LanguageController? _instance; 91 | factory LanguageController( 92 | {required String child, 93 | required String defaultPrefix, 94 | String? initialPrefix, 95 | String? commonRoute}) { 96 | return _instance ??= LanguageController._internal( 97 | child: child, 98 | defaultPrefix: defaultPrefix, 99 | lastPrefix: initialPrefix ?? defaultPrefix, 100 | commonRoute: commonRoute); 101 | } 102 | 103 | LanguageController._internal( 104 | {required this.child, 105 | required this.defaultPrefix, 106 | this.lastPrefix, 107 | this.commonRoute}); 108 | 109 | /// Required called before all to load 110 | Future init() async { 111 | this.lastPrefix ??= defaultPrefix; 112 | _languageDb.sink.add( 113 | (await FirebaseDatabase.instance.ref().child(child).get()) 114 | .value as dynamic); 115 | 116 | FirebaseDatabase.instance 117 | .ref() 118 | .child(child) 119 | .onValue 120 | .listen((v) => _languageDb.sink.add(v.snapshot.value as dynamic)); 121 | _languageDb.listen((v) => _languageList.sink.add(v[lastPrefix])); 122 | 123 | await _setDeviceLanguage(); 124 | } 125 | 126 | Future _setDeviceLanguage() async { 127 | if (kIsWeb) 128 | await changeLanguage(defaultPrefix); 129 | else 130 | await changeLanguage(await Devicelocale.currentLocale ?? defaultPrefix); 131 | 132 | return this; 133 | } 134 | 135 | /// Change current language passing prefix ex: [changeLanguage('en_US')] 136 | Future changeLanguage(String prefix) async { 137 | if (_languageDb.value[prefix] == null) { 138 | prefix = this.defaultPrefix; 139 | print( 140 | 'setting prefix with defaultLanguage because prefix: $prefix dont exists in database!'); 141 | } 142 | 143 | if (this.lastPrefix != (prefix)) { 144 | this.lastPrefix = prefix; 145 | _languageList.sink.add(_languageDb.value[this.lastPrefix]); 146 | print('language inited with prefix: $prefix'); 147 | } else { 148 | print( 149 | 'language don\'t changed because informed prefix is the same as current prefix: $prefix'); 150 | } 151 | } 152 | 153 | /// Get list of languages in route "config" inside jsons 154 | List> getListLanguage() { 155 | List> out = []; 156 | _languageDb.value.forEach((k, v) { 157 | out.add({ 158 | 'prefix': k, 159 | 'iso_code': v['config']['iso_code'], 160 | 'title': v['config']['title'] 161 | }); 162 | }); 163 | 164 | return out; 165 | } 166 | 167 | /// Show alert to change language 168 | Future showAlertChangeLanguage( 169 | {required BuildContext context, 170 | required String title, 171 | required String btnNegative}) async { 172 | List> out = this.getListLanguage(); 173 | 174 | return await showDialog( 175 | context: context, 176 | builder: (context) => AlertDialog( 177 | title: Text(title), 178 | actions: [ 179 | TextButton( 180 | onPressed: () => Navigator.pop(context), 181 | child: Text(btnNegative)), 182 | ], 183 | content: Container( 184 | width: MediaQuery.of(context).size.height * 0.8, 185 | height: MediaQuery.of(context).size.height * 0.3, 186 | child: ListView.builder( 187 | itemCount: out.length, 188 | itemBuilder: (BuildContext context, int index) { 189 | return Material( 190 | color: currentValue['config']['prefix'] == 191 | out[index]['prefix'] 192 | ? Colors.blueAccent[700] 193 | : Colors.transparent, 194 | child: ListTile( 195 | leading: CountryPickerUtils.getDefaultFlagImage( 196 | CountryPickerUtils.getCountryByIsoCode( 197 | out[index]['iso_code'])), 198 | selected: currentValue['config']['prefix'] == 199 | out[index]['prefix'], 200 | title: Text( 201 | out[index]['title'].toString(), 202 | style: TextStyle( 203 | color: currentValue['config']['prefix'] == 204 | out[index]['prefix'] 205 | ? Colors.white 206 | : Colors.black), 207 | ), 208 | onTap: () { 209 | changeLanguage(out[index]['prefix']); 210 | Navigator.pop(context); 211 | }, 212 | ), 213 | ); 214 | }, 215 | ), 216 | ), 217 | )); 218 | } 219 | 220 | @override 221 | void dispose() { 222 | _languageList.close(); 223 | _languageDb.close(); 224 | } 225 | } 226 | 227 | class FirstLanguageStart extends StatelessWidget { 228 | /// Controller 229 | final LanguageController control; 230 | 231 | /// Widget to show while init 232 | final Widget? loadWidget; 233 | 234 | /// Widget builder 235 | final Function(BuildContext context) builder; 236 | 237 | FirstLanguageStart( 238 | {required this.control, required this.builder, this.loadWidget}); 239 | 240 | @override 241 | Widget build(BuildContext context) { 242 | return FutureBuilder( 243 | future: control.init(), 244 | builder: (context, snapshot) { 245 | if (snapshot.connectionState == ConnectionState.done) { 246 | return builder(context); 247 | } 248 | 249 | return loadWidget ?? CircularProgressIndicator(); 250 | }); 251 | } 252 | } 253 | --------------------------------------------------------------------------------