chore: stabilize app startup, auth flow, and cross-platform UI
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit f7354e32a95b4459b5482b57f10ea1c228b7629a
|
||||||
|
|
@ -8,7 +8,7 @@ plugins {
|
||||||
android {
|
android {
|
||||||
namespace = "com.example.telurku"
|
namespace = "com.example.telurku"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = flutter.compileSdkVersion
|
||||||
ndkVersion = flutter.ndkVersion
|
ndkVersion = "27.0.12077973"
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
|
@ -24,7 +24,7 @@ android {
|
||||||
applicationId = "com.example.telurku"
|
applicationId = "com.example.telurku"
|
||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
minSdk = flutter.minSdkVersion
|
minSdk = 23
|
||||||
targetSdk = flutter.targetSdkVersion
|
targetSdk = flutter.targetSdkVersion
|
||||||
versionCode = flutter.versionCode
|
versionCode = flutter.versionCode
|
||||||
versionName = flutter.versionName
|
versionName = flutter.versionName
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
|
@ -427,7 +427,7 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
|
@ -484,7 +484,7 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
|
|
||||||
|
|
@ -1,122 +1 @@
|
||||||
{
|
{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"size" : "20x20",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-20x20@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "20x20",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-20x20@3x.png",
|
|
||||||
"scale" : "3x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "29x29",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-29x29@1x.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "29x29",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-29x29@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "29x29",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-29x29@3x.png",
|
|
||||||
"scale" : "3x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "40x40",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-40x40@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "40x40",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-40x40@3x.png",
|
|
||||||
"scale" : "3x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "60x60",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-60x60@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "60x60",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-60x60@3x.png",
|
|
||||||
"scale" : "3x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "20x20",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-20x20@1x.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "20x20",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-20x20@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "29x29",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-29x29@1x.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "29x29",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-29x29@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "40x40",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-40x40@1x.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "40x40",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-40x40@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "76x76",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-76x76@1x.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "76x76",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-76x76@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "83.5x83.5",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "1024x1024",
|
|
||||||
"idiom" : "ios-marketing",
|
|
||||||
"filename" : "Icon-App-1024x1024@1x.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 848 KiB |
|
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 704 B After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 762 B After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 54 KiB |
|
|
@ -47,6 +47,8 @@ class DefaultFirebaseOptions {
|
||||||
messagingSenderId: '22111260761',
|
messagingSenderId: '22111260761',
|
||||||
projectId: 'telurku-fa78c',
|
projectId: 'telurku-fa78c',
|
||||||
storageBucket: 'telurku-fa78c.firebasestorage.app',
|
storageBucket: 'telurku-fa78c.firebasestorage.app',
|
||||||
|
databaseURL:
|
||||||
|
'https://telurku-fa78c-default-rtdb.asia-southeast1.firebasedatabase.app',
|
||||||
);
|
);
|
||||||
|
|
||||||
static const FirebaseOptions ios = FirebaseOptions(
|
static const FirebaseOptions ios = FirebaseOptions(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
|
import 'package:firebase_database/firebase_database.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'firebase_options.dart';
|
import 'firebase_options.dart';
|
||||||
import 'providers/auth_provider.dart';
|
import 'providers/auth_provider.dart';
|
||||||
|
|
@ -17,6 +19,13 @@ void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||||
|
|
||||||
|
// Realtime DB persistence tidak didukung di semua platform (terutama web).
|
||||||
|
if (!kIsWeb &&
|
||||||
|
(defaultTargetPlatform == TargetPlatform.android ||
|
||||||
|
defaultTargetPlatform == TargetPlatform.iOS)) {
|
||||||
|
FirebaseDatabase.instance.setPersistenceEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize background scheduler untuk panen otomatis
|
// Initialize background scheduler untuk panen otomatis
|
||||||
// TODO: Enable setelah scheduler stabil
|
// TODO: Enable setelah scheduler stabil
|
||||||
// await PanenScheduler.initialize();
|
// await PanenScheduler.initialize();
|
||||||
|
|
@ -52,7 +61,7 @@ class MyApp extends StatelessWidget {
|
||||||
'/landing': (context) => const LandingPage(),
|
'/landing': (context) => const LandingPage(),
|
||||||
'/login': (context) => const LoginPage(),
|
'/login': (context) => const LoginPage(),
|
||||||
'/signup': (context) => const SignupPage(),
|
'/signup': (context) => const SignupPage(),
|
||||||
'/home': (context) => const HomePage(),
|
'/home': (context) => const AuthWrapper(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -73,7 +82,11 @@ class AuthWrapper extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authProvider.isLoggedIn) {
|
if (authProvider.isLoggedIn) {
|
||||||
return const HomePage();
|
final userId = authProvider.user?.uid;
|
||||||
|
if (userId == null) {
|
||||||
|
return const LandingPage();
|
||||||
|
}
|
||||||
|
return HomeBootstrap(userId: userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return const LandingPage();
|
return const LandingPage();
|
||||||
|
|
@ -81,3 +94,77 @@ class AuthWrapper extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class HomeBootstrap extends StatefulWidget {
|
||||||
|
final String userId;
|
||||||
|
|
||||||
|
const HomeBootstrap({super.key, required this.userId});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HomeBootstrap> createState() => _HomeBootstrapState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HomeBootstrapState extends State<HomeBootstrap> {
|
||||||
|
late Future<void> _initFuture;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_initFuture = _initializeData();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _initializeData() async {
|
||||||
|
final penjadwalanProvider = context.read<PenjadwalanProvider>();
|
||||||
|
final kandangProvider = context.read<KandangProvider>();
|
||||||
|
final panenProvider = context.read<PanenProvider>();
|
||||||
|
|
||||||
|
await penjadwalanProvider.initializeWithUser(widget.userId);
|
||||||
|
await kandangProvider.initializeWithUser(widget.userId);
|
||||||
|
await panenProvider.loadTodaySnapshots();
|
||||||
|
await panenProvider.restorePanenHistoryFromFirebase();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FutureBuilder<void>(
|
||||||
|
future: _initFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState != ConnectionState.done) {
|
||||||
|
return const Scaffold(
|
||||||
|
body: Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.hasError) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Gagal memuat data awal. Coba buka ulang aplikasi.',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_initFuture = _initializeData();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: const Text('Coba Lagi'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return const HomePage();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,11 @@ class AuthProvider extends ChangeNotifier {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return false;
|
return false;
|
||||||
|
} catch (_) {
|
||||||
|
_errorMessage = 'Terjadi kesalahan tak terduga. Coba lagi.';
|
||||||
|
_isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,6 +70,11 @@ class AuthProvider extends ChangeNotifier {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return false;
|
return false;
|
||||||
|
} catch (_) {
|
||||||
|
_errorMessage = 'Terjadi kesalahan tak terduga. Coba lagi.';
|
||||||
|
_isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,6 +110,11 @@ class AuthProvider extends ChangeNotifier {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return false;
|
return false;
|
||||||
|
} catch (_) {
|
||||||
|
_errorMessage = 'Terjadi kesalahan tak terduga. Coba lagi.';
|
||||||
|
_isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,14 @@ import '../models/kandang_model.dart';
|
||||||
|
|
||||||
class KandangProvider extends ChangeNotifier {
|
class KandangProvider extends ChangeNotifier {
|
||||||
final FirebaseDatabase _database = FirebaseDatabase.instance;
|
final FirebaseDatabase _database = FirebaseDatabase.instance;
|
||||||
late DatabaseReference _kandangRef;
|
late final DatabaseReference _kandangRef = _database.ref(_sharedKandangPath);
|
||||||
static const String _sharedKandangPath = 'kontrol/kandang';
|
static const String _sharedKandangPath = 'kontrol/kandang';
|
||||||
|
|
||||||
List<Kandang> _kandangs = [];
|
List<Kandang> _kandangs = [];
|
||||||
bool _isLoading = true;
|
bool _isLoading = true;
|
||||||
StreamSubscription? _subscription;
|
StreamSubscription? _subscription;
|
||||||
StreamSubscription? _sensorSubscription;
|
StreamSubscription? _sensorSubscription;
|
||||||
|
String? _activeUserId;
|
||||||
|
|
||||||
// Sensor Real-time Listeners
|
// Sensor Real-time Listeners
|
||||||
int _infra1Value = 0;
|
int _infra1Value = 0;
|
||||||
|
|
@ -25,7 +26,10 @@ class KandangProvider extends ChangeNotifier {
|
||||||
|
|
||||||
/// Initialize provider dengan user ID dan load data dari Firebase
|
/// Initialize provider dengan user ID dan load data dari Firebase
|
||||||
Future<void> initializeWithUser(String userId) async {
|
Future<void> initializeWithUser(String userId) async {
|
||||||
_kandangRef = _database.ref(_sharedKandangPath);
|
if (_activeUserId == userId && _subscription != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_activeUserId = userId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Pastikan ada data awal agar user baru tidak selalu input dari nol.
|
// Pastikan ada data awal agar user baru tidak selalu input dari nol.
|
||||||
|
|
@ -91,15 +95,22 @@ class KandangProvider extends ChangeNotifier {
|
||||||
|
|
||||||
Future<void> _ensureInitialKandangData(String userId) async {
|
Future<void> _ensureInitialKandangData(String userId) async {
|
||||||
final sharedSnapshot = await _kandangRef.get();
|
final sharedSnapshot = await _kandangRef.get();
|
||||||
if (sharedSnapshot.exists) return;
|
if (sharedSnapshot.exists && sharedSnapshot.value is Map) {
|
||||||
|
final current = Map<dynamic, dynamic>.from(sharedSnapshot.value as Map);
|
||||||
|
if (current.isNotEmpty) return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final legacyRef = _database.ref('users/$userId/kandang');
|
final legacyRef = _database.ref('users/$userId/kandang');
|
||||||
final legacySnapshot = await legacyRef.get();
|
final legacySnapshot = await legacyRef.get();
|
||||||
if (legacySnapshot.exists) {
|
if (legacySnapshot.exists && legacySnapshot.value is Map) {
|
||||||
await _kandangRef.set(legacySnapshot.value);
|
final legacyData =
|
||||||
|
Map<dynamic, dynamic>.from(legacySnapshot.value as Map);
|
||||||
|
if (legacyData.isNotEmpty) {
|
||||||
|
await _kandangRef.set(legacyData);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
print('Warning reading legacy kandang data: $e');
|
print('Warning reading legacy kandang data: $e');
|
||||||
|
|
@ -126,6 +137,7 @@ class KandangProvider extends ChangeNotifier {
|
||||||
Future<void> clearData() async {
|
Future<void> clearData() async {
|
||||||
await _subscription?.cancel();
|
await _subscription?.cancel();
|
||||||
await _sensorSubscription?.cancel();
|
await _sensorSubscription?.cancel();
|
||||||
|
_activeUserId = null;
|
||||||
_kandangs = [];
|
_kandangs = [];
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
_infra1Value = 0;
|
_infra1Value = 0;
|
||||||
|
|
|
||||||
|
|
@ -5,18 +5,24 @@ import '../models/penjadwalan_model.dart';
|
||||||
|
|
||||||
class PenjadwalanProvider extends ChangeNotifier {
|
class PenjadwalanProvider extends ChangeNotifier {
|
||||||
final FirebaseDatabase _database = FirebaseDatabase.instance;
|
final FirebaseDatabase _database = FirebaseDatabase.instance;
|
||||||
late DatabaseReference _penjadwalanRef;
|
late final DatabaseReference _penjadwalanRef = _database.ref(
|
||||||
|
'kontrol/penjadwalan',
|
||||||
|
);
|
||||||
|
|
||||||
List<Penjadwalan> _penjadwalans = [];
|
List<Penjadwalan> _penjadwalans = [];
|
||||||
bool _isLoading = true;
|
bool _isLoading = true;
|
||||||
StreamSubscription? _subscription;
|
StreamSubscription? _subscription;
|
||||||
|
String? _activeUserId;
|
||||||
|
|
||||||
List<Penjadwalan> get penjadwalans => _penjadwalans;
|
List<Penjadwalan> get penjadwalans => _penjadwalans;
|
||||||
bool get isLoading => _isLoading;
|
bool get isLoading => _isLoading;
|
||||||
|
|
||||||
/// Initialize provider dengan user ID dan load data dari Firebase
|
/// Initialize provider dengan user ID dan load data dari Firebase
|
||||||
Future<void> initializeWithUser(String userId) async {
|
Future<void> initializeWithUser(String userId) async {
|
||||||
_penjadwalanRef = _database.ref('kontrol/penjadwalan');
|
if (_activeUserId == userId && _subscription != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_activeUserId = userId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Pastikan jadwal awal tersedia agar login pertama langsung ada isi.
|
// Pastikan jadwal awal tersedia agar login pertama langsung ada isi.
|
||||||
|
|
@ -81,7 +87,10 @@ class PenjadwalanProvider extends ChangeNotifier {
|
||||||
|
|
||||||
Future<void> _ensureInitialPenjadwalanData() async {
|
Future<void> _ensureInitialPenjadwalanData() async {
|
||||||
final snapshot = await _penjadwalanRef.get();
|
final snapshot = await _penjadwalanRef.get();
|
||||||
if (snapshot.exists) return;
|
if (snapshot.exists && snapshot.value is Map) {
|
||||||
|
final current = Map<dynamic, dynamic>.from(snapshot.value as Map);
|
||||||
|
if (current.isNotEmpty) return;
|
||||||
|
}
|
||||||
|
|
||||||
await _penjadwalanRef.set({
|
await _penjadwalanRef.set({
|
||||||
'penjadwalan1': {
|
'penjadwalan1': {
|
||||||
|
|
@ -118,6 +127,7 @@ class PenjadwalanProvider extends ChangeNotifier {
|
||||||
/// Clear semua data saat logout
|
/// Clear semua data saat logout
|
||||||
Future<void> clearData() async {
|
Future<void> clearData() async {
|
||||||
await _subscription?.cancel();
|
await _subscription?.cancel();
|
||||||
|
_activeUserId = null;
|
||||||
_penjadwalans = [];
|
_penjadwalans = [];
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ class TelurProvider extends ChangeNotifier {
|
||||||
int _kandang1HariIni = 0;
|
int _kandang1HariIni = 0;
|
||||||
int _kandang2HariIni = 0;
|
int _kandang2HariIni = 0;
|
||||||
int _totalTelur = 0;
|
int _totalTelur = 0;
|
||||||
String _lastResetDate = '';
|
|
||||||
bool _isSynced = false;
|
bool _isSynced = false;
|
||||||
|
|
||||||
int get telurHariIni => _telurHariIni;
|
int get telurHariIni => _telurHariIni;
|
||||||
|
|
@ -18,6 +17,20 @@ class TelurProvider extends ChangeNotifier {
|
||||||
int get totalTelur => _totalTelur;
|
int get totalTelur => _totalTelur;
|
||||||
bool get isSynced => _isSynced;
|
bool get isSynced => _isSynced;
|
||||||
|
|
||||||
|
int _toInt(dynamic value) {
|
||||||
|
if (value is int) return value;
|
||||||
|
if (value is num) return value.toInt();
|
||||||
|
return int.tryParse('${value ?? 0}') ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int _readKandang1(Map<dynamic, dynamic> data) {
|
||||||
|
return _toInt(data['kandang1_hari_ini'] ?? data['kandang_1_hari_ini']);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _readKandang2(Map<dynamic, dynamic> data) {
|
||||||
|
return _toInt(data['kandang2_hari_ini'] ?? data['kandang_2_hari_ini']);
|
||||||
|
}
|
||||||
|
|
||||||
TelurProvider() {
|
TelurProvider() {
|
||||||
_riwayatRef = _database.ref('riwayat/summary');
|
_riwayatRef = _database.ref('riwayat/summary');
|
||||||
_initializeRiwayat();
|
_initializeRiwayat();
|
||||||
|
|
@ -46,14 +59,10 @@ class TelurProvider extends ChangeNotifier {
|
||||||
_riwayatRef.onValue.listen((event) {
|
_riwayatRef.onValue.listen((event) {
|
||||||
if (event.snapshot.exists) {
|
if (event.snapshot.exists) {
|
||||||
final data = event.snapshot.value as Map<dynamic, dynamic>;
|
final data = event.snapshot.value as Map<dynamic, dynamic>;
|
||||||
_telurHariIni = data['telur_hari_ini'] ?? 0;
|
_telurHariIni = _toInt(data['telur_hari_ini']);
|
||||||
_kandang1HariIni = data['kandang1_hari_ini'] ?? 0;
|
_kandang1HariIni = _readKandang1(data);
|
||||||
_kandang2HariIni = data['kandang2_hari_ini'] ?? 0;
|
_kandang2HariIni = _readKandang2(data);
|
||||||
_totalTelur = data['total_telur'] ?? 0;
|
_totalTelur = _toInt(data['total_telur']);
|
||||||
_lastResetDate = data['last_reset_date'] ?? '';
|
|
||||||
|
|
||||||
// Check if auto-reset is needed
|
|
||||||
_checkAndResetIfNeeded();
|
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
@ -72,11 +81,10 @@ class TelurProvider extends ChangeNotifier {
|
||||||
final snapshot = await _riwayatRef.get();
|
final snapshot = await _riwayatRef.get();
|
||||||
if (snapshot.exists) {
|
if (snapshot.exists) {
|
||||||
final data = snapshot.value as Map<dynamic, dynamic>;
|
final data = snapshot.value as Map<dynamic, dynamic>;
|
||||||
_telurHariIni = data['telur_hari_ini'] ?? 0;
|
_telurHariIni = _toInt(data['telur_hari_ini']);
|
||||||
_kandang1HariIni = data['kandang1_hari_ini'] ?? 0;
|
_kandang1HariIni = _readKandang1(data);
|
||||||
_kandang2HariIni = data['kandang2_hari_ini'] ?? 0;
|
_kandang2HariIni = _readKandang2(data);
|
||||||
_totalTelur = data['total_telur'] ?? 0;
|
_totalTelur = _toInt(data['total_telur']);
|
||||||
_lastResetDate = data['last_reset_date'] ?? '';
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -84,33 +92,8 @@ class TelurProvider extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _checkAndResetIfNeeded() async {
|
|
||||||
try {
|
|
||||||
final todayDate = DateTime.now().toIso8601String().split('T')[0];
|
|
||||||
|
|
||||||
if (_lastResetDate != todayDate) {
|
|
||||||
// Reset telur_hari_ini for new day
|
|
||||||
await _riwayatRef.update({
|
|
||||||
'telur_hari_ini': 0,
|
|
||||||
'kandang1_hari_ini': 0,
|
|
||||||
'kandang2_hari_ini': 0,
|
|
||||||
'last_reset_date': todayDate,
|
|
||||||
});
|
|
||||||
_telurHariIni = 0;
|
|
||||||
_kandang1HariIni = 0;
|
|
||||||
_kandang2HariIni = 0;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('Error checking/resetting telur data: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> addTelurHariIni(int jumlah) async {
|
Future<void> addTelurHariIni(int jumlah) async {
|
||||||
try {
|
try {
|
||||||
// Check and reset if needed first
|
|
||||||
await _checkAndResetIfNeeded();
|
|
||||||
|
|
||||||
final newTotal = _telurHariIni + jumlah;
|
final newTotal = _telurHariIni + jumlah;
|
||||||
await _riwayatRef.update({'telur_hari_ini': newTotal});
|
await _riwayatRef.update({'telur_hari_ini': newTotal});
|
||||||
_telurHariIni = newTotal;
|
_telurHariIni = newTotal;
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,7 @@ class DashboardPage extends StatelessWidget {
|
||||||
final kandangProvider = context.watch<KandangProvider>();
|
final kandangProvider = context.watch<KandangProvider>();
|
||||||
final telurProvider = context.watch<TelurProvider>();
|
final telurProvider = context.watch<TelurProvider>();
|
||||||
|
|
||||||
return Scaffold(
|
return Container(
|
||||||
body: Container(
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
|
|
@ -26,7 +25,6 @@ class DashboardPage extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: SafeArea(
|
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -213,8 +211,7 @@ class DashboardPage extends StatelessWidget {
|
||||||
'(${jadwal.durasi})',
|
'(${jadwal.durasi})',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
color:
|
color: Colors.orange.shade600,
|
||||||
Colors.orange.shade600,
|
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -297,8 +294,8 @@ class DashboardPage extends StatelessWidget {
|
||||||
itemCount: kandangProvider.kandangs.length,
|
itemCount: kandangProvider.kandangs.length,
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
final kandang = kandangProvider.kandangs[idx];
|
final kandang = kandangProvider.kandangs[idx];
|
||||||
final lastHarvest = panenProvider
|
final lastHarvest =
|
||||||
.getLastHarvestStatus(kandang.id);
|
panenProvider.getLastHarvestStatus(kandang.id);
|
||||||
|
|
||||||
if (lastHarvest == null) {
|
if (lastHarvest == null) {
|
||||||
return Container(
|
return Container(
|
||||||
|
|
@ -536,8 +533,6 @@ class DashboardPage extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,9 @@ import 'package:provider/provider.dart';
|
||||||
import '../providers/auth_provider.dart';
|
import '../providers/auth_provider.dart';
|
||||||
import '../providers/penjadwalan_provider.dart';
|
import '../providers/penjadwalan_provider.dart';
|
||||||
import '../providers/kandang_provider.dart';
|
import '../providers/kandang_provider.dart';
|
||||||
import '../providers/panen_provider.dart';
|
|
||||||
import 'dashboard_page.dart';
|
import 'dashboard_page.dart';
|
||||||
import 'kontrol_page.dart';
|
import 'kontrol_page.dart';
|
||||||
import 'riwayat_page.dart';
|
import 'riwayat_page.dart';
|
||||||
import 'panen_debug_screen.dart';
|
|
||||||
|
|
||||||
class HomePage extends StatefulWidget {
|
class HomePage extends StatefulWidget {
|
||||||
const HomePage({super.key});
|
const HomePage({super.key});
|
||||||
|
|
@ -18,7 +16,6 @@ class HomePage extends StatefulWidget {
|
||||||
|
|
||||||
class _HomePageState extends State<HomePage> {
|
class _HomePageState extends State<HomePage> {
|
||||||
int _selectedIndex = 0;
|
int _selectedIndex = 0;
|
||||||
String? _initializedForUserId;
|
|
||||||
|
|
||||||
final List<Widget> _pages = [
|
final List<Widget> _pages = [
|
||||||
const DashboardPage(),
|
const DashboardPage(),
|
||||||
|
|
@ -26,38 +23,6 @@ class _HomePageState extends State<HomePage> {
|
||||||
const RiwayatPage(),
|
const RiwayatPage(),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
super.didChangeDependencies();
|
|
||||||
|
|
||||||
final authProvider = context.read<AuthProvider>();
|
|
||||||
final userId = authProvider.user?.uid;
|
|
||||||
|
|
||||||
if (userId == null || userId == _initializedForUserId) return;
|
|
||||||
_initializedForUserId = userId;
|
|
||||||
|
|
||||||
// Re-init provider saat app restart/hot restart ketika user masih login.
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
|
||||||
if (!mounted) return;
|
|
||||||
try {
|
|
||||||
await context.read<PenjadwalanProvider>().initializeWithUser(userId);
|
|
||||||
if (!mounted) return;
|
|
||||||
await context.read<KandangProvider>().initializeWithUser(userId);
|
|
||||||
if (!mounted) return;
|
|
||||||
final panenProvider = context.read<PanenProvider>();
|
|
||||||
await panenProvider.loadTodaySnapshots();
|
|
||||||
await panenProvider.restorePanenHistoryFromFirebase();
|
|
||||||
} catch (_) {
|
|
||||||
if (!mounted) return;
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(
|
|
||||||
content: Text('Sinkronisasi data sedang bermasalah, coba lagi.'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final authProvider = context.watch<AuthProvider>();
|
final authProvider = context.watch<AuthProvider>();
|
||||||
|
|
@ -86,14 +51,6 @@ class _HomePageState extends State<HomePage> {
|
||||||
child: PopupMenuButton<int>(
|
child: PopupMenuButton<int>(
|
||||||
onSelected: (value) async {
|
onSelected: (value) async {
|
||||||
if (value == 0) {
|
if (value == 0) {
|
||||||
// Navigate to Debug Screen
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const PanenDebugScreen(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else if (value == 1) {
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
|
|
@ -168,39 +125,6 @@ class _HomePageState extends State<HomePage> {
|
||||||
itemBuilder: (context) => [
|
itemBuilder: (context) => [
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: 0,
|
value: 0,
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: [
|
|
||||||
Colors.orange.shade400,
|
|
||||||
Colors.orange.shade600
|
|
||||||
],
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.bug_report,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 18,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Text(
|
|
||||||
'Debug Panen',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.orange.shade600,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
PopupMenuItem(
|
|
||||||
value: 1,
|
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../providers/auth_provider.dart';
|
import '../providers/auth_provider.dart';
|
||||||
import '../providers/penjadwalan_provider.dart';
|
|
||||||
import '../providers/kandang_provider.dart';
|
|
||||||
|
|
||||||
class LoginPage extends StatefulWidget {
|
class LoginPage extends StatefulWidget {
|
||||||
const LoginPage({super.key});
|
const LoginPage({super.key});
|
||||||
|
|
@ -34,8 +32,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final emailRegex = RegExp(r'^[\w\-.]+@([\w-]+\.)+[\w-]{2,4}$');
|
if (!email.contains('@') || !email.contains('.')) {
|
||||||
if (!emailRegex.hasMatch(email)) {
|
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(const SnackBar(content: Text('Email tidak valid')));
|
).showSnackBar(const SnackBar(content: Text('Email tidak valid')));
|
||||||
|
|
@ -48,27 +45,6 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
if (success) {
|
if (success) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
// Initialize user data di Firebase
|
|
||||||
final userId = authProvider.user?.uid;
|
|
||||||
if (userId != null) {
|
|
||||||
final penjadwalanProvider = context.read<PenjadwalanProvider>();
|
|
||||||
final kandangProvider = context.read<KandangProvider>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await penjadwalanProvider.initializeWithUser(userId);
|
|
||||||
await kandangProvider.initializeWithUser(userId);
|
|
||||||
} catch (e) {
|
|
||||||
if (!mounted) return;
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(
|
|
||||||
content: Text(
|
|
||||||
'Login berhasil, tetapi sinkronisasi data belum sempurna.',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
Navigator.pushReplacementNamed(context, '/home');
|
Navigator.pushReplacementNamed(context, '/home');
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -95,7 +71,11 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: SingleChildScrollView(
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(minHeight: constraints.maxHeight),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(24.0),
|
padding: const EdgeInsets.all(24.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -127,7 +107,8 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(
|
Text(
|
||||||
'Masuk ke akun Anda',
|
'Masuk ke akun Anda',
|
||||||
style: TextStyle(fontSize: 16, color: Colors.grey.shade700),
|
style: TextStyle(
|
||||||
|
fontSize: 16, color: Colors.grey.shade700),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 60),
|
const SizedBox(height: 60),
|
||||||
// Login Form Card
|
// Login Form Card
|
||||||
|
|
@ -162,7 +143,8 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
keyboardType: TextInputType.emailAddress,
|
keyboardType: TextInputType.emailAddress,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Masukkan email Anda',
|
hintText: 'Masukkan email Anda',
|
||||||
hintStyle: TextStyle(color: Colors.grey.shade400),
|
hintStyle:
|
||||||
|
TextStyle(color: Colors.grey.shade400),
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: Colors.grey.shade100,
|
fillColor: Colors.grey.shade100,
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
|
|
@ -191,7 +173,8 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
obscureText: _obscurePassword,
|
obscureText: _obscurePassword,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Masukkan password Anda',
|
hintText: 'Masukkan password Anda',
|
||||||
hintStyle: TextStyle(color: Colors.grey.shade400),
|
hintStyle:
|
||||||
|
TextStyle(color: Colors.grey.shade400),
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: Colors.grey.shade100,
|
fillColor: Colors.grey.shade100,
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
|
|
@ -254,11 +237,14 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
? null
|
? null
|
||||||
: _handleLogin,
|
: _handleLogin,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: const Color(0xFFF4D03F),
|
backgroundColor:
|
||||||
disabledBackgroundColor: Colors.grey.shade300,
|
const Color(0xFFF4D03F),
|
||||||
|
disabledBackgroundColor:
|
||||||
|
Colors.grey.shade300,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius:
|
||||||
|
BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: authProvider.isLoading
|
child: authProvider.isLoading
|
||||||
|
|
@ -297,7 +283,8 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () => Navigator.pushNamed(context, '/signup'),
|
onTap: () =>
|
||||||
|
Navigator.pushNamed(context, '/signup'),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Daftar di sini',
|
'Daftar di sini',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|
@ -313,6 +300,9 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../providers/auth_provider.dart';
|
import '../providers/auth_provider.dart';
|
||||||
import '../providers/penjadwalan_provider.dart';
|
|
||||||
import '../providers/kandang_provider.dart';
|
|
||||||
|
|
||||||
class SignupPage extends StatefulWidget {
|
class SignupPage extends StatefulWidget {
|
||||||
const SignupPage({super.key});
|
const SignupPage({super.key});
|
||||||
|
|
@ -46,8 +44,7 @@ class _SignupPageState extends State<SignupPage> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final emailRegex = RegExp(r'^[\w\-.]+@([\w-]+\.)+[\w-]{2,4}$');
|
if (!email.contains('@') || !email.contains('.')) {
|
||||||
if (!emailRegex.hasMatch(email)) {
|
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(const SnackBar(content: Text('Email tidak valid')));
|
).showSnackBar(const SnackBar(content: Text('Email tidak valid')));
|
||||||
|
|
@ -83,27 +80,6 @@ class _SignupPageState extends State<SignupPage> {
|
||||||
if (success) {
|
if (success) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|
||||||
// Initialize user data di Firebase
|
|
||||||
final userId = authProvider.user?.uid;
|
|
||||||
if (userId != null) {
|
|
||||||
final penjadwalanProvider = context.read<PenjadwalanProvider>();
|
|
||||||
final kandangProvider = context.read<KandangProvider>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await penjadwalanProvider.initializeWithUser(userId);
|
|
||||||
await kandangProvider.initializeWithUser(userId);
|
|
||||||
} catch (e) {
|
|
||||||
if (!mounted) return;
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
const SnackBar(
|
|
||||||
content: Text(
|
|
||||||
'Akun berhasil dibuat, tetapi sinkronisasi data belum sempurna.',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ScaffoldMessenger.of(
|
ScaffoldMessenger.of(
|
||||||
context,
|
context,
|
||||||
).showSnackBar(const SnackBar(content: Text('Akun berhasil dibuat!')));
|
).showSnackBar(const SnackBar(content: Text('Akun berhasil dibuat!')));
|
||||||
|
|
@ -135,7 +111,11 @@ class _SignupPageState extends State<SignupPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: SingleChildScrollView(
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(minHeight: constraints.maxHeight),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(24.0),
|
padding: const EdgeInsets.all(24.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
@ -167,7 +147,8 @@ class _SignupPageState extends State<SignupPage> {
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(
|
Text(
|
||||||
'Bergabunglah dengan TelurKu',
|
'Bergabunglah dengan TelurKu',
|
||||||
style: TextStyle(fontSize: 16, color: Colors.grey.shade700),
|
style: TextStyle(
|
||||||
|
fontSize: 16, color: Colors.grey.shade700),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 40),
|
const SizedBox(height: 40),
|
||||||
// Signup Form Card
|
// Signup Form Card
|
||||||
|
|
@ -201,7 +182,8 @@ class _SignupPageState extends State<SignupPage> {
|
||||||
controller: _nameController,
|
controller: _nameController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Masukkan nama lengkap',
|
hintText: 'Masukkan nama lengkap',
|
||||||
hintStyle: TextStyle(color: Colors.grey.shade400),
|
hintStyle:
|
||||||
|
TextStyle(color: Colors.grey.shade400),
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: Colors.grey.shade100,
|
fillColor: Colors.grey.shade100,
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
|
|
@ -230,7 +212,8 @@ class _SignupPageState extends State<SignupPage> {
|
||||||
keyboardType: TextInputType.emailAddress,
|
keyboardType: TextInputType.emailAddress,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Masukkan email Anda',
|
hintText: 'Masukkan email Anda',
|
||||||
hintStyle: TextStyle(color: Colors.grey.shade400),
|
hintStyle:
|
||||||
|
TextStyle(color: Colors.grey.shade400),
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: Colors.grey.shade100,
|
fillColor: Colors.grey.shade100,
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
|
|
@ -259,7 +242,8 @@ class _SignupPageState extends State<SignupPage> {
|
||||||
obscureText: _obscurePassword,
|
obscureText: _obscurePassword,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Minimal 6 karakter',
|
hintText: 'Minimal 6 karakter',
|
||||||
hintStyle: TextStyle(color: Colors.grey.shade400),
|
hintStyle:
|
||||||
|
TextStyle(color: Colors.grey.shade400),
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: Colors.grey.shade100,
|
fillColor: Colors.grey.shade100,
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
|
|
@ -301,7 +285,8 @@ class _SignupPageState extends State<SignupPage> {
|
||||||
obscureText: _obscureConfirmPassword,
|
obscureText: _obscureConfirmPassword,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Konfirmasi password Anda',
|
hintText: 'Konfirmasi password Anda',
|
||||||
hintStyle: TextStyle(color: Colors.grey.shade400),
|
hintStyle:
|
||||||
|
TextStyle(color: Colors.grey.shade400),
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: Colors.grey.shade100,
|
fillColor: Colors.grey.shade100,
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
|
|
@ -375,11 +360,14 @@ class _SignupPageState extends State<SignupPage> {
|
||||||
? null
|
? null
|
||||||
: _handleSignup,
|
: _handleSignup,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: const Color(0xFFF4D03F),
|
backgroundColor:
|
||||||
disabledBackgroundColor: Colors.grey.shade300,
|
const Color(0xFFF4D03F),
|
||||||
|
disabledBackgroundColor:
|
||||||
|
Colors.grey.shade300,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius:
|
||||||
|
BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: authProvider.isLoading
|
child: authProvider.isLoading
|
||||||
|
|
@ -418,7 +406,8 @@ class _SignupPageState extends State<SignupPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () => Navigator.pushNamed(context, '/login'),
|
onTap: () =>
|
||||||
|
Navigator.pushNamed(context, '/login'),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Masuk di sini',
|
'Masuk di sini',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|
@ -435,6 +424,9 @@ class _SignupPageState extends State<SignupPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,68 +1,68 @@
|
||||||
{
|
{
|
||||||
"images" : [
|
"info": {
|
||||||
|
"version": 1,
|
||||||
|
"author": "xcode"
|
||||||
|
},
|
||||||
|
"images": [
|
||||||
{
|
{
|
||||||
"size" : "16x16",
|
"size": "16x16",
|
||||||
"idiom" : "mac",
|
"idiom": "mac",
|
||||||
"filename" : "app_icon_16.png",
|
"filename": "app_icon_16.png",
|
||||||
"scale" : "1x"
|
"scale": "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "16x16",
|
"size": "16x16",
|
||||||
"idiom" : "mac",
|
"idiom": "mac",
|
||||||
"filename" : "app_icon_32.png",
|
"filename": "app_icon_32.png",
|
||||||
"scale" : "2x"
|
"scale": "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "32x32",
|
"size": "32x32",
|
||||||
"idiom" : "mac",
|
"idiom": "mac",
|
||||||
"filename" : "app_icon_32.png",
|
"filename": "app_icon_32.png",
|
||||||
"scale" : "1x"
|
"scale": "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "32x32",
|
"size": "32x32",
|
||||||
"idiom" : "mac",
|
"idiom": "mac",
|
||||||
"filename" : "app_icon_64.png",
|
"filename": "app_icon_64.png",
|
||||||
"scale" : "2x"
|
"scale": "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "128x128",
|
"size": "128x128",
|
||||||
"idiom" : "mac",
|
"idiom": "mac",
|
||||||
"filename" : "app_icon_128.png",
|
"filename": "app_icon_128.png",
|
||||||
"scale" : "1x"
|
"scale": "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "128x128",
|
"size": "128x128",
|
||||||
"idiom" : "mac",
|
"idiom": "mac",
|
||||||
"filename" : "app_icon_256.png",
|
"filename": "app_icon_256.png",
|
||||||
"scale" : "2x"
|
"scale": "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "256x256",
|
"size": "256x256",
|
||||||
"idiom" : "mac",
|
"idiom": "mac",
|
||||||
"filename" : "app_icon_256.png",
|
"filename": "app_icon_256.png",
|
||||||
"scale" : "1x"
|
"scale": "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "256x256",
|
"size": "256x256",
|
||||||
"idiom" : "mac",
|
"idiom": "mac",
|
||||||
"filename" : "app_icon_512.png",
|
"filename": "app_icon_512.png",
|
||||||
"scale" : "2x"
|
"scale": "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "512x512",
|
"size": "512x512",
|
||||||
"idiom" : "mac",
|
"idiom": "mac",
|
||||||
"filename" : "app_icon_512.png",
|
"filename": "app_icon_512.png",
|
||||||
"scale" : "1x"
|
"scale": "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "512x512",
|
"size": "512x512",
|
||||||
"idiom" : "mac",
|
"idiom": "mac",
|
||||||
"filename" : "app_icon_1024.png",
|
"filename": "app_icon_1024.png",
|
||||||
"scale" : "2x"
|
"scale": "2x"
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 848 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 520 B After Width: | Height: | Size: 846 B |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 310 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 9.6 KiB |
88
pubspec.lock
|
|
@ -9,6 +9,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.59"
|
version: "1.3.59"
|
||||||
|
archive:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: archive
|
||||||
|
sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.9"
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.0"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -33,6 +49,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
checked_yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: checked_yaml
|
||||||
|
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.3"
|
||||||
|
cli_util:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cli_util
|
||||||
|
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.2"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -198,6 +230,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
version: "6.0.0"
|
||||||
|
flutter_launcher_icons:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: flutter_launcher_icons
|
||||||
|
sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.14.4"
|
||||||
flutter_lints:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
|
|
@ -240,6 +280,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.2"
|
version: "4.1.2"
|
||||||
|
image:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image
|
||||||
|
sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.8.0"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -248,6 +296,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.19.0"
|
version: "0.19.0"
|
||||||
|
json_annotation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: json_annotation
|
||||||
|
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.9.0"
|
||||||
leak_tracker:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -368,6 +424,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.0"
|
||||||
|
petitparser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: petitparser
|
||||||
|
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.0"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -384,6 +448,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
version: "2.1.8"
|
||||||
|
posix:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: posix
|
||||||
|
sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.5.0"
|
||||||
provider:
|
provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -493,6 +565,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
|
xml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xml
|
||||||
|
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.5.0"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.7.0 <4.0.0"
|
dart: ">=3.7.0 <4.0.0"
|
||||||
flutter: ">=3.29.0"
|
flutter: ">=3.29.0"
|
||||||
|
|
|
||||||
19
pubspec.yaml
|
|
@ -61,6 +61,7 @@ dependencies:
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
flutter_launcher_icons: ^0.14.3
|
||||||
|
|
||||||
# The "flutter_lints" package below contains a set of recommended lints to
|
# The "flutter_lints" package below contains a set of recommended lints to
|
||||||
# encourage good coding practices. The lint set provided by the package is
|
# encourage good coding practices. The lint set provided by the package is
|
||||||
|
|
@ -110,3 +111,21 @@ flutter:
|
||||||
#
|
#
|
||||||
# For details regarding fonts from package dependencies,
|
# For details regarding fonts from package dependencies,
|
||||||
# see https://flutter.dev/to/font-from-package
|
# see https://flutter.dev/to/font-from-package
|
||||||
|
|
||||||
|
flutter_launcher_icons:
|
||||||
|
android: true
|
||||||
|
ios: true
|
||||||
|
image_path: "assets/icons/telurku.jpg"
|
||||||
|
min_sdk_android: 21
|
||||||
|
web:
|
||||||
|
generate: true
|
||||||
|
image_path: "assets/icons/telurku.jpg"
|
||||||
|
background_color: "#ffffff"
|
||||||
|
theme_color: "#ffffff"
|
||||||
|
windows:
|
||||||
|
generate: true
|
||||||
|
image_path: "assets/icons/telurku.jpg"
|
||||||
|
icon_size: 48
|
||||||
|
macos:
|
||||||
|
generate: true
|
||||||
|
image_path: "assets/icons/telurku.jpg"
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,19 @@
|
||||||
// This is a basic Flutter widget test.
|
|
||||||
//
|
|
||||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
|
||||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
|
||||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
|
||||||
// tree, read text, and verify that the values of widget properties are correct.
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import 'package:telurku/main.dart';
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
testWidgets('basic material app renders', (WidgetTester tester) async {
|
||||||
// Build our app and trigger a frame.
|
await tester.pumpWidget(
|
||||||
await tester.pumpWidget(const MyApp());
|
const MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: Text('TelurKu Test Smoke'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// Verify that our counter starts at 0.
|
expect(find.text('TelurKu Test Smoke'), findsOneWidget);
|
||||||
expect(find.text('0'), findsOneWidget);
|
expect(find.byType(MaterialApp), findsOneWidget);
|
||||||
expect(find.text('1'), findsNothing);
|
|
||||||
|
|
||||||
// Tap the '+' icon and trigger a frame.
|
|
||||||
await tester.tap(find.byIcon(Icons.add));
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
// Verify that our counter has incremented.
|
|
||||||
expect(find.text('0'), findsNothing);
|
|
||||||
expect(find.text('1'), findsOneWidget);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
web/favicon.png
|
Before Width: | Height: | Size: 917 B After Width: | Height: | Size: 846 B |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 310 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 310 KiB |
|
|
@ -3,8 +3,8 @@
|
||||||
"short_name": "telurku",
|
"short_name": "telurku",
|
||||||
"start_url": ".",
|
"start_url": ".",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#0175C2",
|
"background_color": "#ffffff",
|
||||||
"theme_color": "#0175C2",
|
"theme_color": "#ffffff",
|
||||||
"description": "A new Flutter project.",
|
"description": "A new Flutter project.",
|
||||||
"orientation": "portrait-primary",
|
"orientation": "portrait-primary",
|
||||||
"prefer_related_applications": false,
|
"prefer_related_applications": false,
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 5.7 KiB |