diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7dd78b6..aee315a 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -2,7 +2,7 @@ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index db77bb4..f092457 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 17987b7..31cec23 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 09d4391..51f1188 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index d5f1c8d..89a445b 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 4d6372e..9d251fa 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/build.gradle.kts b/android/build.gradle.kts index 89176ef..28c6558 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -15,6 +15,11 @@ subprojects { subprojects { project.evaluationDependsOn(":app") } +subprojects { + tasks.withType().configureEach { + options.compilerArgs.add("-Xlint:-options") + } +} tasks.register("clean") { delete(rootProject.layout.buildDirectory) diff --git a/android/gradle.properties b/android/gradle.properties index f018a61..7177f59 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,5 @@ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +org.gradle.java.home=C:/Program Files/Android/Android Studio1/jbr android.useAndroidX=true android.enableJetifier=true +android.javaCompile.suppressSourceTargetDeprecationWarning=true diff --git a/assets/branding/app_icon.png b/assets/branding/app_icon.png new file mode 100644 index 0000000..525b1f7 Binary files /dev/null and b/assets/branding/app_icon.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index dc9ada4..6dff545 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 7353c41..bd83370 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 797d452..1a8a223 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index 6ed2d93..f0944f4 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 4cd7b00..6ba10a6 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index fe73094..b2c35e6 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index 321773c..ad2abce 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 797d452..1a8a223 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index 502f463..546cd40 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index 0ec3034..04aabd7 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index 0ec3034..04aabd7 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index e9f5fea..71ed5fe 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index 84ac32a..9187b0a 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 8953cba..7cf3f6a 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index 0467bf1..95b7f91 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 6152c38..979201f 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -5,7 +5,7 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName - Finalproject + Tobaku Sulastri CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -13,7 +13,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - finalproject + Tobaku Sulastri CFBundlePackageType APPL CFBundleShortVersionString diff --git a/lib/main.dart b/lib/main.dart index aced403..6c8c61b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -20,7 +20,7 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - title: 'Prediksi Stok Bahan Kue', + title: 'Tobaku Sulastri', theme: AppTheme.lightTheme(), debugShowCheckedModeBanner: false, navigatorObservers: [routeObserver], diff --git a/lib/screens/login/login_controller.dart b/lib/screens/login/login_controller.dart index a3d99a7..abe42fb 100644 --- a/lib/screens/login/login_controller.dart +++ b/lib/screens/login/login_controller.dart @@ -1,11 +1,14 @@ import 'package:flutter/material.dart'; +import 'package:finalproject/services/auth_service.dart'; +import 'package:finalproject/services/ml_service.dart'; class LoginController extends ChangeNotifier { - final usernameController = TextEditingController(text: 'admin@sulastri.com'); - final passwordController = TextEditingController(text: 'password'); + final usernameController = TextEditingController(); + final passwordController = TextEditingController(); bool isPasswordVisible = false; bool isLoading = false; + String? errorMessage; void togglePasswordVisibility() { isPasswordVisible = !isPasswordVisible; @@ -24,13 +27,27 @@ class LoginController extends ChangeNotifier { if (error != null) return false; isLoading = true; + errorMessage = null; notifyListeners(); - await Future.delayed(const Duration(seconds: 1)); + final result = await MLService.login( + usernameOrEmail: usernameController.text.trim(), + password: passwordController.text, + ); isLoading = false; + final success = result['status'] == 'success'; + if (success) { + final data = result['data']; + await AuthService.saveLoginSession( + name: data is Map ? data['name']?.toString() : null, + email: data is Map ? data['email']?.toString() : null, + ); + } else { + errorMessage = result['message']?.toString() ?? 'Login gagal'; + } notifyListeners(); - return true; + return success; } @override diff --git a/lib/screens/login/login_page.dart b/lib/screens/login/login_page.dart index 83cbb9f..56a36ab 100644 --- a/lib/screens/login/login_page.dart +++ b/lib/screens/login/login_page.dart @@ -37,9 +37,27 @@ class _LoginScreenState extends State { if (success) { Navigator.of(context).pushReplacementNamed('/dashboard'); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(_controller.errorMessage ?? 'Login gagal'), + backgroundColor: AppColors.statusError, + ), + ); } } + void _onForgotPasswordPressed() { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Text( + 'Fitur lupa password dengan OTP akan dikembangkan pada tahap berikutnya.', + ), + backgroundColor: AppColors.primaryBrown, + ), + ); + } + @override Widget build(BuildContext context) { return AnimatedBuilder( @@ -193,7 +211,9 @@ class _LoginScreenState extends State { alignment: Alignment.centerRight, child: TextButton( onPressed: - _controller.isLoading ? null : () {}, + _controller.isLoading + ? null + : _onForgotPasswordPressed, style: TextButton.styleFrom( padding: EdgeInsets.zero, ), @@ -247,35 +267,13 @@ class _LoginScreenState extends State { ), ), ), - const SizedBox(height: 24), - Container( - width: double.infinity, - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: AppColors.bgLight, - borderRadius: BorderRadius.circular(8), - border: Border.all(color: AppColors.grey300), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Demo: Klik "Masuk" untuk melanjutkan', - style: AppTextStyles.bodySmall.copyWith( - color: AppColors.statusError, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ), ], ), ), ), const SizedBox(height: 48), Text( - '© 2025 Toko Bahan Kue Sulastri', + '(c) 2025 Tobaku Sulastri', style: AppTextStyles.bodySmall.copyWith( color: AppColors.textTertiary, ), diff --git a/lib/screens/settings/settings_page.dart b/lib/screens/settings/settings_page.dart index 1113667..96188d5 100644 --- a/lib/screens/settings/settings_page.dart +++ b/lib/screens/settings/settings_page.dart @@ -1,6 +1,7 @@ import 'package:finalproject/theme/colors.dart'; import 'package:finalproject/theme/text_styles.dart'; import 'package:flutter/material.dart'; +import 'package:finalproject/services/auth_service.dart'; import 'settings_controller.dart'; @@ -51,8 +52,10 @@ class _SettingsScreenState extends State { ), ), ElevatedButton( - onPressed: () { + onPressed: () async { Navigator.pop(context); + await AuthService.logout(); + if (!context.mounted) return; Navigator.of( context, ).pushNamedAndRemoveUntil('/login', (route) => false); @@ -341,7 +344,7 @@ class _SettingsScreenState extends State { ), const SizedBox(height: 12), Text( - '© 2025 Toko Bahan Kue Sulastri', + '© 2025 Tobaku Sulastri', style: AppTextStyles.bodySmall.copyWith( color: AppColors.textTertiary, ), diff --git a/lib/screens/splash_screen.dart b/lib/screens/splash_screen.dart index 9840a0f..c499c28 100644 --- a/lib/screens/splash_screen.dart +++ b/lib/screens/splash_screen.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:finalproject/services/auth_service.dart'; import 'package:finalproject/theme/colors.dart'; class SplashScreen extends StatefulWidget { - const SplashScreen({Key? key}) : super(key: key); + const SplashScreen({super.key}); @override State createState() => _SplashScreenState(); @@ -63,10 +64,12 @@ class _SplashScreenState extends State // Tunggu sebentar sebelum navigate await Future.delayed(const Duration(seconds: 1)); - if (mounted) { - // Navigate ke Login - Navigator.of(context).pushReplacementNamed('/login'); - } + final isLoggedIn = await AuthService.isLoggedIn(); + if (!mounted) return; + + Navigator.of( + context, + ).pushReplacementNamed(isLoggedIn ? '/dashboard' : '/login'); } catch (e) { setState(() => _statusMessage = 'Error: ${e.toString()}'); await Future.delayed(const Duration(seconds: 2)); @@ -148,16 +151,17 @@ class _SplashScreenState extends State borderRadius: BorderRadius.circular(28), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.15), + color: Colors.black.withValues(alpha: 0.15), blurRadius: 25, offset: const Offset(0, 10), ), ], ), - child: Center( - child: Text( - '🍰', - style: TextStyle(fontSize: 60), + child: Padding( + padding: const EdgeInsets.all(18), + child: Image.asset( + 'assets/branding/app_icon.png', + fit: BoxFit.contain, ), ), ), @@ -179,7 +183,7 @@ class _SplashScreenState extends State children: [ // Main title Text( - 'Sulastri', + 'Tobaku Sulastri', style: TextStyle( fontSize: 36, fontWeight: FontWeight.w700, @@ -301,6 +305,16 @@ class _SplashScreenState extends State ), const SizedBox(height: 40), + Text( + _statusMessage, + style: TextStyle( + fontSize: 13, + color: AppColors.textTertiary, + fontWeight: FontWeight.w400, + ), + ), + const SizedBox(height: 16), + // Version text Text( 'Version 1.0.0', diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart new file mode 100644 index 0000000..83f03b3 --- /dev/null +++ b/lib/services/auth_service.dart @@ -0,0 +1,32 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +class AuthService { + static const String _isLoggedInKey = 'is_logged_in'; + static const String _userNameKey = 'user_name'; + static const String _userEmailKey = 'user_email'; + + static Future saveLoginSession({String? name, String? email}) async { + final preferences = await SharedPreferences.getInstance(); + await preferences.setBool(_isLoggedInKey, true); + + if (name != null && name.isNotEmpty) { + await preferences.setString(_userNameKey, name); + } + + if (email != null && email.isNotEmpty) { + await preferences.setString(_userEmailKey, email); + } + } + + static Future isLoggedIn() async { + final preferences = await SharedPreferences.getInstance(); + return preferences.getBool(_isLoggedInKey) ?? false; + } + + static Future logout() async { + final preferences = await SharedPreferences.getInstance(); + await preferences.remove(_isLoggedInKey); + await preferences.remove(_userNameKey); + await preferences.remove(_userEmailKey); + } +} diff --git a/lib/services/ml_service.dart b/lib/services/ml_service.dart index 8596b71..94ac8ff 100644 --- a/lib/services/ml_service.dart +++ b/lib/services/ml_service.dart @@ -5,10 +5,42 @@ class MLService { // API URL - Change based on environment // Untuk emulator Android: 10.0.2.2 // Untuk device fisik: 192.168.x.x atau 127.0.0.1 kalau local - static const String baseUrl = 'http://192.168.1.72:5000'; + static const String baseUrl = 'http://192.168.18.30:5000'; static const int timeoutSeconds = 30; + /// Login using account data from MySQL login table. + static Future> login({ + required String usernameOrEmail, + required String password, + }) async { + try { + final response = await http + .post( + Uri.parse('$baseUrl/api/login'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({ + 'email': usernameOrEmail, + 'username': usernameOrEmail, + 'password': password, + }), + ) + .timeout(Duration(seconds: timeoutSeconds)); + + final body = jsonDecode(response.body) as Map; + if (response.statusCode == 200) { + return body; + } + + return {'status': 'error', 'message': body['message'] ?? 'Login gagal'}; + } catch (e) { + return { + 'status': 'error', + 'message': 'Tidak dapat terhubung ke server: $e', + }; + } + } + /// Convert grams to kilograms with rounding rules: /// - <= 500g -> 0.5kg /// - 500g < g <= 1000g -> 1kg diff --git a/linux/runner/my_application.cc b/linux/runner/my_application.cc index 0712b28..61e2c37 100644 --- a/linux/runner/my_application.cc +++ b/linux/runner/my_application.cc @@ -40,11 +40,11 @@ static void my_application_activate(GApplication* application) { if (use_header_bar) { GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); - gtk_header_bar_set_title(header_bar, "finalproject"); + gtk_header_bar_set_title(header_bar, "Tobaku Sulastri"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); } else { - gtk_window_set_title(window, "finalproject"); + gtk_window_set_title(window, "Tobaku Sulastri"); } gtk_window_set_default_size(window, 1280, 720); diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 17f9da9..c3f782e 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,8 +7,10 @@ import Foundation import path_provider_foundation import share_plus +import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 82b6f9d..525b1f7 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index 13b35eb..94ba88e 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 0a3f5fa..4b7feb1 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bdb5722..e563025 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index f083318..f19dc75 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index 326c0e7..19d37c5 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 2f1632c..41964a7 100644 Binary files a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig index 8b91945..f74d979 100644 --- a/macos/Runner/Configs/AppInfo.xcconfig +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -5,10 +5,10 @@ // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = finalproject +PRODUCT_NAME = Tobaku Sulastri // The application's bundle identifier PRODUCT_BUNDLE_IDENTIFIER = com.example.finalproject // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2026 com.example. All rights reserved. +PRODUCT_COPYRIGHT = Copyright (c) 2026 Tobaku Sulastri. All rights reserved. diff --git a/ml_model/app.py b/ml_model/app.py index 863be05..2a732ce 100644 --- a/ml_model/app.py +++ b/ml_model/app.py @@ -10,6 +10,7 @@ import pandas as pd import numpy as np import logging import math +import hashlib from datetime import datetime import mysql.connector from mysql.connector import Error @@ -97,6 +98,14 @@ def build_report_response(success: bool, message: str, data: list | None = None, 'data': data or [] }), status_code +def verify_login_password(input_password: str, stored_password: str) -> bool: + """Accept plain text passwords and SHA-256 hashes for simple local auth.""" + if input_password == stored_password: + return True + + hashed_input = hashlib.sha256(input_password.encode('utf-8')).hexdigest() + return hashed_input == stored_password + def ensure_prediction_needs_column(connection, table_name: str = 'predictions') -> str | None: needs_col = get_existing_column( connection, @@ -477,6 +486,64 @@ def get_metadata(): }), 200 +@app.route('/api/login', methods=['POST']) +def login(): + """Login using email/username and password stored in the login table.""" + try: + data = request.json or {} + username_or_email = (data.get('email') or data.get('username') or '').strip() + password = data.get('password') or '' + + if not username_or_email or not password: + return jsonify({ + 'status': 'error', + 'message': 'Email/username dan password wajib diisi' + }), 400 + + connection = get_db_connection() + if connection is None: + return jsonify({ + 'status': 'error', + 'message': 'Tidak dapat terhubung ke database' + }), 500 + + cursor = connection.cursor(dictionary=True) + cursor.execute(""" + SELECT id, name, email, username, password + FROM login + WHERE email = %s OR username = %s + LIMIT 1 + """, (username_or_email, username_or_email)) + user = cursor.fetchone() + cursor.close() + connection.close() + + if user is None or not verify_login_password(password, user['password']): + return jsonify({ + 'status': 'error', + 'message': 'Email/username atau password salah' + }), 401 + + user.pop('password', None) + return jsonify({ + 'status': 'success', + 'message': 'Login berhasil', + 'data': user + }), 200 + except Error as e: + logger.error(f"Login database error: {e}") + return jsonify({ + 'status': 'error', + 'message': 'Tabel login belum tersedia atau database bermasalah' + }), 500 + except Exception as e: + logger.error(f"Login error: {e}") + return jsonify({ + 'status': 'error', + 'message': 'Terjadi kesalahan saat login' + }), 500 + + @app.route('/prediksi', methods=['POST']) def prediksi(): """ diff --git a/ml_model/database_setup.py b/ml_model/database_setup.py index 42f97e3..e48a54d 100644 --- a/ml_model/database_setup.py +++ b/ml_model/database_setup.py @@ -102,6 +102,19 @@ def create_tables(): """) logger.info("✅ Predictions table created successfully") + # Login table + cursor.execute(""" + CREATE TABLE IF NOT EXISTS login ( + id INT PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(100) NOT NULL, + email VARCHAR(100) NOT NULL UNIQUE, + username VARCHAR(50) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """) + logger.info("Login table created successfully") + connection.commit() cursor.close() connection.close() @@ -136,6 +149,29 @@ def insert_default_products(): print(f"❌ Error inserting products: {err}") return False +def insert_default_login(): + """Insert default login account""" + try: + connection = mysql.connector.connect(**DB_CONFIG) + cursor = connection.cursor() + + cursor.execute(""" + INSERT INTO login (name, email, username, password) + VALUES (%s, %s, %s, %s) + ON DUPLICATE KEY UPDATE + name = VALUES(name), + username = VALUES(username) + """, ('Ibu Sulastri', 'admin@sulastri.com', 'admin', 'password')) + + connection.commit() + logger.info("Default login account ready") + cursor.close() + connection.close() + return True + except Error as err: + print(f"Error inserting login account: {err}") + return False + def verify_connection(): """Verify database connection""" try: @@ -171,6 +207,9 @@ def main(): if not insert_default_products(): return + if not insert_default_login(): + return + verify_connection() print() diff --git a/ml_model/setup_database.sql b/ml_model/setup_database.sql index 36b7117..8fe6593 100644 --- a/ml_model/setup_database.sql +++ b/ml_model/setup_database.sql @@ -59,6 +59,16 @@ CREATE TABLE IF NOT EXISTS predictions ( created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +-- Create Login Table +CREATE TABLE IF NOT EXISTS login ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100) NOT NULL, + email VARCHAR(100) NOT NULL UNIQUE, + username VARCHAR(50) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + -- Create Recipes Table CREATE TABLE IF NOT EXISTS recipes ( id INT AUTO_INCREMENT PRIMARY KEY, @@ -88,6 +98,13 @@ INSERT INTO products (name, category, product_type, unit, price, current_stock) ('Keju Parut 250gr', 'Keju', 'Bahan', 'kg', 28000, 10), ('Baking Powder', 'Bahan Tambahan', 'Bahan', 'kg', 8000, 35); +-- Insert Default Login Account +INSERT INTO login (name, email, username, password) VALUES +('Ibu Sulastri', 'admin@sulastri.com', 'admin', 'password') +ON DUPLICATE KEY UPDATE + name = VALUES(name), + username = VALUES(username); + -- Insert Sample Recipes INSERT INTO recipes (recipe_name, description) VALUES ('Donat', 'Resep donat lezat'), diff --git a/pubspec.lock b/pubspec.lock index 36b9b30..c4400f8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -392,6 +392,62 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.2" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e + url: "https://pub.dev" + source: hosted + version: "2.4.13" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index e579e5c..6e957de 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,6 +38,9 @@ dependencies: # HTTP Client untuk API http: ^1.1.0 + # Local storage untuk menyimpan status login + shared_preferences: ^2.2.3 + # Intl untuk date formatting intl: ^0.19.0 @@ -73,6 +76,9 @@ flutter: # the material Icons class. uses-material-design: true + assets: + - assets/branding/app_icon.png + # To add assets to your application, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png index b749bfe..9d251fa 100644 Binary files a/web/icons/Icon-192.png and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png index 88cfd48..19d37c5 100644 Binary files a/web/icons/Icon-512.png and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png index eb9b4d7..9d251fa 100644 Binary files a/web/icons/Icon-maskable-192.png and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png index d69c566..19d37c5 100644 Binary files a/web/icons/Icon-maskable-512.png and b/web/icons/Icon-maskable-512.png differ diff --git a/web/index.html b/web/index.html index fc1713f..a0f6fc5 100644 --- a/web/index.html +++ b/web/index.html @@ -23,13 +23,13 @@ - + - finalproject + Tobaku Sulastri diff --git a/web/manifest.json b/web/manifest.json index 9d43523..a9928a0 100644 --- a/web/manifest.json +++ b/web/manifest.json @@ -1,11 +1,11 @@ { - "name": "finalproject", - "short_name": "finalproject", + "name": "Tobaku Sulastri", + "short_name": "Tobaku", "start_url": ".", "display": "standalone", - "background_color": "#0175C2", - "theme_color": "#0175C2", - "description": "A new Flutter project.", + "background_color": "#F8F1E8", + "theme_color": "#8B5A3C", + "description": "Aplikasi prediksi permintaan dan stok bahan kue Tobaku Sulastri.", "orientation": "portrait-primary", "prefer_related_applications": false, "icons": [ diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc index f9bc737..e740edf 100644 --- a/windows/runner/Runner.rc +++ b/windows/runner/Runner.rc @@ -90,12 +90,12 @@ BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "com.example" "\0" - VALUE "FileDescription", "finalproject" "\0" + VALUE "FileDescription", "Tobaku Sulastri" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" - VALUE "InternalName", "finalproject" "\0" + VALUE "InternalName", "Tobaku Sulastri" "\0" VALUE "LegalCopyright", "Copyright (C) 2026 com.example. All rights reserved." "\0" VALUE "OriginalFilename", "finalproject.exe" "\0" - VALUE "ProductName", "finalproject" "\0" + VALUE "ProductName", "Tobaku Sulastri" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp index bcac38e..e72fb0f 100644 --- a/windows/runner/main.cpp +++ b/windows/runner/main.cpp @@ -27,7 +27,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, FlutterWindow window(project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); - if (!window.Create(L"finalproject", origin, size)) { + if (!window.Create(L"Tobaku Sulastri", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico index c04e20c..d0cb0a9 100644 Binary files a/windows/runner/resources/app_icon.ico and b/windows/runner/resources/app_icon.ico differ