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