diff --git a/lib/main.dart b/lib/main.dart index f62f86b..e508cd2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,15 @@ +import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; +import 'package:gasku/firebase_options.dart'; import 'package:gasku/pages/main.dart'; +import 'package:gasku/pages/masuk.dart'; import 'package:google_fonts/google_fonts.dart'; -void main() { +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); runApp(const MyApp()); } @@ -27,7 +34,7 @@ class MyApp extends StatelessWidget { dragHandleColor: Colors.white, backgroundColor: Colors.transparent, )), - home: const MyMainPage(), + home: const MyMasukPage(), ); } } diff --git a/lib/models/user.dart b/lib/models/user.dart new file mode 100644 index 0000000..fd2e50b --- /dev/null +++ b/lib/models/user.dart @@ -0,0 +1,143 @@ +import 'dart:convert'; + +import 'package:crypto/crypto.dart'; +import 'package:firebase_database/firebase_database.dart'; + +class User { + final String id; + final String nama; + final String nik; + final String kk; + final String noTelepon; + final String kataSandi; + + User({ + required this.id, + required this.nama, + required this.nik, + required this.kk, + required this.noTelepon, + required this.kataSandi, + }); + + factory User.autoID({ + required String nama, + required String nik, + required String kk, + required String noTelepon, + required String kataSandi, + }) { + final id = DateTime.now().millisecondsSinceEpoch.toString(); + + return User( + id: id, + nama: nama, + nik: nik, + kk: kk, + noTelepon: noTelepon, + kataSandi: kataSandi, + ); + } + + User copyWith({ + String? id, + String? nama, + String? nik, + String? kk, + String? noTelepon, + String? kataSandi, + }) { + return User( + id: id ?? this.id, + nama: nama ?? this.nama, + nik: nik ?? this.nik, + kk: kk ?? this.kk, + noTelepon: noTelepon ?? this.noTelepon, + kataSandi: kataSandi ?? this.kataSandi, + ); + } + + static Future add(User user) async { + final hashedPassword = sha1.convert(utf8.encode(user.kataSandi)).toString(); + final newUser = user.copyWith(kataSandi: hashedPassword); + + await FirebaseDatabase.instance + .ref('users/${newUser.id}') + .set(newUser.toMap()); + } + + static Future get(String userID) async { + final snapshot = await FirebaseDatabase.instance.ref('users/$userID').get(); + + if (!snapshot.exists) { + print('User tidak ditemukan!'); + return null; + } + + print(snapshot.value); + return User.fromMap(snapshot.value as Map); + } + + static Future resetKataSandi( + String userID, String kataSandiBaru) async { + final hashedPassword = sha1.convert(utf8.encode(kataSandiBaru)).toString(); + + await FirebaseDatabase.instance + .ref('users/${userID}') + .update({'kataSandi': hashedPassword}); + } + + Map toMap() { + return { + 'id': id, + 'nama': nama, + 'nik': nik, + 'kk': kk, + 'noTelepon': noTelepon, + 'kataSandi': kataSandi, + }; + } + + factory User.fromMap(Map map) { + return User( + id: map['id'] as String, + nama: map['nama'] as String, + nik: map['nik'] as String, + kk: map['kk'] as String, + noTelepon: map['noTelepon'] as String, + kataSandi: map['kataSandi'] as String, + ); + } + + String toJson() => json.encode(toMap()); + + factory User.fromJson(String source) => + User.fromMap(json.decode(source) as Map); + + @override + String toString() { + return 'User(id: $id, nama: $nama, nik: $nik, kk: $kk, noTelepon: $noTelepon, kataSandi: $kataSandi)'; + } + + @override + bool operator ==(covariant User other) { + if (identical(this, other)) return true; + + return other.id == id && + other.nama == nama && + other.nik == nik && + other.kk == kk && + other.noTelepon == noTelepon && + other.kataSandi == kataSandi; + } + + @override + int get hashCode { + return id.hashCode ^ + nama.hashCode ^ + nik.hashCode ^ + kk.hashCode ^ + noTelepon.hashCode ^ + kataSandi.hashCode; + } +} diff --git a/lib/pages/daftar.dart b/lib/pages/daftar.dart index 4c7b774..7f8c9f5 100644 --- a/lib/pages/daftar.dart +++ b/lib/pages/daftar.dart @@ -1,5 +1,8 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:gasku/models/user.dart'; +import 'package:gasku/pages/masuk.dart'; +import 'package:gasku/utils/show_loading_screen.dart'; import 'package:gasku/widgets/filled_button.dart'; import 'package:gasku/widgets/text_form_field.dart'; @@ -13,6 +16,43 @@ class MyDaftarPage extends StatefulWidget { class _MyDaftarPageState extends State { final _formKey = GlobalKey(); + String _nama = ''; + String _nik = ''; + String _kk = ''; + String _noTelepon = ''; + String _kataSandi = ''; + + Future onSubmit() async { + if (!_formKey.currentState!.validate()) return; + + showLoadingScreen(context); + + await User.add( + User.autoID( + nama: _nama, + nik: _nik, + kk: _kk, + noTelepon: _noTelepon, + kataSandi: _kataSandi, + ), + ); + + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (_) => MyMasukPage()), + (_) => false, + ); + + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text('Akun berhasil didaftarkan'), + backgroundColor: Theme.of(context).colorScheme.primary, + action: SnackBarAction( + label: 'Tutup', + onPressed: () => ScaffoldMessenger.of(context).hideCurrentSnackBar(), + ), + )); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -44,38 +84,91 @@ class _MyDaftarPageState extends State { const SizedBox(height: 10), Form( key: _formKey, - child: const Column( + child: Column( children: [ MyTextFormField( title: 'Nama Lengkap', label: 'Masukkan Nama Anda', + onChanged: (value) => _nama = value, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Nama tidak boleh kosong'; + } + if (value.length > 40) { + return 'Nama maksimal 40 karakter'; + } + return null; + }, ), MyTextFormField( title: 'Nomor Induk Kependudukan (NIK)', label: 'Masukkan NIK Anda', + keyboardType: TextInputType.number, + onChanged: (value) => _nik = value, + validator: (value) { + if (value == null || value.isEmpty) { + return 'NIK tidak boleh kosong'; + } + if (value.length != 16) { + return 'NIK harus 16 digit'; + } + return null; + }, ), MyTextFormField( title: 'Nomor Kartu Keluarga (KK)', label: 'Masukkan Nomor KK Anda', + keyboardType: TextInputType.number, + onChanged: (value) => _kk = value, + validator: (value) { + if (value == null || value.isEmpty) { + return 'KK tidak boleh kosong'; + } + if (value.length != 16) { + return 'KK harus 16 digit'; + } + return null; + }, ), MyTextFormField( prefix: Text('+62'), keyboardType: TextInputType.number, title: 'Nomor Telepon', label: 'Masukkan Nomor Telepon Anda', + onChanged: (value) => _noTelepon = value, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Nomor Telepon tidak boleh kosong'; + } + if (value.length > 20) { + return 'Nomor Telepon maksimal 20 karakter'; + } + return null; + }, ), MyTextFormField( isObscure: true, title: 'Kata Sandi', label: 'Masukkan Kata Sandi', + onChanged: (value) => _kataSandi = value, + onFieldSubmitted: onSubmit, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Kata Sandi tidak boleh kosong'; + } + if (value.length > 40) { + return 'Kata Sandi maksimal 40 karakter'; + } + return null; + }, ), ], ), ), const SizedBox(height: 35), - const SizedBox( + SizedBox( width: double.infinity, - child: MyFilledButton(text: 'Daftar'), + child: MyFilledButton(text: 'Daftar', onPressed: onSubmit), ), const SizedBox(height: 15), RichText( diff --git a/lib/utils/show_loading_screen.dart b/lib/utils/show_loading_screen.dart new file mode 100644 index 0000000..062d2e9 --- /dev/null +++ b/lib/utils/show_loading_screen.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; + +void showLoadingScreen(BuildContext context) { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) { + return Container( + color: Colors.black26, + child: Center( + child: CircularProgressIndicator( + color: Theme.of(context).colorScheme.primary, + ), + ), + ); + }, + ); +} diff --git a/lib/widgets/filled_button.dart b/lib/widgets/filled_button.dart index 6bc9cbf..de1d312 100644 --- a/lib/widgets/filled_button.dart +++ b/lib/widgets/filled_button.dart @@ -1,14 +1,21 @@ import 'package:flutter/material.dart'; class MyFilledButton extends StatelessWidget { - const MyFilledButton({super.key, required this.text, this.color}); + const MyFilledButton({ + super.key, + required this.text, + this.color, + this.onPressed, + }); + final String text; final Color? color; + final void Function()? onPressed; @override Widget build(BuildContext context) { return FilledButton( - onPressed: () {}, + onPressed: onPressed, style: FilledButton.styleFrom( shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(10)), diff --git a/lib/widgets/text_form_field.dart b/lib/widgets/text_form_field.dart index 0195772..7b7dc0a 100644 --- a/lib/widgets/text_form_field.dart +++ b/lib/widgets/text_form_field.dart @@ -9,6 +9,9 @@ class MyTextFormField extends StatefulWidget { required this.label, this.prefix, this.keyboardType, + this.validator, + this.onChanged, + this.onFieldSubmitted, }); final String? initialValue; @@ -17,6 +20,9 @@ class MyTextFormField extends StatefulWidget { final String label; final Widget? prefix; final TextInputType? keyboardType; + final String? Function(String?)? validator; + final void Function(String value)? onChanged; + final void Function()? onFieldSubmitted; @override State createState() => _MyTextFormFieldState(); @@ -41,9 +47,9 @@ class _MyTextFormFieldState extends State { keyboardType: widget.keyboardType, obscureText: widget.isObscure ? _isInvisible : false, textInputAction: TextInputAction.next, - onFieldSubmitted: (a) { - print('SUBMIT'); - }, + validator: widget.validator, + onChanged: widget.onChanged, + onFieldSubmitted: (a) => widget.onFieldSubmitted!(), decoration: InputDecoration( border: const OutlineInputBorder(), labelText: widget.label, diff --git a/pubspec.lock b/pubspec.lock index 7fd08da..cc16392 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: "0816f12bbbd9e21f72ea8592b11bce4a628d4e5cb7a81ff9f1eee4f3dc23206e" + url: "https://pub.dev" + source: hosted + version: "1.3.37" ansicolor: dependency: transitive description: @@ -66,7 +74,7 @@ packages: source: hosted version: "1.18.0" crypto: - dependency: transitive + dependency: "direct main" description: name: crypto sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab @@ -129,6 +137,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.17.2" + firebase_database: + dependency: "direct main" + description: + name: firebase_database + sha256: "35d357a36bfedb70aae108f7bb80986e6a87905580a537a3a483d77ef20264df" + url: "https://pub.dev" + source: hosted + version: "11.0.1" + firebase_database_platform_interface: + dependency: transitive + description: + name: firebase_database_platform_interface + sha256: "7d742b187d6fb3bb93f807fe0ca45709b0803bc0f7103332acfc781a96bf3c6e" + url: "https://pub.dev" + source: hosted + version: "0.2.5+37" + firebase_database_web: + dependency: transitive + description: + name: firebase_database_web + sha256: "70640f8800261fd6629952b6f83c607e35698bb54d6f215522d12864db800f0e" + url: "https://pub.dev" + source: hosted + version: "0.2.5+9" flutter: dependency: "direct main" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index ce354b5..f1d4d03 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -40,6 +40,8 @@ dependencies: intl: ^0.19.0 flutter_native_splash: ^2.4.0 firebase_core: ^3.1.0 + firebase_database: ^11.0.1 + crypto: ^3.0.3 dev_dependencies: flutter_test: