diff --git a/Store.png b/Store.png new file mode 100644 index 0000000..2bcacc8 Binary files /dev/null and b/Store.png differ diff --git a/android/app/build.gradle b/android/app/build.gradle index 83300dd..c9c4822 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -22,8 +22,14 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('key.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} + android { - namespace "com.example.beta_app1" + namespace "com.nutrify.app" compileSdk flutter.compileSdkVersion ndkVersion flutter.ndkVersion @@ -42,7 +48,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.beta_app1" + applicationId "com.nutrify.app" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. minSdkVersion flutter.minSdkVersion @@ -51,13 +57,17 @@ android { versionName flutterVersionName } + signingConfigs { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + storePassword keystoreProperties['storePassword'] + } + } buildTypes { release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug - minifyEnabled true - shrinkResources true + signingConfig signingConfigs.release } } } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8515904..24e7174 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -6,7 +6,7 @@ + android:icon="@mipmap/launcher_icon"> { final prefs = await SharedPreferences.getInstance(); String mqttServerIp = - prefs.getString('mqtt_server_ip') ?? '192.168.183.153'; + prefs.getString('mqtt_server_ip') ?? '192.168.182.153'; String url = 'http://$mqttServerIp/test_api/login.php'; final response = await http.post( @@ -128,7 +128,7 @@ class _LoginPageState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( - "images/nature.gif", + "images/nutrify.png", height: 200, width: 200, ), @@ -216,31 +216,41 @@ class _LoginPageState extends State { ), child: const Text( 'Login', - style: TextStyle(color: Colors.white), + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold), ), ), - const SizedBox(height: 10), - ElevatedButton( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => RegisterPage()), - ); - }, - style: ElevatedButton.styleFrom( - backgroundColor: - const Color.fromARGB(255, 255, 255, 255), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12.0), - ), - padding: const EdgeInsets.symmetric(vertical: 16.0), - minimumSize: const Size(double.infinity, 50), - ), - child: const Text( - 'Register', - style: TextStyle( - color: Color.fromARGB(166, 26, 21, 21)), + const SizedBox(height: 25), + Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Belum memiliki akun?', + style: TextStyle( + color: Colors.black, + fontSize: 14.0, + ), + ), + GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => RegisterPage(), + ), + ); + }, + child: const Text( + ' Register', + style: TextStyle( + color: Colors.red, + fontSize: 14.0, + ), + ), + ), + ], ), ), ], @@ -259,7 +269,7 @@ class _LoginPageState extends State { const Text( "@2024", style: TextStyle(color: Colors.black, fontSize: 10.0), - ) + ), ], ), ), diff --git a/lib/login/register.dart b/lib/login/register.dart index f72f0dc..353ae36 100644 --- a/lib/login/register.dart +++ b/lib/login/register.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'login.dart'; @@ -53,35 +54,55 @@ class _RegisterPageState extends State { }); } - // Validasi fullname (opsional) - final prefs = await SharedPreferences.getInstance(); - String mqttServerIp = prefs.getString('mqtt_server_ip') ?? '192.168.0.1'; + String mqttServerIp = + prefs.getString('mqtt_server_ip') ?? '192.168.182.153'; String url = 'http://$mqttServerIp/test_api/register.php'; final response = await http.post( Uri.parse(url), - body: { + headers: {"Content-Type": "application/json"}, + body: jsonEncode({ "username": username, "password": password, "fullname": fullname, - }, + }), ); + if (response.statusCode == 200) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Akun berhasil dibuat'), - duration: Duration(seconds: 2), - backgroundColor: Colors.green, - ), - ); - Navigator.push( - context, - MaterialPageRoute(builder: (context) => const LoginPage()), - ); + final responseData = jsonDecode(response.body); + if (responseData['status'] == 'success') { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Akun berhasil dibuat'), + duration: Duration(seconds: 2), + backgroundColor: Colors.green, + ), + ); + Navigator.push( + context, + MaterialPageRoute(builder: (context) => const LoginPage()), + ); + } else { + setState(() { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(responseData['message']), + duration: Duration(seconds: 2), + backgroundColor: Colors.red, + ), + ); + }); + } } else { setState(() { - // Handle error + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Failed to register user'), + duration: Duration(seconds: 2), + backgroundColor: Colors.red, + ), + ); }); } } @@ -89,154 +110,208 @@ class _RegisterPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('Halaman Registrasi'), - ), - body: SingleChildScrollView( - child: Center( - child: Padding( - padding: const EdgeInsets.all(15.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.all(10.0), - child: - Image.asset("images/nature.gif", height: 200, width: 200), - ), - const SizedBox( - height: 10, - ), - Card( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(15.0), + backgroundColor: Colors.white, + body: Center( + child: SingleChildScrollView( + child: Center( + child: Padding( + padding: const EdgeInsets.all(15.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + "images/nutrify.png", + height: 200, + width: 200, ), - elevation: 8, - margin: const EdgeInsets.symmetric(horizontal: 16.0), - child: Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Center( - child: Text( - 'Register', - style: TextStyle( - fontSize: 24.0, - fontFamily: 'Poppins', - fontWeight: FontWeight.bold, - color: Color.fromARGB(150, 0, 0, 0), - ), - ), - ), - const SizedBox(height: 10), - TextFormField( - controller: usernameController, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.person), - labelText: 'Username', - border: const OutlineInputBorder( - borderRadius: - BorderRadius.all(Radius.circular(12.0)), - ), - hintText: 'Username', - hintStyle: TextStyle( - color: const Color.fromARGB(92, 0, 0, 0) - .withOpacity(0.25), - fontSize: 14.0, - ), - ), - ), - if (usernameError.isNotEmpty) - Padding( - padding: - const EdgeInsets.only(top: 8.0, left: 12.0), + const SizedBox( + height: 20, + ), + Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15.0), + ), + elevation: 8, + margin: const EdgeInsets.symmetric(horizontal: 16.0), + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Center( child: Text( - usernameError, - style: const TextStyle( - color: Colors.red, - fontSize: 12.0, + 'Register', + style: TextStyle( + fontSize: 24.0, + fontFamily: 'Poppins', + fontWeight: FontWeight.bold, + color: Color.fromARGB(150, 0, 0, 0), ), ), ), - const SizedBox(height: 10), - TextFormField( - controller: fullnameController, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.perm_identity), - labelText: 'Fullname', - border: const OutlineInputBorder( - borderRadius: - BorderRadius.all(Radius.circular(12.0)), - ), - hintText: 'Fullname', - hintStyle: TextStyle( - color: Colors.black.withOpacity(0.25), - fontSize: 14.0, - ), - ), - ), - const SizedBox(height: 10), - TextFormField( - controller: passwordController, - obscureText: _obscureText, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.password), - labelText: 'Password', - border: const OutlineInputBorder( - borderRadius: - BorderRadius.all(Radius.circular(12.0)), - ), - hintText: 'Password', - hintStyle: TextStyle( - color: Colors.black.withOpacity(0.25), - fontSize: 14.0, - ), - suffixIcon: IconButton( - icon: Icon( - _obscureText - ? Icons.visibility - : Icons.visibility_off, + const SizedBox(height: 10), + TextFormField( + controller: usernameController, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.person), + labelText: 'Username', + border: const OutlineInputBorder( + borderRadius: + BorderRadius.all(Radius.circular(12.0)), ), - onPressed: () { - setState(() { - _obscureText = !_obscureText; - }); - }, - ), - ), - ), - if (passwordError.isNotEmpty) - Padding( - padding: - const EdgeInsets.only(top: 8.0, left: 12.0), - child: Text( - passwordError, - style: const TextStyle( - color: Colors.red, - fontSize: 12.0, + hintText: 'Username', + hintStyle: TextStyle( + color: const Color.fromARGB(92, 0, 0, 0) + .withOpacity(0.25), + fontSize: 14.0, ), ), ), - const SizedBox( - height: 20, - ), - ElevatedButton( - onPressed: registerUser, - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12.0), + if (usernameError.isNotEmpty) + Padding( + padding: + const EdgeInsets.only(top: 8.0, left: 12.0), + child: Text( + usernameError, + style: const TextStyle( + color: Colors.red, + fontSize: 12.0, + ), + ), + ), + const SizedBox(height: 10), + TextFormField( + controller: fullnameController, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.perm_identity), + labelText: 'Fullname', + border: const OutlineInputBorder( + borderRadius: + BorderRadius.all(Radius.circular(12.0)), + ), + hintText: 'Fullname', + hintStyle: TextStyle( + color: Colors.black.withOpacity(0.25), + fontSize: 14.0, + ), ), - padding: const EdgeInsets.symmetric(vertical: 16.0), - minimumSize: const Size(double.infinity, 50), ), - child: const Text('Daftar'), - ), - ], + const SizedBox(height: 10), + TextFormField( + controller: passwordController, + obscureText: _obscureText, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.password), + labelText: 'Password', + border: const OutlineInputBorder( + borderRadius: + BorderRadius.all(Radius.circular(12.0)), + ), + hintText: 'Password', + hintStyle: TextStyle( + color: Colors.black.withOpacity(0.25), + fontSize: 14.0, + ), + suffixIcon: IconButton( + icon: Icon( + _obscureText + ? Icons.visibility + : Icons.visibility_off, + ), + onPressed: () { + setState(() { + _obscureText = !_obscureText; + }); + }, + ), + ), + ), + if (passwordError.isNotEmpty) + Padding( + padding: + const EdgeInsets.only(top: 8.0, left: 12.0), + child: Text( + passwordError, + style: const TextStyle( + color: Colors.red, + fontSize: 12.0, + ), + ), + ), + const SizedBox( + height: 20, + ), + ElevatedButton( + onPressed: registerUser, + style: ElevatedButton.styleFrom( + backgroundColor: Color.fromARGB(255, 221, 41, 41), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + ), + padding: + const EdgeInsets.symmetric(vertical: 16.0), + minimumSize: const Size(double.infinity, 50), + ), + child: const Text( + 'Daftar', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox( + height: 20, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'Sudah memiliki akun?', + style: TextStyle( + color: Colors.black, + fontSize: 14.0, + ), + ), + GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const LoginPage(), + ), + ); + }, + child: const Text( + ' Masuk', + style: TextStyle( + color: Colors.green, + fontSize: 14.0, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ], + ), ), ), - ), - ], + const SizedBox(height: 20), + const Text( + "Apps Made For", + style: TextStyle(color: Colors.black, fontSize: 10), + ), + const Text( + "Nutrient Hydroponic Control", + style: TextStyle(color: Colors.black, fontSize: 10.0), + ), + const Text( + "@2024", + style: TextStyle(color: Colors.black, fontSize: 10.0), + ), + ], + ), ), ), ), diff --git a/lib/page/homecontent.dart b/lib/page/homecontent.dart index 97ce2c1..e896321 100644 --- a/lib/page/homecontent.dart +++ b/lib/page/homecontent.dart @@ -258,8 +258,8 @@ class _ChartsSectionState extends State { ), primaryYAxis: NumericAxis( minimum: 0, - maximum: 20, - interval: 5, + maximum: 1000, + interval: 200, majorGridLines: MajorGridLines(width: 0.5, color: Colors.grey[300]), axisLine: AxisLine(width: 0), labelStyle: TextStyle(color: Colors.grey[700], fontSize: 12), diff --git a/lib/splash_screen.dart b/lib/splash_screen.dart index efa6fa3..eb64563 100644 --- a/lib/splash_screen.dart +++ b/lib/splash_screen.dart @@ -33,7 +33,7 @@ class _SplashScreenState extends State { backgroundColor: Colors.white, // Ubah sesuai warna latar belakang yang diinginkan body: Center( - child: Image.asset('images/nature.gif', + child: Image.asset('images/nutrify.png', width: 200, height: 200), // Pastikan untuk menambahkan logo Anda di folder assets diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index fe1d18c..e286f1d 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -7,7 +7,7 @@ project(runner LANGUAGES CXX) set(BINARY_NAME "beta_app1") # The unique GTK application identifier for this application. See: # https://wiki.gnome.org/HowDoI/ChooseApplicationID -set(APPLICATION_ID "com.example.beta_app1") +set(APPLICATION_ID "com.nutrify.app") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. diff --git a/pubspec.lock b/pubspec.lock index 1da6f4c..4a62c39 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + archive: + dependency: transitive + description: + name: archive + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d + url: "https://pub.dev" + source: hosted + version: "3.6.1" args: dependency: transitive description: @@ -65,6 +73,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + 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: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + url: "https://pub.dev" + source: hosted + version: "0.4.1" clock: dependency: transitive description: @@ -166,6 +190,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.3.2" + flutter_launcher_icons: + dependency: "direct main" + description: + name: flutter_launcher_icons + sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" + url: "https://pub.dev" + source: hosted + version: "0.13.1" flutter_lints: dependency: "direct dev" description: @@ -232,6 +264,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + image: + dependency: transitive + description: + name: image + sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8" + url: "https://pub.dev" + source: hosted + version: "4.2.0" intl: dependency: transitive description: @@ -240,6 +280,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.18.1" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" leak_tracker: dependency: transitive description: @@ -661,6 +709,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.5.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" sdks: dart: ">=3.3.4 <4.0.0" flutter: ">=3.19.0" diff --git a/pubspec.yaml b/pubspec.yaml index 0997562..9ee6cec 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,6 +42,7 @@ dependencies: liquid_progress_indicator_v2: ^0.5.0 cached_network_image: ^3.3.1 flutter_local_notifications: ^17.2.1+2 + flutter_launcher_icons: "^0.13.1" dev_dependencies: flutter_test: @@ -54,6 +55,12 @@ dev_dependencies: # rules and activating additional ones. flutter_lints: ^4.0.0 +flutter_launcher_icons: + android: "launcher_icon" + ios: true + image_path: "images/nutrify.png" + min_sdk_android: 21 # android min sdk min:16, default 21 + # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -66,12 +73,10 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - - images/ahri_coven.gif - - images/ahri_ori.gif - images/ahri_icon.jpg - images/nature.gif - images/arga.jpg - - images/hidroponik.png + - images/nutrify.png # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware diff --git a/test_api/data_chart.php b/test_api/data_chart.php new file mode 100644 index 0000000..80c0ee4 --- /dev/null +++ b/test_api/data_chart.php @@ -0,0 +1,93 @@ +connect_error) { + die("Connection failed: " . $conn->connect_error); +} + +// Mendapatkan tanggal mulai dan akhir dari permintaan +$startDate = isset($_GET['start_date']) ? $_GET['start_date'] : null; +$endDate = isset($_GET['end_date']) ? $_GET['end_date'] : null; + +// Menyiapkan klausa WHERE untuk filter tanggal +$whereClause = ""; +if ($startDate && $endDate) { + $whereClause = " WHERE timestamp BETWEEN '$startDate' AND '$endDate'"; +} + +$data = array(); +$data1 = array(); +$data2 = array(); +$data3 = array(); + +// Mengambil data dari tabel tb_waterflow1 termasuk kolom timestamp dan id +$sql = "SELECT id_wf1 as x, timestamp, waterflow1_value as y1 FROM tb_waterflow1 $whereClause"; +$result = $conn->query($sql); + +if ($result) { + if ($result->num_rows > 0) { + while ($row = $result->fetch_assoc()) { + $data[] = $row; + } + } +} else { + echo "Error: " . $conn->error; +} + +// Mengambil data dari tabel tb_waterflow2 termasuk kolom timestamp dan id +$sql1 = "SELECT id_wf2 as x, timestamp, waterflow2_value as y2 FROM tb_waterflow2 $whereClause"; +$result1 = $conn->query($sql1); + +if ($result1) { + if ($result1->num_rows > 0) { + while ($row = $result1->fetch_assoc()) { + $data1[] = $row; + } + } +} else { + echo "Error: " . $conn->error; +} + +// Mengambil data dari tabel tb_ppm termasuk kolom timestamp dan id +$sql2 = "SELECT id_ppm as x, timestamp, ppm_value as y3 FROM tb_ppm $whereClause"; +$result2 = $conn->query($sql2); + +if ($result2) { + if ($result2->num_rows > 0) { + while ($row = $result2->fetch_assoc()) { + $data2[] = $row; + } + } +} else { + echo "Error: " . $conn->error; +} + +// Mengambil data dari tabel tb_waterlevel termasuk kolom timestamp dan id +$sql3 = "SELECT id_wl as x, timestamp, ultrasonic_value as y FROM tb_waterlevel $whereClause"; +$result3 = $conn->query($sql3); + +if ($result3) { + if ($result3->num_rows > 0) { + while ($row = $result3->fetch_assoc()) { + $data3[] = $row; + } + } +} else { + echo "Error: " . $conn->error; +} + +// Tutup koneksi +$conn->close(); + +// Mengatur header untuk respon JSON +header('Content-Type: application/json'); + +// Mengirimkan data sebagai JSON +echo json_encode(array( + 'tb_waterflow1' => $data, + 'tb_waterflow2' => $data1, + 'tb_ppm' => $data2, + 'tb_waterlevel' => $data3 +)); diff --git a/test_api/data_plant.php b/test_api/data_plant.php new file mode 100644 index 0000000..4f7b213 --- /dev/null +++ b/test_api/data_plant.php @@ -0,0 +1,42 @@ + 0) { + // Ambil setiap baris data tanaman dan tambahkan ke array + while ($row = mysqli_fetch_assoc($result)) { + // Tambahkan URL penuh untuk gambar + $row['img'] = $base_url . $row['img']; + $plants[] = $row; + } +} + +// Konversi array ke format JSON +$json_response = json_encode($plants); + +// Set header untuk respon JSON +header('Content-Type: application/json'); + +// Tampilkan respon JSON +echo $json_response; + +// Tutup koneksi database +mysqli_close($conn); diff --git a/test_api/dbconnection.php b/test_api/dbconnection.php new file mode 100644 index 0000000..322bc5b --- /dev/null +++ b/test_api/dbconnection.php @@ -0,0 +1,27 @@ + + + + + + \ No newline at end of file diff --git a/test_api/get_data.php b/test_api/get_data.php new file mode 100644 index 0000000..6310bf0 --- /dev/null +++ b/test_api/get_data.php @@ -0,0 +1,43 @@ + 0) { + // Ambil setiap baris data pengguna dan tambahkan ke array + while ($row = mysqli_fetch_assoc($result)) { + $users[] = $row; + } +} + +// Konversi array ke format JSON +$json_response = json_encode($users); + +// Set header untuk respon JSON +header('Content-Type: application/json'); + +// Tampilkan respon JSON +echo $json_response; + +// Tutup koneksi database +mysqli_close($conn); diff --git a/test_api/login.php b/test_api/login.php new file mode 100644 index 0000000..e3924e4 --- /dev/null +++ b/test_api/login.php @@ -0,0 +1,44 @@ +connect_error) { + die("Connection failed: " . $conn->connect_error); +} + +$username = $_POST['username']; +$password = $_POST['password']; + +// Query untuk mengambil hash password berdasarkan username +$query = $conn->prepare("SELECT * FROM tb_login WHERE username=?"); +$query->bind_param("s", $username); +$query->execute(); +$result = $query->get_result(); + +if ($result->num_rows == 1) { + $data = $result->fetch_assoc(); + $hashedPassword = $data['password']; + + // Verifikasi password + if (password_verify($password, $hashedPassword)) { + $token = bin2hex(random_bytes(10)); // Generate a random token with 10 bytes + + // Simpan token ke database + $updateQuery = $conn->prepare("UPDATE tb_login SET token=? WHERE username=?"); + $updateQuery->bind_param("ss", $token, $username); + if ($updateQuery->execute()) { + $data['token'] = $token; + echo json_encode(array("success" => true, "data" => $data)); + } else { + echo json_encode(array("success" => false, "message" => "Failed to update token")); + } + } else { + echo json_encode(array("success" => false, "message" => "Invalid username or password.")); + } +} else { + echo json_encode(array("success" => false, "message" => "Invalid username or password.")); +} + +$conn->close(); diff --git a/test_api/logout.php b/test_api/logout.php new file mode 100644 index 0000000..0d7a9da --- /dev/null +++ b/test_api/logout.php @@ -0,0 +1,21 @@ +connect_error) { + die("Connection failed: " . $conn->connect_error); +} + +$token = $_POST['token']; + +$query = "UPDATE tb_login SET token='' WHERE token='$token'"; + +if (mysqli_query($conn, $query)) { + echo json_encode(["success" => true]); +} else { + echo json_encode(["error" => "Failed to update token."]); +} + +$conn->close(); diff --git a/test_api/register.php b/test_api/register.php new file mode 100644 index 0000000..6f6c23e --- /dev/null +++ b/test_api/register.php @@ -0,0 +1,57 @@ +connect_error) { + echo json_encode(array("status" => "error", "message" => "Connection failed: " . $conn->connect_error)); + exit(); +} + +// Get POST data +$data = json_decode(file_get_contents("php://input"), true); +$username = $data['username']; +$password = $data['password']; +$fullname = $data['fullname']; + +// Validate input data +if (empty($username) || empty($password) || empty($fullname)) { + echo json_encode(array("status" => "error", "message" => "All fields are required.")); + exit(); +} + +// Check if username already exists +$sql = "SELECT * FROM tb_login WHERE username = ?"; +$stmt = $conn->prepare($sql); +$stmt->bind_param("s", $username); +$stmt->execute(); +$result = $stmt->get_result(); +if ($result->num_rows > 0) { + echo json_encode(array("status" => "error", "message" => "Username already exists.")); + exit(); +} + +// Hash the password +$hashed_password = password_hash($password, PASSWORD_DEFAULT); + +// Insert new user +$sql = "INSERT INTO tb_login (username, password, fullname) VALUES (?, ?, ?)"; +$stmt = $conn->prepare($sql); +$stmt->bind_param("sss", $username, $hashed_password, $fullname); + +if ($stmt->execute()) { + echo json_encode(array("status" => "success", "message" => "Registration successful.")); +} else { + echo json_encode(array("status" => "error", "message" => "Registration failed.")); +} + +// Close connections +$stmt->close(); +$conn->close(); diff --git a/test_api/save_msg.php b/test_api/save_msg.php new file mode 100644 index 0000000..2c2c1fe --- /dev/null +++ b/test_api/save_msg.php @@ -0,0 +1,48 @@ +connect_error) { + die("Connection failed: " . $conn->connect_error); +} + +// Get JSON input +$input = file_get_contents('php://input'); +$data = json_decode($input, true); + +$topic = $data['topic']; +$value = $data['value']; +$speed = isset($data['speed']) ? $data['speed'] : null; + +if ($topic == 'sensor/waterflow1') { + $stmt = $conn->prepare("INSERT INTO tb_waterflow1 (timestamp, waterflow1_value, waterflow1_speed) VALUES (NOW(), ?, ?)"); + $stmt->bind_param("dd", $value, $speed); +} else if ($topic == 'sensor/waterflow2') { + $stmt = $conn->prepare("INSERT INTO tb_waterflow2 (timestamp, waterflow2_value, waterflow2_speed) VALUES (NOW(), ?, ?)"); + $stmt->bind_param("dd", $value, $speed); +} else if ($topic == 'sensor/tds') { + $stmt = $conn->prepare("INSERT INTO tb_ppm (timestamp, ppm_value) VALUES (NOW(), ?)"); + $stmt->bind_param("d", $value); +} else if ($topic == 'sensor/ultrasonic') { + $stmt = $conn->prepare("INSERT INTO tb_waterlevel (timestamp, ultrasonic_value) VALUES (NOW(), ?)"); + $stmt->bind_param("d", $value); +} else { + echo json_encode(["status" => "error", "message" => "Unknown topic"]); + exit; +} + +if ($stmt->execute()) { + echo json_encode(["status" => "success"]); +} else { + echo json_encode(["status" => "error", "message" => $stmt->error]); +} + +$stmt->close(); +$conn->close(); diff --git a/upload-keystore.jks b/upload-keystore.jks new file mode 100644 index 0000000..6ee7530 Binary files /dev/null and b/upload-keystore.jks differ