first commit

This commit is contained in:
amrimd 2025-08-12 12:31:32 +07:00
commit 71c0e8a538
162 changed files with 10326 additions and 0 deletions

View File

@ -0,0 +1,137 @@
#include <WiFi.h>
#include <HTTPClient.h>
#include <SPI.h>
#include <MFRC522.h>
#include <LiquidCrystal_I2C.h>
const char* ssid = "amriiiii.02";
const char* password = "123sampai10";
const char* serverName = "http://192.168.92.176/playground_api/receive_rfid_scan.php";
#define SS_PIN 5
#define RST_PIN 15
MFRC522 rfid(SS_PIN, RST_PIN);
LiquidCrystal_I2C lcd(0x27, 16, 2);
void setup() {
Serial.begin(115200);
lcd.init();
lcd.backlight();
SPI.begin();
rfid.PCD_Init();
lcd.setCursor(0, 0);
lcd.print("Menghubungkan...");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
lcd.setCursor(0, 1);
lcd.print("WiFi Connecting..");
}
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("WiFi Tersambung");
Serial.println("WiFi Tersambung!");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
delay(1000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("SISTEM PEMANTAU");
lcd.setCursor(0, 1);
lcd.print("ANAK PLAYGROUND");
delay(2000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("TEMPELKAN GELANG");
lcd.setCursor(0, 1);
lcd.print("UNTUK MENDAFTAR");
Serial.println("Sistem siap. Tempelkan gelang RFID.");
}
void loop() {
if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) {
delay(50);
return;
}
String uidStr = "";
for (byte i = 0; i < rfid.uid.size; i++) {
if (rfid.uid.uidByte[i] < 0x10) uidStr += "0";
uidStr += String(rfid.uid.uidByte[i], HEX);
}
uidStr.toUpperCase();
Serial.print("UID Gelang Terbaca: ");
Serial.println(uidStr);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("GELANG TERBACA");
lcd.setCursor(0, 1);
lcd.print(uidStr);
delay(1000);
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin(serverName);
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
// Hanya kirim kode_gelang
String httpRequestData = "kode_gelang=" + uidStr;
Serial.print("Mengirim data: ");
Serial.println(httpRequestData);
int httpResponseCode = http.POST(httpRequestData);
lcd.clear();
lcd.setCursor(0, 0);
if (httpResponseCode > 0) {
String response = http.getString();
Serial.print("HTTP Response Code: ");
Serial.println(httpResponseCode);
Serial.print("Server Response: ");
Serial.println(response);
lcd.print("Data Dikirim OK");
lcd.setCursor(0, 1);
lcd.print("Status: ");
lcd.print(httpResponseCode);
} else {
Serial.print("Error Mengirim Data. HTTP Response Code: ");
Serial.println(httpResponseCode);
lcd.print("Gagal Kirim");
lcd.setCursor(0, 1);
lcd.print("Kode: ");
lcd.print(httpResponseCode);
}
http.end();
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("WiFi Terputus!");
lcd.setCursor(0, 1);
lcd.print("Reconnecting...");
Serial.println("WiFi Terputus, mencoba menyambungkan ulang...");
WiFi.reconnect();
delay(2000);
}
delay(2000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("TEMPELKAN GELANG");
lcd.setCursor(0, 1);
lcd.print("UNTUK MENDAFTAR");
}

138
ALAT_IOT/Interaktif.ino Normal file
View File

@ -0,0 +1,138 @@
#include <WiFi.h>
#include <HTTPClient.h>
#include <SPI.h>
#include <MFRC522.h>
#include <LiquidCrystal_I2C.h>
const char* ssid = "amriiiii.02";
const char* password = "123sampai10";
const char* serverName = "http://192.168.92.176/playground_api/process_wahana_scan.php";
const char* namaWahana = "Interaktif";
#define SS_PIN 5
#define RST_PIN 15
MFRC522 rfid(SS_PIN, RST_PIN);
LiquidCrystal_I2C lcd(0x27, 16, 2);
void setup() {
Serial.begin(115200);
lcd.init();
lcd.backlight();
SPI.begin();
rfid.PCD_Init();
lcd.setCursor(0, 0);
lcd.print("Menghubungkan...");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
lcd.setCursor(0, 1);
lcd.print("WiFi Connecting..");
}
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("WiFi Tersambung");
Serial.println("WiFi Tersambung!");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
delay(1000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("SISTEM PEMANTAU");
lcd.setCursor(0, 1);
lcd.print("ANAK PLAYGROUND");
delay(2000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("TEMPELKAN GELANG");
lcd.setCursor(0, 1);
lcd.print(" UNTUK MASUK ");
Serial.println("Sistem siap. Tempelkan gelang RFID.");
}
void loop() {
if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) {
delay(50);
return;
}
String uidStr = "";
for (byte i = 0; i < rfid.uid.size; i++) {
if (rfid.uid.uidByte[i] < 0x10) uidStr += "0";
uidStr += String(rfid.uid.uidByte[i], HEX);
}
uidStr.toUpperCase();
Serial.print("UID Gelang Terbaca: ");
Serial.println(uidStr);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("GELANG TERBACA");
lcd.setCursor(0, 1);
lcd.print(uidStr);
delay(1000);
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin(serverName);
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
String httpRequestData = "kode_gelang=" + uidStr + "&wahana=" + String(namaWahana);
Serial.print("Mengirim data: ");
Serial.println(httpRequestData);
int httpResponseCode = http.POST(httpRequestData);
lcd.clear();
lcd.setCursor(0, 0);
if (httpResponseCode > 0) {
String response = http.getString();
Serial.print("HTTP Response Code: ");
Serial.println(httpResponseCode);
Serial.print("Server Response: ");
Serial.println(response);
lcd.print("Data Dikirim OK");
lcd.setCursor(0, 1);
lcd.print("Status: ");
lcd.print(httpResponseCode);
} else {
Serial.print("Error Mengirim Data. HTTP Response Code: ");
Serial.println(httpResponseCode);
lcd.print("Gagal Kirim");
lcd.setCursor(0, 1);
lcd.print("Kode: ");
lcd.print(httpResponseCode);
}
http.end();
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("WiFi Terputus!");
lcd.setCursor(0, 1);
lcd.print("Reconnecting...");
Serial.println("WiFi Terputus, mencoba menyambungkan ulang...");
WiFi.reconnect();
delay(2000);
}
delay(2000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("TEMPELKAN GELANG");
lcd.setCursor(0, 1);
lcd.print(" UNTUK MASUK ");
}

138
ALAT_IOT/Simulasi.ino Normal file
View File

@ -0,0 +1,138 @@
#include <WiFi.h>
#include <HTTPClient.h>
#include <SPI.h>
#include <MFRC522.h>
#include <LiquidCrystal_I2C.h>
const char* ssid = "amriiiii.02";
const char* password = "123sampai10";
const char* serverName = "http://192.168.92.176/playground_api/process_wahana_scan.php";
const char* namaWahana = "Simulasi";
#define SS_PIN 5
#define RST_PIN 15
MFRC522 rfid(SS_PIN, RST_PIN);
LiquidCrystal_I2C lcd(0x27, 16, 2);
void setup() {
Serial.begin(115200);
lcd.init();
lcd.backlight();
SPI.begin();
rfid.PCD_Init();
lcd.setCursor(0, 0);
lcd.print("Menghubungkan...");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
lcd.setCursor(0, 1);
lcd.print("WiFi Connecting..");
}
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("WiFi Tersambung");
Serial.println("WiFi Tersambung!");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
delay(1000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("SISTEM PEMANTAU");
lcd.setCursor(0, 1);
lcd.print("ANAK PLAYGROUND");
delay(2000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("TEMPELKAN GELANG");
lcd.setCursor(0, 1);
lcd.print(" UNTUK MASUK ");
Serial.println("Sistem siap. Tempelkan gelang RFID.");
}
void loop() {
if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) {
delay(50);
return;
}
String uidStr = "";
for (byte i = 0; i < rfid.uid.size; i++) {
if (rfid.uid.uidByte[i] < 0x10) uidStr += "0";
uidStr += String(rfid.uid.uidByte[i], HEX);
}
uidStr.toUpperCase();
Serial.print("UID Gelang Terbaca: ");
Serial.println(uidStr);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("GELANG TERBACA");
lcd.setCursor(0, 1);
lcd.print(uidStr);
delay(1000);
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin(serverName);
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
String httpRequestData = "kode_gelang=" + uidStr + "&wahana=" + String(namaWahana);
Serial.print("Mengirim data: ");
Serial.println(httpRequestData);
int httpResponseCode = http.POST(httpRequestData);
lcd.clear();
lcd.setCursor(0, 0);
if (httpResponseCode > 0) {
String response = http.getString();
Serial.print("HTTP Response Code: ");
Serial.println(httpResponseCode);
Serial.print("Server Response: ");
Serial.println(response);
lcd.print("Data Dikirim OK");
lcd.setCursor(0, 1);
lcd.print("Status: ");
lcd.print(httpResponseCode);
} else {
Serial.print("Error Mengirim Data. HTTP Response Code: ");
Serial.println(httpResponseCode);
lcd.print("Gagal Kirim");
lcd.setCursor(0, 1);
lcd.print("Kode: ");
lcd.print(httpResponseCode);
}
http.end();
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("WiFi Terputus!");
lcd.setCursor(0, 1);
lcd.print("Reconnecting...");
Serial.println("WiFi Terputus, mencoba menyambungkan ulang...");
WiFi.reconnect();
delay(2000);
}
delay(2000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("TEMPELKAN GELANG");
lcd.setCursor(0, 1);
lcd.print(" UNTUK MASUK ");
}

138
ALAT_IOT/Virtual.ino Normal file
View File

@ -0,0 +1,138 @@
#include <WiFi.h>
#include <HTTPClient.h>
#include <SPI.h>
#include <MFRC522.h>
#include <LiquidCrystal_I2C.h>
const char* ssid = "amriiiii.02";
const char* password = "123sampai10";
const char* serverName = "http://192.168.92.176/playground_api/process_wahana_scan.php";
const char* namaWahana = "Virtual";
#define SS_PIN 5
#define RST_PIN 15
MFRC522 rfid(SS_PIN, RST_PIN);
LiquidCrystal_I2C lcd(0x27, 16, 2);
void setup() {
Serial.begin(115200);
lcd.init();
lcd.backlight();
SPI.begin();
rfid.PCD_Init();
lcd.setCursor(0, 0);
lcd.print("Menghubungkan...");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
lcd.setCursor(0, 1);
lcd.print("WiFi Connecting..");
}
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("WiFi Tersambung");
Serial.println("WiFi Tersambung!");
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
delay(1000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("SISTEM PEMANTAU");
lcd.setCursor(0, 1);
lcd.print("ANAK PLAYGROUND");
delay(2000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("TEMPELKAN GELANG");
lcd.setCursor(0, 1);
lcd.print(" UNTUK MASUK ");
Serial.println("Sistem siap. Tempelkan gelang RFID.");
}
void loop() {
if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial()) {
delay(50);
return;
}
String uidStr = "";
for (byte i = 0; i < rfid.uid.size; i++) {
if (rfid.uid.uidByte[i] < 0x10) uidStr += "0";
uidStr += String(rfid.uid.uidByte[i], HEX);
}
uidStr.toUpperCase();
Serial.print("UID Gelang Terbaca: ");
Serial.println(uidStr);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("GELANG TERBACA");
lcd.setCursor(0, 1);
lcd.print(uidStr);
delay(1000);
if (WiFi.status() == WL_CONNECTED) {
HTTPClient http;
http.begin(serverName);
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
String httpRequestData = "kode_gelang=" + uidStr + "&wahana=" + String(namaWahana);
Serial.print("Mengirim data: ");
Serial.println(httpRequestData);
int httpResponseCode = http.POST(httpRequestData);
lcd.clear();
lcd.setCursor(0, 0);
if (httpResponseCode > 0) {
String response = http.getString();
Serial.print("HTTP Response Code: ");
Serial.println(httpResponseCode);
Serial.print("Server Response: ");
Serial.println(response);
lcd.print("Data Dikirim OK");
lcd.setCursor(0, 1);
lcd.print("Status: ");
lcd.print(httpResponseCode);
} else {
Serial.print("Error Mengirim Data. HTTP Response Code: ");
Serial.println(httpResponseCode);
lcd.print("Gagal Kirim");
lcd.setCursor(0, 1);
lcd.print("Kode: ");
lcd.print(httpResponseCode);
}
http.end();
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("WiFi Terputus!");
lcd.setCursor(0, 1);
lcd.print("Reconnecting...");
Serial.println("WiFi Terputus, mencoba menyambungkan ulang...");
WiFi.reconnect();
delay(2000);
}
delay(2000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("TEMPELKAN GELANG");
lcd.setCursor(0, 1);
lcd.print(" UNTUK MASUK ");
}

15
APLIKASI/.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

3
APLIKASI/.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

1
APLIKASI/.idea/.name Normal file
View File

@ -0,0 +1 @@
MoniKids

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>

19
APLIKASI/.idea/gradle.xml Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

9
APLIKASI/.idea/misc.xml Normal file
View File

@ -0,0 +1,9 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

1
APLIKASI/app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

41
APLIKASI/app/build.gradle Normal file
View File

@ -0,0 +1,41 @@
plugins {
alias(libs.plugins.android.application)
}
android {
namespace 'com.tugasakhir.monikids'
compileSdk 35
defaultConfig {
applicationId "com.tugasakhir.monikids"
minSdk 24
targetSdk 35
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
}
dependencies {
implementation libs.appcompat
implementation libs.material
implementation libs.activity
implementation libs.constraintlayout
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
implementation 'com.android.volley:volley:1.2.1'
}

21
APLIKASI/app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,26 @@
package com.tugasakhir.monikids;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.tugasakhir.monikids", appContext.getPackageName());
}
}

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MoniKids"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".view.IPSettingsActivity"
android:exported="false">
</activity>
<activity
android:name=".view.HomeActivity"
android:exported="false" />
<activity
android:name=".view.LoginActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:exported="false" />
</application>
</manifest>

View File

@ -0,0 +1,24 @@
package com.tugasakhir.monikids;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
}
}

View File

@ -0,0 +1,281 @@
package com.tugasakhir.monikids.utils;
import android.animation.ObjectAnimator;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
import com.tugasakhir.monikids.R;
public class CustomAlertDialog {
public enum AlertType {
SUCCESS, ERROR, WARNING, INFO
}
public interface OnAlertClickListener {
void onPositiveClick();
void onNegativeClick();
}
private Dialog dialog;
private Context context;
private View dialogView;
private TextView tvTitle, tvMessage;
private ImageView ivIcon;
private Button btnPositive, btnNegative;
private ViewGroup layoutButtons;
public CustomAlertDialog(Context context) {
this.context = context;
initDialog();
}
private void initDialog() {
dialog = new Dialog(context);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
dialog.setCancelable(false);
LayoutInflater inflater = LayoutInflater.from(context);
dialogView = inflater.inflate(R.layout.dialog_custom_alert, null);
dialog.setContentView(dialogView);
// Initialize views
tvTitle = dialogView.findViewById(R.id.tvAlertTitle);
tvMessage = dialogView.findViewById(R.id.tvAlertMessage);
ivIcon = dialogView.findViewById(R.id.ivAlertIcon);
btnPositive = dialogView.findViewById(R.id.btnPositive);
btnNegative = dialogView.findViewById(R.id.btnNegative);
layoutButtons = dialogView.findViewById(R.id.layoutButtons);
// Set dialog size dengan proporsi yang lebih baik
setupDialogSize();
}
private void setupDialogSize() {
Window window = dialog.getWindow();
if (window != null) {
WindowManager.LayoutParams layoutParams = window.getAttributes();
// Dapatkan ukuran layar
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
int screenWidth = displayMetrics.widthPixels;
int screenHeight = displayMetrics.heightPixels;
// Set lebar dialog: 85% dari lebar layar (atau maksimal 400dp)
int maxWidthDp = 400; // maksimal 400dp
int maxWidthPx = (int) (maxWidthDp * displayMetrics.density);
int desiredWidth = (int) (screenWidth * 0.85f); // 85% dari lebar layar
layoutParams.width = Math.min(desiredWidth, maxWidthPx);
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
// Center dialog di layar
layoutParams.gravity = android.view.Gravity.CENTER;
window.setAttributes(layoutParams);
}
}
public CustomAlertDialog setTitle(String title) {
tvTitle.setText(title);
tvTitle.setVisibility(View.VISIBLE);
return this;
}
public CustomAlertDialog setMessage(String message) {
tvMessage.setText(message);
return this;
}
public CustomAlertDialog setAlertType(AlertType type) {
switch (type) {
case SUCCESS:
ivIcon.setImageResource(R.drawable.ic_check_circle);
ivIcon.setColorFilter(ContextCompat.getColor(context, R.color.success_color));
tvTitle.setTextColor(ContextCompat.getColor(context, R.color.success_color));
break;
case ERROR:
ivIcon.setImageResource(R.drawable.ic_error_circle);
ivIcon.setColorFilter(ContextCompat.getColor(context, R.color.error_color));
tvTitle.setTextColor(ContextCompat.getColor(context, R.color.error_color));
break;
case WARNING:
ivIcon.setImageResource(R.drawable.ic_warning_circle);
ivIcon.setColorFilter(ContextCompat.getColor(context, R.color.warning_color));
tvTitle.setTextColor(ContextCompat.getColor(context, R.color.warning_color));
break;
case INFO:
ivIcon.setImageResource(R.drawable.ic_info_circle);
ivIcon.setColorFilter(ContextCompat.getColor(context, R.color.purple_primary));
tvTitle.setTextColor(ContextCompat.getColor(context, R.color.purple_primary));
break;
}
return this;
}
public CustomAlertDialog setPositiveButton(String text, OnAlertClickListener listener) {
btnPositive.setText(text);
btnPositive.setVisibility(View.VISIBLE);
btnPositive.setOnClickListener(v -> {
animateButtonPress(v);
if (listener != null) {
listener.onPositiveClick();
}
dismiss();
});
return this;
}
public CustomAlertDialog setNegativeButton(String text, OnAlertClickListener listener) {
btnNegative.setText(text);
btnNegative.setVisibility(View.VISIBLE);
btnNegative.setOnClickListener(v -> {
animateButtonPress(v);
if (listener != null) {
listener.onNegativeClick();
}
dismiss();
});
return this;
}
public CustomAlertDialog setCancelable(boolean cancelable) {
dialog.setCancelable(cancelable);
return this;
}
public void show() {
if (!dialog.isShowing()) {
dialog.show();
animateShow();
}
}
public void dismiss() {
if (dialog.isShowing()) {
animateHide(() -> dialog.dismiss());
}
}
private void animateShow() {
// Scale and fade in animation dengan efek lebih smooth
ObjectAnimator scaleX = ObjectAnimator.ofFloat(dialogView, "scaleX", 0.8f, 1.05f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(dialogView, "scaleY", 0.8f, 1.05f, 1f);
ObjectAnimator alpha = ObjectAnimator.ofFloat(dialogView, "alpha", 0f, 1f);
scaleX.setDuration(400);
scaleY.setDuration(400);
alpha.setDuration(300);
scaleX.setInterpolator(new AccelerateDecelerateInterpolator());
scaleY.setInterpolator(new AccelerateDecelerateInterpolator());
alpha.setInterpolator(new AccelerateDecelerateInterpolator());
scaleX.start();
scaleY.start();
alpha.start();
// Icon bounce animation yang lebih halus
ObjectAnimator iconBounce = ObjectAnimator.ofFloat(ivIcon, "scaleX", 0f, 1.3f, 1f);
ObjectAnimator iconBounceY = ObjectAnimator.ofFloat(ivIcon, "scaleY", 0f, 1.3f, 1f);
iconBounce.setDuration(500);
iconBounceY.setDuration(500);
iconBounce.setStartDelay(200);
iconBounceY.setStartDelay(200);
iconBounce.setInterpolator(new AccelerateDecelerateInterpolator());
iconBounceY.setInterpolator(new AccelerateDecelerateInterpolator());
iconBounce.start();
iconBounceY.start();
}
private void animateHide(Runnable onComplete) {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(dialogView, "scaleX", 1f, 0.9f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(dialogView, "scaleY", 1f, 0.9f);
ObjectAnimator alpha = ObjectAnimator.ofFloat(dialogView, "alpha", 1f, 0f);
scaleX.setDuration(250);
scaleY.setDuration(250);
alpha.setDuration(250);
alpha.addListener(new android.animation.AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(android.animation.Animator animation) {
onComplete.run();
}
});
scaleX.start();
scaleY.start();
alpha.start();
}
private void animateButtonPress(View button) {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(button, "scaleX", 1f, 0.95f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(button, "scaleY", 1f, 0.95f, 1f);
scaleX.setDuration(150);
scaleY.setDuration(150);
scaleX.start();
scaleY.start();
}
// Builder pattern for easy creation
public static class Builder {
private CustomAlertDialog dialog;
public Builder(Context context) {
dialog = new CustomAlertDialog(context);
}
public Builder setTitle(String title) {
dialog.setTitle(title);
return this;
}
public Builder setMessage(String message) {
dialog.setMessage(message);
return this;
}
public Builder setAlertType(AlertType type) {
dialog.setAlertType(type);
return this;
}
public Builder setPositiveButton(String text, OnAlertClickListener listener) {
dialog.setPositiveButton(text, listener);
return this;
}
public Builder setNegativeButton(String text, OnAlertClickListener listener) {
dialog.setNegativeButton(text, listener);
return this;
}
public Builder setCancelable(boolean cancelable) {
dialog.setCancelable(cancelable);
return this;
}
public CustomAlertDialog create() {
return dialog;
}
public void show() {
dialog.show();
}
}
}

View File

@ -0,0 +1,120 @@
package com.tugasakhir.monikids.utils;
import android.content.Context;
import android.content.SharedPreferences;
public class IPManager {
private static final String PREF_NAME = "IP_SETTINGS";
private static final String KEY_SERVER_IP = "SERVER_IP";
private static final String DEFAULT_IP = "192.168.18.120";
private static IPManager instance;
private SharedPreferences preferences;
private String currentIP;
private IPManager(Context context) {
preferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
currentIP = preferences.getString(KEY_SERVER_IP, DEFAULT_IP);
}
public static synchronized IPManager getInstance(Context context) {
if (instance == null) {
instance = new IPManager(context.getApplicationContext());
}
return instance;
}
/**
* Mendapatkan IP server saat ini
*/
public String getCurrentIP() {
return currentIP;
}
/**
* Mengubah IP server dan menyimpannya secara persisten
*/
public boolean setServerIP(String newIP) {
if (isValidIP(newIP)) {
currentIP = newIP;
SharedPreferences.Editor editor = preferences.edit();
editor.putString(KEY_SERVER_IP, newIP);
return editor.commit();
}
return false;
}
/**
* Mendapatkan base URL untuk API
*/
public String getBaseURL() {
return "http://" + currentIP + "/playground_api/";
}
/**
* Mendapatkan home URL
*/
public String getHomeURL() {
return "http://" + currentIP + "/playground_api/home.php";
}
/**
* Mendapatkan URL lengkap untuk endpoint tertentu
*/
public String getURL(String endpoint) {
return getBaseURL() + endpoint;
}
/**
* Validasi format IP address
*/
private boolean isValidIP(String ip) {
if (ip == null || ip.trim().isEmpty()) {
return false;
}
// Cek untuk localhost
if (ip.equals("localhost") || ip.equals("127.0.0.1")) {
return true;
}
// Cek untuk emulator
if (ip.equals("10.0.2.2")) {
return true;
}
// Validasi IP address format
String[] parts = ip.split("\\.");
if (parts.length != 4) {
return false;
}
try {
for (String part : parts) {
int num = Integer.parseInt(part);
if (num < 0 || num > 255) {
return false;
}
}
return true;
} catch (NumberFormatException e) {
return false;
}
}
/**
* Reset ke IP default
*/
public void resetToDefault() {
setServerIP(DEFAULT_IP);
}
/**
* Mendapatkan daftar IP yang umum digunakan
*/
public static class QuickIPs {
public static final String LOCALHOST = "127.0.0.1";
public static final String EMULATOR = "10.0.2.2";
public static final String DEFAULT = "192.168.18.120";
}
}

View File

@ -0,0 +1,635 @@
package com.tugasakhir.monikids.view;
import android.Manifest;
import android.animation.ObjectAnimator;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.cardview.widget.CardView;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;
import com.tugasakhir.monikids.R;
import com.tugasakhir.monikids.utils.CustomAlertDialog;
import com.tugasakhir.monikids.utils.IPManager; // Import IPManager
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class HomeActivity extends AppCompatActivity {
private static final String TAG = "HomeActivity";
// Hapus hardcoded HOME_URL, ini akan diset dari IPManager
// private static final String HOME_URL = "http://192.168.18.120/playground_api/home.php";
private String HOME_URL; // Deklarasikan sebagai non-static dan tanpa inisialisasi awal
private SharedPreferences sharedPreferences;
private RequestQueue requestQueue;
private IPManager ipManager; // Deklarasi instance IPManager
// UI Components
private TextView tvUserName, tvBandId, tvPhoneNumber, tvEntryTime;
private TextView tvLastAttraction, tvPlayDuration;
private LinearLayout llTitle, llStatistik, llLogout;
private LinearLayout timelineContainer; // Timeline container
private CardView cardLogout;
// User data
private int idAnak;
private String nama, kodeGelang;
private String lastKnownExitTime = null; // Untuk menyimpan waktu keluar terakhir yang diketahui
// Polling mechanism
private Handler handler;
private Runnable runnable;
private static final long POLLING_INTERVAL_MS = 10000; // Poll setiap 10 detik
// Notification specific
private static final String CHANNEL_ID = "monikids_exit_channel";
private static final int NOTIFICATION_ID = 101; // ID unik untuk notifikasi keluar
private Vibrator vibrator; // Untuk kontrol getaran manual
private CustomAlertDialog activeExitDialog = null; // Referensi ke dialog yang sedang aktif
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
initComponents();
setupAnimations();
getUserData();
createNotificationChannel(); // Buat channel notifikasi
// HOME_URL akan diinisialisasi di onResume untuk memastikan IP terbaru
// startPollingHomeData(); // Mulai polling data akan dipanggil di onResume
setupLogout();
}
private void initComponents() {
sharedPreferences = getSharedPreferences("MoniKidsPrefs", MODE_PRIVATE);
requestQueue = Volley.newRequestQueue(this);
ipManager = IPManager.getInstance(this); // Inisialisasi IPManager di sini
vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); // Inisialisasi vibrator
tvUserName = findViewById(R.id.tvUserName);
tvBandId = findViewById(R.id.tvBandId);
tvPhoneNumber = findViewById(R.id.tvPhoneNumber);
tvEntryTime = findViewById(R.id.tvEntryTime);
tvLastAttraction = findViewById(R.id.tvLastAttraction);
tvPlayDuration = findViewById(R.id.tvPlayDuration);
cardLogout = findViewById(R.id.cardLogout);
llTitle = findViewById(R.id.llTitle);
llStatistik = findViewById(R.id.llStatistik);
llLogout = findViewById(R.id.llLogout);
// Initialize timeline container
timelineContainer = findViewById(R.id.timelineContainer);
}
private void getUserData() {
idAnak = sharedPreferences.getInt("id_anak", 0);
nama = sharedPreferences.getString("nama", "");
kodeGelang = sharedPreferences.getString("kode_gelang", "");
tvUserName.setText(nama);
tvBandId.setText(kodeGelang);
// Ambil waktu keluar terakhir yang disimpan jika ada
lastKnownExitTime = sharedPreferences.getString("last_known_exit_time_" + idAnak, null);
Log.d(TAG, "Initial lastKnownExitTime: " + lastKnownExitTime);
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "Notifikasi Anak Keluar Playground";
String description = "Notifikasi penting ketika anak keluar dari area bermain";
int importance = NotificationManager.IMPORTANCE_HIGH; // Prioritas tinggi
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
channel.setDescription(description);
// Pengaturan getar untuk channel (pola berulang)
long[] vibrationPattern = {0, 1000, 500, 1000, 500, 1000, 500, 1000};
channel.enableVibration(true);
channel.setVibrationPattern(vibrationPattern);
// Menggunakan USAGE_NOTIFICATION karena USAGE_NOTIFICATION_COMMUNICATION membutuhkan API 29+
channel.setSound(null, new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
.build());
NotificationManager notificationManager = getSystemService(NotificationManager.class);
if (notificationManager != null) {
notificationManager.createNotificationChannel(channel);
}
}
}
private void startPollingHomeData() {
handler = new Handler(Looper.getMainLooper());
runnable = new Runnable() {
@Override
public void run() {
loadHomeData();
handler.postDelayed(this, POLLING_INTERVAL_MS);
}
};
handler.post(runnable);
}
private void stopPollingHomeData() {
if (handler != null && runnable != null) {
handler.removeCallbacks(runnable);
}
}
private void loadHomeData() {
if (idAnak == 0) {
showError("Data user tidak valid");
stopPollingHomeData();
return;
}
JSONObject requestData = new JSONObject();
try {
requestData.put("id_anak", idAnak);
} catch (JSONException e) {
Log.e(TAG, "Error creating request data", e);
return;
}
JsonObjectRequest request = new JsonObjectRequest(
Request.Method.POST,
HOME_URL, // Menggunakan HOME_URL yang sudah diset dari IPManager
requestData,
this::handleHomeResponse,
this::handleErrorResponse
) {
@Override
public java.util.Map<String, String> getHeaders() {
java.util.Map<String, String> headers = new java.util.HashMap<>();
headers.put("Content-Type", "application/json");
headers.put("Accept", "application/json");
return headers;
}
};
request.setRetryPolicy(new com.android.volley.DefaultRetryPolicy(
10000, // 10 seconds timeout
1, // no retry
1.0f
));
requestQueue.add(request);
}
private void handleHomeResponse(JSONObject response) {
try {
if (response.getBoolean("success")) {
JSONObject data = response.optJSONObject("data");
if (data != null) {
JSONObject userData = data.optJSONObject("user_data");
if (userData != null) {
updateUserData(userData);
String currentExitTime = userData.optString("exit_time", null);
// Cek apakah currentExitTime benar-benar string "null" dari PHP
if ("null".equals(currentExitTime)) {
currentExitTime = null;
}
checkAndNotifyChildExit(currentExitTime);
}
JSONArray timelineArray = data.optJSONArray("timeline");
if (timelineArray != null) {
populateTimeline(timelineArray);
}
}
} else {
Log.e(TAG, "API Success false: " + response.optString("message"));
}
} catch (JSONException e) {
Log.e(TAG, "Error parsing home response", e);
}
}
private void handleErrorResponse(VolleyError error) {
if (error.networkResponse != null && error.networkResponse.data != null) {
String errorBody = new String(error.networkResponse.data);
Log.e(TAG, "Server Error: " + errorBody);
} else {
Log.e(TAG, "Network Error", error);
}
}
private void updateUserData(JSONObject userData) throws JSONException {
String phoneNumber = userData.optString("no_hp", "-");
String entryTime = userData.optString("entry_time", "-");
String playDuration = userData.optString("play_duration", "-");
String lastAttraction = userData.optString("last_attraction", "-");
tvPhoneNumber.setText(phoneNumber);
tvEntryTime.setText(entryTime);
tvPlayDuration.setText(playDuration);
tvLastAttraction.setText(lastAttraction);
}
private void checkAndNotifyChildExit(String currentExitTime) {
Log.d(TAG, "Checking child exit. Current Exit Time: " + currentExitTime + ", Last Known Exit Time: " + lastKnownExitTime);
// Jika currentExitTime tidak null/kosong DAN ada perubahan status
if (currentExitTime != null && !currentExitTime.isEmpty() &&
(lastKnownExitTime == null || !lastKnownExitTime.equals(currentExitTime))) {
lastKnownExitTime = currentExitTime;
sharedPreferences.edit().putString("last_known_exit_time_" + idAnak, currentExitTime).apply();
Log.d(TAG, "Anak terdeteksi keluar! Notifikasi akan dikirim.");
// Hentikan polling sementara untuk mencegah pemicuan berulang
stopPollingHomeData();
sendChildExitNotification();
showChildExitDialog();
} else if (currentExitTime == null || currentExitTime.isEmpty()) {
// Jika anak masih bermain (currentExitTime null/kosong)
// Pastikan lastKnownExitTime juga null agar deteksi berjalan lagi jika anak keluar setelah masuk kembali
if (lastKnownExitTime != null) {
Log.d(TAG, "Anak masuk kembali atau status reset. Resetting lastKnownExitTime.");
lastKnownExitTime = null;
sharedPreferences.edit().remove("last_known_exit_time_" + idAnak).apply();
}
}
}
private void sendChildExitNotification() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && // Hanya berlaku untuk Android 13 (API 33) ke atas
ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
Log.w(TAG, "Notifikasi tidak dapat dikirim: Izin POST_NOTIFICATIONS tidak diberikan.");
return; // Hentikan jika izin tidak diberikan
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_warning_circle)
.setContentTitle("Anak Anda Keluar Playground!")
.setContentText("Anak Anda (" + nama + ") telah keluar dari area bermain.")
.setPriority(NotificationCompat.PRIORITY_MAX)
.setCategory(NotificationCompat.CATEGORY_ALARM)
.setAutoCancel(false);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
builder.setVibrate(new long[]{0, 1000, 500, 1000, 500, 1000, 500, 1000});
}
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.notify(NOTIFICATION_ID, builder.build());
startContinuousVibration();
}
private void showChildExitDialog() {
// Karena CustomAlertDialog Anda tidak mengekspos isShowing() secara publik,
// kita akan menggunakan referensi activeExitDialog itu sendiri.
// Jika activeExitDialog tidak null, asumsi dia sedang ditampilkan.
// Anda mungkin perlu menambahkan method `isShowing()` ke CustomAlertDialog Anda
// untuk penanganan yang lebih robust (seperti yang disarankan di respons sebelumnya).
if (activeExitDialog != null) {
Log.d(TAG, "Dialog keluar anak sudah ditampilkan, tidak perlu menampilkan lagi.");
return;
}
activeExitDialog = new CustomAlertDialog.Builder(this)
.setTitle("Pemberitahuan: Anak Terdeteksi Keluar")
.setMessage("Anak Anda, " + nama + ", telah terdeteksi keluar dari area bermain. Ini menandakan sesi bermain telah berakhir. Silakan jemput anak Anda.")
.setAlertType(CustomAlertDialog.AlertType.WARNING)
.setPositiveButton("OKE", new CustomAlertDialog.OnAlertClickListener() {
@Override
public void onPositiveClick() {
Log.d(TAG, "Tombol OK pada dialog keluar anak ditekan.");
stopContinuousVibration();
clearChildExitNotification();
activeExitDialog = null; // Reset referensi dialog
startPollingHomeData(); // Lanjutkan polling
}
@Override
public void onNegativeClick() {
// Tidak digunakan di sini
}
})
.setCancelable(false)
.create();
activeExitDialog.show();
}
private void startContinuousVibration() {
// --- Periksa izin VIBRATE sebelum memulai getaran ---
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // API 23 = Android 6.0
if (ContextCompat.checkSelfPermission(this, Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) {
Log.w(TAG, "Vibration not allowed: VIBRATE permission denied.");
return; // Hentikan jika izin tidak diberikan
}
}
// --- Akhir pemeriksaan izin VIBRATE ---
if (vibrator != null && vibrator.hasVibrator()) {
long[] pattern = {0, 1000, 500};
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibrator.vibrate(VibrationEffect.createWaveform(pattern, 0));
} else {
vibrator.vibrate(pattern, 0);
}
Log.d(TAG, "Continuous vibration started.");
} else {
Log.w(TAG, "Vibrator not available or not supported on this device.");
}
}
private void stopContinuousVibration() {
if (vibrator != null) {
vibrator.cancel();
Log.d(TAG, "Continuous vibration stopped.");
}
}
private void clearChildExitNotification() {
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.cancel(NOTIFICATION_ID);
Log.d(TAG, "Child exit notification cleared.");
}
private void populateTimeline(JSONArray timelineArray) {
timelineContainer.removeAllViews();
Log.d(TAG, "Populating timeline with " + timelineArray.length() + " items");
try {
for (int i = 0; i < timelineArray.length(); i++) {
JSONObject timelineItem = timelineArray.getJSONObject(i);
String type = timelineItem.optString("type", "placeholder");
String wahanaName = timelineItem.optString("wahana_name", "");
String waktuMasuk = timelineItem.optString("waktu_masuk", "");
String waktuKeluar = timelineItem.optString("waktu_keluar", "");
String durasi = timelineItem.optString("durasi", "");
Log.d(TAG, "Creating timeline item - Type: " + type + ", Wahana: " + wahanaName);
// Perbaiki inflate timeline_item agar tidak IllegalStateException
// Gunakan parent (timelineContainer) dan false untuk attachToRoot
View timelineView = createTimelineItem(type, wahanaName, waktuMasuk, waktuKeluar, durasi, i == timelineArray.length() - 1);
if (timelineView != null) {
timelineContainer.addView(timelineView);
}
}
animateTimelineIn();
} catch (JSONException e) {
Log.e(TAG, "Error parsing timeline data", e);
}
}
private View createTimelineItem(String type, String wahanaName, String waktuMasuk, String waktuKeluar, String durasi, boolean isLastItem) {
LayoutInflater inflater = LayoutInflater.from(this);
View timelineView = null;
switch (type) {
case "active":
timelineView = inflater.inflate(R.layout.timeline_item_active, timelineContainer, false);
TextView tvActiveTitle = timelineView.findViewById(R.id.tvTimelineTitle);
TextView tvActiveTime = timelineView.findViewById(R.id.tvTimelineTime);
if (tvActiveTitle != null) tvActiveTitle.setText(wahanaName);
if (tvActiveTime != null) {
String displayTime = waktuMasuk;
if (!durasi.isEmpty()) {
displayTime += "" + durasi;
}
tvActiveTime.setText(displayTime);
}
break;
case "inactive":
timelineView = inflater.inflate(R.layout.timeline_item_inactive, timelineContainer, false);
TextView tvInactiveTitle = timelineView.findViewById(R.id.tvTimelineTitle);
TextView tvInactiveTime = timelineView.findViewById(R.id.tvTimelineTime);
TextView tvInactiveExitTime = timelineView.findViewById(R.id.tvTimelineExitTime);
if (tvInactiveTitle != null) tvInactiveTitle.setText(wahanaName);
if (tvInactiveTime != null) tvInactiveTime.setText(waktuMasuk);
if (tvInactiveExitTime != null) tvInactiveExitTime.setText(waktuKeluar);
break;
case "entry":
timelineView = inflater.inflate(R.layout.timeline_item_entry, timelineContainer, false);
TextView tvEntryTimeView = timelineView.findViewById(R.id.tvTimelineTime); // Ubah nama variabel agar tidak konflik
if (tvEntryTimeView != null) tvEntryTimeView.setText(waktuMasuk);
break;
case "placeholder":
timelineView = inflater.inflate(R.layout.timeline_item_placeholder, timelineContainer, false);
break;
default:
Log.w(TAG, "Unknown timeline type: " + type);
return null;
}
if (isLastItem && timelineView != null) {
View timelineLine = timelineView.findViewById(R.id.timelineLine);
if (timelineLine != null) {
timelineLine.setVisibility(View.GONE);
}
}
return timelineView;
}
private void animateTimelineIn() {
for (int i = 0; i < timelineContainer.getChildCount(); i++) {
View child = timelineContainer.getChildAt(i);
child.setAlpha(0f);
child.setTranslationX(100f);
ObjectAnimator fadeIn = ObjectAnimator.ofFloat(child, "alpha", 0f, 1f);
ObjectAnimator slideIn = ObjectAnimator.ofFloat(child, "translationX", 100f, 0f);
fadeIn.setDuration(400);
slideIn.setDuration(400);
fadeIn.setStartDelay(i * 100);
slideIn.setStartDelay(i * 100);
fadeIn.setInterpolator(new AccelerateDecelerateInterpolator());
slideIn.setInterpolator(new AccelerateDecelerateInterpolator());
fadeIn.start();
slideIn.start();
}
}
private void setupLogout() {
cardLogout.setOnClickListener(v -> {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(cardLogout, "scaleX", 1f, 0.95f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(cardLogout, "scaleY", 1f, 0.95f, 1f);
scaleX.setDuration(150);
scaleY.setDuration(150);
scaleX.start();
scaleY.start();
performLogout();
});
}
private void performLogout() {
new CustomAlertDialog.Builder(this)
.setTitle("Konfirmasi Logout")
.setMessage("Apakah Anda yakin ingin keluar dari akun ini?")
.setAlertType(CustomAlertDialog.AlertType.INFO)
.setPositiveButton("YA", new CustomAlertDialog.OnAlertClickListener() {
@Override
public void onPositiveClick() {
showSuccessAnimation();
new Handler().postDelayed(() -> {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.clear();
editor.apply();
stopPollingHomeData();
Intent intent = new Intent(HomeActivity.this, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
}, 500);
}
@Override
public void onNegativeClick() {
// Not used
}
})
.setNegativeButton("TIDAK", null)
.setCancelable(false)
.show();
}
private void showSuccessAnimation() {
ObjectAnimator.ofFloat(cardLogout, "alpha", 1f, 0.5f, 1f)
.setDuration(300)
.start();
}
private void setupAnimations() {
ObjectAnimator fadeInTitle = ObjectAnimator.ofFloat(llTitle, "alpha", 0f, 1f);
fadeInTitle.setDuration(800);
fadeInTitle.setInterpolator(new AccelerateDecelerateInterpolator());
fadeInTitle.start();
ObjectAnimator fadeInStatistik = ObjectAnimator.ofFloat(llStatistik, "alpha", 0f, 1f);
fadeInStatistik.setDuration(800);
fadeInStatistik.setInterpolator(new AccelerateDecelerateInterpolator());
fadeInStatistik.start();
ObjectAnimator fadeInLogout = ObjectAnimator.ofFloat(llLogout, "alpha", 0f, 1f);
fadeInLogout.setDuration(800);
fadeInLogout.setInterpolator(new AccelerateDecelerateInterpolator());
fadeInLogout.start();
}
@Override
public void onBackPressed() {
// Karena CustomAlertDialog Anda sudah punya method show() dan dismiss() publik,
// kita bisa menggunakannya secara langsung. Namun, tidak ada method isShowing() publik.
// Jadi, kita berasumsi jika activeExitDialog tidak null, berarti dia sedang ditampilkan.
// Pilihan yang lebih baik adalah menambahkan method isShowing() publik di CustomAlertDialog.
if (activeExitDialog != null) {
return; // Jangan lakukan apa-apa jika dialog notifikasi anak keluar sedang tampil
}
new CustomAlertDialog.Builder(this)
.setTitle("Konfirmasi Logout")
.setMessage("Apakah Anda yakin ingin keluar dari akun ini?")
.setAlertType(CustomAlertDialog.AlertType.INFO)
.setPositiveButton("KELUAR", new CustomAlertDialog.OnAlertClickListener() {
@Override
public void onPositiveClick() {
showSuccessAnimation();
new Handler().postDelayed(() -> {
stopPollingHomeData();
Intent intent = new Intent(HomeActivity.this, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
}, 500);
}
@Override
public void onNegativeClick() {
// Not used
}
})
.setNegativeButton("BATAL", null)
.show();
}
private void showError(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
@Override
protected void onResume() {
super.onResume();
// Inisialisasi HOME_URL dari IPManager di onResume
HOME_URL = ipManager.getURL("home.php");
Log.d(TAG, "Using API URL: " + HOME_URL);
if (idAnak != 0) {
startPollingHomeData();
}
}
@Override
protected void onPause() {
super.onPause();
stopPollingHomeData();
}
@Override
protected void onDestroy() {
super.onDestroy();
stopPollingHomeData();
if (vibrator != null) {
vibrator.cancel();
}
if (activeExitDialog != null) {
activeExitDialog.dismiss();
activeExitDialog = null;
}
// Batalkan semua request Volley untuk Activity ini saat dihancurkan
if (requestQueue != null) {
requestQueue.cancelAll(TAG);
}
}
}

View File

@ -0,0 +1,184 @@
package com.tugasakhir.monikids.view;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
import com.tugasakhir.monikids.R;
import com.tugasakhir.monikids.utils.IPManager;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.TimeUnit;
public class IPSettingsActivity extends AppCompatActivity {
private IPManager ipManager;
private TextView tvCurrentIPDisplay;
private TextInputEditText etNewIP;
private TextInputLayout inputLayoutIP;
private Button btnSaveIP, btnTestConnection;
private Button btnLocalhost, btnEmulator, btnDefault;
private TextView tvConnectionStatus;
private ImageButton btnBack;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ip_settings);
initViews();
initIPManager();
setupClickListeners();
updateCurrentIPDisplay();
}
private void initViews() {
btnBack = findViewById(R.id.btnBack);
tvCurrentIPDisplay = findViewById(R.id.tvCurrentIPDisplay);
etNewIP = findViewById(R.id.etNewIP);
inputLayoutIP = findViewById(R.id.inputLayoutIP);
btnSaveIP = findViewById(R.id.btnSaveIP);
btnTestConnection = findViewById(R.id.btnTestConnection);
btnLocalhost = findViewById(R.id.btnLocalhost);
btnEmulator = findViewById(R.id.btnEmulator);
btnDefault = findViewById(R.id.btnDefault);
tvConnectionStatus = findViewById(R.id.tvConnectionStatus);
}
private void initIPManager() {
ipManager = IPManager.getInstance(this);
}
private void setupClickListeners() {
btnBack.setOnClickListener(v -> finish());
btnSaveIP.setOnClickListener(v -> saveNewIP());
btnTestConnection.setOnClickListener(v -> testConnection());
btnLocalhost.setOnClickListener(v -> {
etNewIP.setText(IPManager.QuickIPs.LOCALHOST);
clearError();
});
btnEmulator.setOnClickListener(v -> {
etNewIP.setText(IPManager.QuickIPs.EMULATOR);
clearError();
});
btnDefault.setOnClickListener(v -> {
etNewIP.setText(IPManager.QuickIPs.DEFAULT);
clearError();
});
}
private void updateCurrentIPDisplay() {
String currentIP = ipManager.getCurrentIP();
tvCurrentIPDisplay.setText(currentIP);
etNewIP.setText(currentIP);
}
private void saveNewIP() {
String newIP = etNewIP.getText().toString().trim();
if (newIP.isEmpty()) {
showError("IP tidak boleh kosong");
return;
}
boolean success = ipManager.setServerIP(newIP);
if (success) {
updateCurrentIPDisplay();
clearError();
Toast.makeText(this, "IP berhasil disimpan: " + newIP, Toast.LENGTH_SHORT).show();
// Broadcast change to other activities if needed
broadcastIPChange();
} else {
showError("Format IP tidak valid");
}
}
private void testConnection() {
String testIP = etNewIP.getText().toString().trim();
if (testIP.isEmpty()) {
showError("Masukkan IP terlebih dahulu");
return;
}
btnTestConnection.setEnabled(false);
btnTestConnection.setText("TESTING...");
tvConnectionStatus.setVisibility(View.VISIBLE);
tvConnectionStatus.setText("Menguji koneksi...");
tvConnectionStatus.setTextColor(Color.GRAY);
new TestConnectionTask().execute(testIP);
}
private void showError(String message) {
inputLayoutIP.setError(message);
}
private void clearError() {
inputLayoutIP.setError(null);
}
private void broadcastIPChange() {
// Optional: Send broadcast to notify other activities about IP change
// Intent intent = new Intent("IP_CHANGED");
// sendBroadcast(intent);
}
private class TestConnectionTask extends AsyncTask<String, Void, Boolean> {
private String testIP;
private String errorMessage = "";
@Override
protected Boolean doInBackground(String... params) {
testIP = params[0];
try {
String testUrl = "http://" + testIP + "/playground_api/";
URL url = new URL(testUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(5000); // 5 seconds timeout
connection.setReadTimeout(5000);
int responseCode = connection.getResponseCode();
connection.disconnect();
return responseCode == HttpURLConnection.HTTP_OK ||
responseCode == HttpURLConnection.HTTP_NOT_FOUND ||
responseCode == HttpURLConnection.HTTP_FORBIDDEN;
} catch (IOException e) {
errorMessage = e.getMessage();
return false;
}
}
@Override
protected void onPostExecute(Boolean success) {
btnTestConnection.setEnabled(true);
btnTestConnection.setText("UJI KONEKSI");
if (success) {
tvConnectionStatus.setText("✓ Koneksi berhasil ke " + testIP);
tvConnectionStatus.setTextColor(Color.GREEN);
} else {
tvConnectionStatus.setText("✗ Koneksi gagal: " + errorMessage);
tvConnectionStatus.setTextColor(Color.RED);
}
}
}
}

View File

@ -0,0 +1,627 @@
package com.tugasakhir.monikids.view;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.text.InputType;
import android.util.Log;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
import com.tugasakhir.monikids.R;
import com.tugasakhir.monikids.utils.CustomAlertDialog;
import com.tugasakhir.monikids.utils.IPManager; // Ensure this import is present
import org.json.JSONException;
import org.json.JSONObject;
public class LoginActivity extends AppCompatActivity {
private Button btnLoginByName, btnLoginByCode, btnLoginByPhone, btnLogin;
private TextInputEditText etLoginInput;
private LinearLayout llLogin;
private TextInputLayout inputLayout;
private RequestQueue requestQueue;
private SharedPreferences sharedPreferences;
private IPManager ipManager; // IPManager instance
private TextView tvCurrentIP; // TextView to display current IP
private ImageButton btnIPSettings; // Button for IP settings
// Remove hardcoded BASE_URL, it will be retrieved from IPManager
// private static final String BASE_URL = "http://192.168.18.120/playground_api/";
private String LOGIN_URL; // This will be set dynamically
private static final String TAG = "LoginActivity";
private static final String PREFS_NAME = "MoniKidsPrefs";
private enum LoginType {
NAME, CODE, PHONE
}
private LoginType currentLoginType = LoginType.NAME;
private boolean isLoading = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initViews();
initNetworkAndIPManager(); // Renamed to include IPManager init
setupClickListeners();
updateInputForLoginType();
setupAnimations();
updateCurrentIPDisplay(); // Display current IP on create
}
@Override
protected void onResume() {
super.onResume();
updateCurrentIPDisplay(); // Update IP display when returning to this activity
// Re-initialize LOGIN_URL to ensure it reflects potential IP changes
LOGIN_URL = ipManager.getURL("login.php");
Log.d(TAG, "Final LOGIN_URL: " + LOGIN_URL);
Log.d(TAG, "Current IP from IPManager: " + ipManager.getCurrentIP());
Log.d(TAG, "Base URL: " + ipManager.getBaseURL());
Log.d(TAG, "Using API URL: " + LOGIN_URL);
}
private void initViews() {
btnLoginByName = findViewById(R.id.btnLoginByName);
btnLoginByCode = findViewById(R.id.btnLoginByCode);
btnLoginByPhone = findViewById(R.id.btnLoginByPhone);
btnLogin = findViewById(R.id.btnLogin);
etLoginInput = findViewById(R.id.etLoginInput);
inputLayout = findViewById(R.id.inputLayout);
llLogin = findViewById(R.id.llLogin);
// Initialize IP-related views
tvCurrentIP = findViewById(R.id.tvCurrentIP);
btnIPSettings = findViewById(R.id.btnIPSettings);
}
private void initNetworkAndIPManager() { // Renamed method
requestQueue = Volley.newRequestQueue(this);
sharedPreferences = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
ipManager = IPManager.getInstance(this); // Initialize IPManager
// Set the LOGIN_URL dynamically using IPManager
LOGIN_URL = ipManager.getURL("login.php");
Log.d(TAG, "Initialized with API URL: " + LOGIN_URL);
}
private void setupClickListeners() {
btnLoginByName.setOnClickListener(v -> {
if (currentLoginType != LoginType.NAME) {
animateButtonSelection(v);
selectLoginType(LoginType.NAME);
}
});
btnLoginByCode.setOnClickListener(v -> {
if (currentLoginType != LoginType.CODE) {
animateButtonSelection(v);
selectLoginType(LoginType.CODE);
}
});
btnLoginByPhone.setOnClickListener(v -> {
if (currentLoginType != LoginType.PHONE) {
animateButtonSelection(v);
selectLoginType(LoginType.PHONE);
}
});
btnLogin.setOnClickListener(v -> {
if (!isLoading) {
animateButtonPress(v);
performLogin();
}
});
// Click listener for IP Settings button
btnIPSettings.setOnClickListener(v -> {
Intent intent = new Intent(LoginActivity.this, IPSettingsActivity.class);
startActivity(intent);
});
}
private void setupAnimations() {
// Add entrance animations
ObjectAnimator fadeIn = ObjectAnimator.ofFloat(llLogin, "alpha", 0f, 1f);
fadeIn.setDuration(800);
fadeIn.setInterpolator(new AccelerateDecelerateInterpolator());
fadeIn.start();
}
private void animateButtonSelection(View button) {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(button, "scaleX", 1f, 1.1f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(button, "scaleY", 1f, 1.1f, 1f);
scaleX.setDuration(200);
scaleY.setDuration(200);
scaleX.start();
scaleY.start();
}
private void animateButtonPress(View button) {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(button, "scaleX", 1f, 0.95f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(button, "scaleY", 1f, 0.95f, 1f);
scaleX.setDuration(150);
scaleY.setDuration(150);
scaleX.start();
scaleY.start();
}
private void selectLoginType(LoginType loginType) {
currentLoginType = loginType;
// Reset all buttons to unselected state
resetButtonStyles();
// Set selected button style with animation
switch (loginType) {
case NAME:
btnLoginByName.setBackgroundResource(R.drawable.login_option_selected);
btnLoginByName.setTextColor(ContextCompat.getColor(this, android.R.color.white));
break;
case CODE:
btnLoginByCode.setBackgroundResource(R.drawable.login_option_selected);
btnLoginByCode.setTextColor(ContextCompat.getColor(this, android.R.color.white));
break;
case PHONE:
btnLoginByPhone.setBackgroundResource(R.drawable.login_option_selected);
btnLoginByPhone.setTextColor(ContextCompat.getColor(this, android.R.color.white));
break;
}
updateInputForLoginType();
}
private void resetButtonStyles() {
int unselectedColor = ContextCompat.getColor(this, R.color.purple_primary);
btnLoginByName.setBackgroundResource(R.drawable.login_option_unselected);
btnLoginByName.setTextColor(unselectedColor);
btnLoginByCode.setBackgroundResource(R.drawable.login_option_unselected);
btnLoginByCode.setTextColor(unselectedColor);
btnLoginByPhone.setBackgroundResource(R.drawable.login_option_unselected);
btnLoginByPhone.setTextColor(unselectedColor);
}
private void updateInputForLoginType() {
etLoginInput.setText(""); // Clear previous input
inputLayout.setError(null); // Clear any previous errors
// Animate input field change
ObjectAnimator fadeOut = ObjectAnimator.ofFloat(inputLayout, "alpha", 1f, 0.7f, 1f);
fadeOut.setDuration(300);
fadeOut.start();
switch (currentLoginType) {
case NAME:
inputLayout.setHint("Masukkan nama anak");
inputLayout.setStartIconDrawable(R.drawable.ic_child);
etLoginInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PERSON_NAME);
break;
case CODE:
inputLayout.setHint("Masukkan kode gelang");
inputLayout.setStartIconDrawable(R.drawable.ic_code);
etLoginInput.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL);
break;
case PHONE:
inputLayout.setHint("Masukkan nomor HP");
inputLayout.setStartIconDrawable(R.drawable.ic_phone);
etLoginInput.setInputType(InputType.TYPE_CLASS_PHONE);
break;
}
}
private void updateCurrentIPDisplay() {
// Update the TextView with the current IP from IPManager
if (tvCurrentIP != null && ipManager != null) {
tvCurrentIP.setText(ipManager.getCurrentIP());
}
}
private void performLogin() {
String input = etLoginInput.getText().toString().trim();
if (input.isEmpty()) {
String errorMessage = getEmptyErrorMessage();
new CustomAlertDialog.Builder(this)
.setTitle("Input Kosong")
.setMessage(errorMessage)
.setAlertType(CustomAlertDialog.AlertType.WARNING)
.setPositiveButton("OK", new CustomAlertDialog.OnAlertClickListener() {
@Override
public void onPositiveClick() {
etLoginInput.requestFocus();
}
@Override
public void onNegativeClick() {
// Not used
}
})
.show();
inputLayout.setError(errorMessage);
animateErrorShake();
return;
}
inputLayout.setError(null);
if (!validateInput(input)) {
animateErrorShake();
return;
}
setLoadingState(true);
performAPILogin(input);
}
private void performAPILogin(String input) {
try {
JSONObject jsonBody = new JSONObject();
jsonBody.put("login_type", currentLoginType.name());
jsonBody.put("input_value", input);
JsonObjectRequest request = new JsonObjectRequest(Request.Method.POST, LOGIN_URL, jsonBody, new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
handleLoginResponse(response, input);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
handleLoginError(error);
}
});
requestQueue.add(request);
} catch (JSONException e) {
Log.e(TAG, "Error creating JSON request", e);
setLoadingState(false);
showErrorAlert("Error", "Terjadi kesalahan dalam membuat request: " + e.getMessage());
}
}
private void handleLoginResponse(JSONObject response, String input) {
setLoadingState(false);
try {
boolean success = response.getBoolean("success");
String message = response.getString("message");
if (success) {
new CustomAlertDialog.Builder(this)
.setTitle("Login Berhasil!")
.setMessage("Selamat datang! Anda akan diarahkan ke halaman utama.")
.setAlertType(CustomAlertDialog.AlertType.SUCCESS)
.setPositiveButton("LANJUTKAN", new CustomAlertDialog.OnAlertClickListener() {
@Override
public void onPositiveClick() {
try {
JSONObject data = response.getJSONObject("data");
saveLoginData(data);
showSuccessAnimation();
new Handler().postDelayed(() -> {
navigateToHomeActivity(data);
}, 500);
} catch (JSONException e) {
Log.e(TAG, "Error parsing data", e);
showErrorAlert("Error", "Terjadi kesalahan dalam memproses data login.");
}
}
@Override
public void onNegativeClick() {
// Not used
}
})
.setCancelable(false)
.show();
} else {
new CustomAlertDialog.Builder(this)
.setTitle("Login Gagal")
.setMessage(message)
.setAlertType(CustomAlertDialog.AlertType.ERROR)
.setPositiveButton("COBA LAGI", new CustomAlertDialog.OnAlertClickListener() {
@Override
public void onPositiveClick() {
etLoginInput.setText("");
etLoginInput.requestFocus();
}
@Override
public void onNegativeClick() {
// Not used
}
})
.show();
inputLayout.setError(message);
animateErrorShake();
}
} catch (JSONException e) {
Log.e(TAG, "Error parsing login response", e);
showErrorAlert("Error", "Terjadi kesalahan dalam memproses data. Silakan coba lagi.");
}
}
private void handleLoginError(VolleyError error) {
setLoadingState(false);
String errorMessage = "Koneksi gagal. Pastikan server berjalan dan IP server diatur dengan benar."; // Updated message
if (error.networkResponse != null) {
try {
String responseBody = new String(error.networkResponse.data, "utf-8");
JSONObject data = new JSONObject(responseBody);
errorMessage = data.getString("message");
} catch (Exception e) {
Log.e(TAG, "Error parsing error response", e);
}
} else if (error.getCause() instanceof java.net.ConnectException) {
errorMessage = "Tidak dapat terhubung ke server. Pastikan IP server sudah benar dan server sedang berjalan.";
} else if (error.getCause() instanceof java.net.UnknownHostException) {
errorMessage = "IP server tidak dikenal. Pastikan IP yang dimasukkan valid.";
}
Log.e(TAG, "Login API Error: " + error.toString());
Log.e(TAG, "Error details - NetworkResponse: " + error.networkResponse);
Log.e(TAG, "Error details - Cause: " + error.getCause());
Log.e(TAG, "Full URL being called: " + LOGIN_URL);
new CustomAlertDialog.Builder(this)
.setTitle("Koneksi Bermasalah")
.setMessage(errorMessage)
.setAlertType(CustomAlertDialog.AlertType.ERROR)
.setPositiveButton("COBA LAGI", new CustomAlertDialog.OnAlertClickListener() {
@Override
public void onPositiveClick() {
String input = etLoginInput.getText().toString().trim();
if (!input.isEmpty()) {
performLogin();
}
}
@Override
public void onNegativeClick() {
// Not used
}
})
.setNegativeButton("BATAL", null) // Just close dialog
.show();
inputLayout.setError(errorMessage);
animateErrorShake();
}
private void saveLoginData(JSONObject data) {
try {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putInt("id_anak", data.getInt("id_anak"));
editor.putString("nama", data.getString("nama"));
editor.putString("kode_gelang", data.getString("kode_gelang"));
editor.putString("login_type", data.getString("login_type"));
editor.putLong("login_time", System.currentTimeMillis());
editor.apply();
} catch (JSONException e) {
Log.e(TAG, "Error saving login data", e);
}
}
private void showSuccessAnimation() {
ObjectAnimator successScale = ObjectAnimator.ofFloat(btnLogin, "scaleX", 1f, 1.1f, 1f);
ObjectAnimator successScaleY = ObjectAnimator.ofFloat(btnLogin, "scaleY", 1f, 1.1f, 1f);
successScale.setDuration(300);
successScaleY.setDuration(300);
successScale.start();
successScaleY.start();
}
private void navigateToHomeActivity(JSONObject data) {
try {
Intent intent = new Intent(LoginActivity.this, HomeActivity.class);
intent.putExtra("id_anak", data.getInt("id_anak"));
intent.putExtra("nama", data.getString("nama"));
intent.putExtra("kode_gelang", data.getString("kode_gelang"));
intent.putExtra("login_type", data.getString("login_type"));
startActivity(intent);
finish();
} catch (JSONException e) {
Log.e(TAG, "Error navigating to home activity", e);
showErrorAlert("Error", "Terjadi kesalahan saat navigasi ke halaman utama.");
}
}
private String getEmptyErrorMessage() {
switch (currentLoginType) {
case NAME:
return "Nama anak tidak boleh kosong";
case CODE:
return "Kode gelang tidak boleh kosong";
case PHONE:
return "Nomor HP tidak boleh kosong";
default:
return "Input tidak boleh kosong";
}
}
private void animateErrorShake() {
ObjectAnimator shake = ObjectAnimator.ofFloat(inputLayout, "translationX", 0, 25, -25, 25, -25, 15, -15, 6, -6, 0);
shake.setDuration(600);
shake.start();
}
private boolean validateInput(String input) {
switch (currentLoginType) {
case NAME:
if (input.length() < 2) {
showValidationAlert("Nama minimal 2 karakter");
return false;
}
if (!input.matches("^[a-zA-Z\\s]+$")) {
showValidationAlert("Nama hanya boleh berisi huruf dan spasi");
return false;
}
break;
case CODE:
if (input.length() < 4) {
showValidationAlert("Kode gelang minimal 4 karakter");
return false;
}
if (!input.matches("^[A-Za-z0-9]+$")) {
showValidationAlert("Kode hanya boleh berisi huruf dan angka");
return false;
}
break;
case PHONE:
// Regex for common phone number formats, allows +, -, (, ), space and digits, min 10 digits
if (!input.matches("^[0-9+\\-\\s()]+$") || input.length() < 10) {
showValidationAlert("Format nomor HP tidak valid (minimal 10 digit)");
return false;
}
break;
}
return true;
}
private void showValidationAlert(String message) {
new CustomAlertDialog.Builder(this)
.setTitle("Input Tidak Valid")
.setMessage(message)
.setAlertType(CustomAlertDialog.AlertType.WARNING)
.setPositiveButton("OK", new CustomAlertDialog.OnAlertClickListener() {
@Override
public void onPositiveClick() {
etLoginInput.requestFocus();
}
@Override
public void onNegativeClick() {
// Not used
}
})
.show();
inputLayout.setError(message);
}
private void setLoadingState(boolean loading) {
isLoading = loading;
btnLogin.setEnabled(!loading);
if (loading) {
btnLogin.setText("MEMUAT...");
ObjectAnimator pulseAnimator = ObjectAnimator.ofFloat(btnLogin, "alpha", 1f, 0.7f, 1f);
pulseAnimator.setDuration(800);
pulseAnimator.setRepeatCount(ValueAnimator.INFINITE);
pulseAnimator.start();
} else {
btnLogin.setText("MASUK");
btnLogin.clearAnimation();
btnLogin.setAlpha(1f);
}
}
private void showSuccessAlert(String title, String message) {
new CustomAlertDialog.Builder(this)
.setTitle(title)
.setMessage(message)
.setAlertType(CustomAlertDialog.AlertType.SUCCESS)
.setPositiveButton("OK", null)
.show();
}
private void showErrorAlert(String title, String message) {
new CustomAlertDialog.Builder(this)
.setTitle(title)
.setMessage(message)
.setAlertType(CustomAlertDialog.AlertType.ERROR)
.setPositiveButton("OK", null)
.show();
}
private void showWarningAlert(String title, String message) {
new CustomAlertDialog.Builder(this)
.setTitle(title)
.setMessage(message)
.setAlertType(CustomAlertDialog.AlertType.WARNING)
.setPositiveButton("OK", null)
.show();
}
private void showInfoAlert(String title, String message) {
new CustomAlertDialog.Builder(this)
.setTitle(title)
.setMessage(message)
.setAlertType(CustomAlertDialog.AlertType.INFO)
.setPositiveButton("OK", null)
.show();
}
@Override
public void onBackPressed() {
new CustomAlertDialog.Builder(this)
.setTitle("Keluar Aplikasi")
.setMessage("Apakah Anda yakin ingin keluar dari MoniKids?")
.setAlertType(CustomAlertDialog.AlertType.INFO)
.setPositiveButton("KELUAR", new CustomAlertDialog.OnAlertClickListener() {
@Override
public void onPositiveClick() {
LoginActivity.super.onBackPressed();
}
@Override
public void onNegativeClick() {
// Not used
}
})
.setNegativeButton("BATAL", null)
.show();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (btnLogin != null) {
btnLogin.clearAnimation();
}
if (requestQueue != null) {
requestQueue.cancelAll(TAG);
}
}
}

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_decelerate_interpolator">
<scale
android:fromXScale="1.0"
android:toXScale="0.95"
android:fromYScale="1.0"
android:toYScale="0.95"
android:pivotX="50%"
android:pivotY="50%"
android:duration="100" />
<scale
android:fromXScale="0.95"
android:toXScale="1.0"
android:fromYScale="0.95"
android:toYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
android:duration="100"
android:startOffset="100" />
</set>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/bounce_interpolator">
<scale
android:fromXScale="1.0"
android:toXScale="1.1"
android:fromYScale="1.0"
android:toYScale="1.1"
android:pivotX="50%"
android:pivotY="50%"
android:duration="150" />
<scale
android:fromXScale="1.1"
android:toXScale="1.0"
android:fromYScale="1.1"
android:toYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
android:duration="150"
android:startOffset="150" />
</set>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_decelerate_interpolator">
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="800" />
<scale
android:fromXScale="0.9"
android:toXScale="1.0"
android:fromYScale="0.9"
android:toYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
android:duration="800" />
</set>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:repeatCount="infinite"
android:repeatMode="reverse">
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.6"
android:duration="800" />
<scale
android:fromXScale="1.0"
android:toXScale="1.05"
android:fromYScale="1.0"
android:toYScale="1.05"
android:pivotX="50%"
android:pivotY="50%"
android:duration="800" />
</set>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0"
android:toXDelta="10"
android:duration="1000"
android:interpolator="@anim/shake_interpolator" />

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<cycleInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:cycles="7" />

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="100%"
android:toXDelta="0%"
android:duration="300"
android:interpolator="@android:anim/decelerate_interpolator" />
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="300" />
</set>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="0%"
android:toXDelta="-100%"
android:duration="300"
android:interpolator="@android:anim/accelerate_interpolator" />
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:duration="300" />
</set>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_decelerate_interpolator">
<translate
android:fromYDelta="100%"
android:toYDelta="0%"
android:duration="600" />
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="600" />
</set>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/bounce_interpolator">
<scale
android:fromXScale="1.0"
android:toXScale="1.2"
android:fromYScale="1.0"
android:toYScale="1.2"
android:pivotX="50%"
android:pivotY="50%"
android:duration="300" />
<scale
android:fromXScale="1.2"
android:toXScale="1.0"
android:fromYScale="1.2"
android:toYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
android:duration="400"
android:startOffset="300" />
</set>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<solid android:color="#F0F0F0" />
<corners android:radius="12dp" />
<stroke
android:width="2dp"
android:color="#6A5ACD" />
</shape>
</item>
<item>
<shape>
<solid android:color="@android:color/white" />
<corners android:radius="12dp" />
<stroke
android:width="1dp"
android:color="#6A5ACD" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<gradient
android:angle="135"
android:endColor="#5A4FCF"
android:startColor="#7B68EE" />
<corners android:radius="12dp" />
</shape>
</item>
<item>
<shape>
<gradient
android:angle="135"
android:endColor="#6A5ACD"
android:startColor="#8A7FDC" />
<corners android:radius="12dp" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/white" />
<corners android:radius="20dp" />
<stroke
android:width="1dp"
android:color="#E8E8E8" />
</shape>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#F0F0F0" />
<corners android:radius="50dp" />
</shape>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<solid android:color="#F3F4F6" />
<corners android:radius="20dp" />
</shape>
</item>
<item>
<shape>
<solid android:color="@android:color/white" />
<corners android:radius="20dp" />
<stroke android:width="1dp" android:color="#E5E7EB" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#667EEA"
android:centerColor="#F093FB"
android:endColor="#F5576C"
android:angle="0" />
<corners
android:bottomLeftRadius="20dp"
android:bottomRightRadius="20dp" />
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#667EEA"
android:endColor="#764BA2"
android:angle="45" />
<corners android:radius="28dp" />
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/transparent" />
<corners android:radius="16dp" />
<stroke
android:width="1dp"
android:color="@color/purple_light" />
</shape>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#EEF2FF" />
<corners android:radius="12dp" />
<stroke android:width="1dp" android:color="#C7D2FE" />
</shape>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<gradient
android:startColor="@color/purple_primary"
android:endColor="@color/primary_purple"
android:type="radial"
android:gradientRadius="8dp" />
<size
android:width="12dp"
android:height="12dp" />
</shape>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<gradient
android:startColor="@color/success_color"
android:endColor="#00FFFFFF"
android:type="radial"
android:gradientRadius="9dp" />
<size
android:width="18dp"
android:height="18dp" />
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@android:color/white" />
<size
android:width="8dp"
android:height="8dp" />
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/text_secondary" />
<size
android:width="10dp"
android:height="10dp" />
</shape>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke
android:width="1dp"
android:color="@color/text_secondary" />
<size
android:width="16dp"
android:height="16dp" />
</shape>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke
android:width="1dp"
android:color="@color/text_secondary"
android:dashWidth="3dp"
android:dashGap="2dp" />
<size
android:width="8dp"
android:height="8dp" />
</shape>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke
android:width="1dp"
android:color="@color/text_secondary"
android:dashWidth="4dp"
android:dashGap="2dp" />
<size
android:width="16dp"
android:height="16dp" />
</shape>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<stroke
android:width="2dp"
android:color="@color/purple_primary" />
<size
android:width="16dp"
android:height="16dp" />
</shape>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- res/drawable/gradient_background.xml -->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:angle="135"
android:startColor="#8B5CF6"
android:centerColor="#A78BFA"
android:endColor="#C4B5FD"
android:type="linear" />
</shape>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="@color/background_white"
android:centerColor="@color/purple_soft"
android:endColor="@color/background_white"
android:angle="135" />
<corners android:radius="24dp" />
<stroke
android:width="1dp"
android:color="@color/border_light" />
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/info_light" />
<corners android:radius="12dp" />
<stroke
android:width="1dp"
android:color="@color/info_color" />
</shape>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="512dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M10.957,12.354a0.5,0.5 0,0 1,0 -0.708l4.586,-4.585a1.5,1.5 0,0 0,-2.121 -2.122L8.836,9.525a3.505,3.505 0,0 0,0 4.95l4.586,4.586a1.5,1.5 0,0 0,2.121 -2.122Z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="512dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M15.75,9.525 L11.164,4.939A1.5,1.5 0,0 0,9.043 7.061l4.586,4.585a0.5,0.5 0,0 1,0 0.708L9.043,16.939a1.5,1.5 0,0 0,2.121 2.122l4.586,-4.586A3.505,3.505 0,0 0,15.75 9.525Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M6,2v6h0.01L6,8.01 10,12l-4,4 0.01,0.01H6V22h12v-5.99h-0.01L18,16.01 14,12l4,-4 -0.01,-0.01H18V2H6zM16,16.5V20H8v-3.5l4,-4 4,4zM12,11.5 8,7.5V4h8v3.5l-4,4z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="512dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M12,24c-1.65,0 -3,-1.35 -3,-3L9,3c0,-1.65 1.35,-3 3,-3s3,1.35 3,3L15,21c0,1.65 -1.35,3 -3,3ZM21,24c-1.65,0 -3,-1.35 -3,-3L18,9c0,-1.65 1.35,-3 3,-3s3,1.35 3,3v12c0,1.65 -1.35,3 -3,3ZM3,24c-1.65,0 -3,-1.35 -3,-3v-6c0,-1.65 1.35,-3 3,-3s3,1.35 3,3v6c0,1.65 -1.35,3 -3,3Z"/>
</vector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#4CAF50"
android:pathData="m12,0C5.383,0 0,5.383 0,12s5.383,12 12,12 12,-5.383 12,-12S18.617,0 12,0ZM18.2,10.512l-4.426,4.345c-0.783,0.768 -1.791,1.151 -2.8,1.151 -0.998,0 -1.996,-0.376 -2.776,-1.129l-1.899,-1.867c-0.394,-0.387 -0.399,-1.02 -0.012,-1.414 0.386,-0.395 1.021,-0.4 1.414,-0.012l1.893,1.861c0.776,0.75 2.001,0.746 2.781,-0.018l4.425,-4.344c0.393,-0.388 1.024,-0.381 1.414,0.013 0.387,0.394 0.381,1.027 -0.014,1.414Z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="m13.916,15c0.506,0 0.795,0.612 0.447,0.98 -0.592,0.628 -1.431,1.02 -2.362,1.02s-1.77,-0.392 -2.362,-1.02c-0.347,-0.368 -0.059,-0.98 0.447,-0.98h3.831zM20.732,14.992c-0.477,1.04 -1.196,2.064 -2.082,2.966 1.245,1.371 2.028,3.059 2.261,4.918 0.036,0.284 -0.053,0.57 -0.242,0.786 -0.19,0.215 -0.463,0.338 -0.75,0.338h-15.859c-0.287,0 -0.561,-0.123 -0.75,-0.339 -0.189,-0.215 -0.278,-0.501 -0.242,-0.786 0.234,-1.864 1.021,-3.556 2.271,-4.928 -0.881,-0.9 -1.596,-1.919 -2.071,-2.955 -1.821,-0.12 -3.268,-1.641 -3.268,-3.492 0,-1.434 0.869,-2.692 2.149,-3.226 0.825,-4.761 4.947,-8.274 9.851,-8.274s9.026,3.513 9.851,8.274c1.28,0.533 2.149,1.792 2.149,3.226 0,1.852 -1.446,3.372 -3.268,3.492zM20.5,13c0.827,0 1.5,-0.673 1.5,-1.5 0,-0.723 -0.518,-1.342 -1.23,-1.472 -0.437,-0.079 -0.77,-0.437 -0.815,-0.878 -0.308,-2.913 -2.146,-5.307 -4.665,-6.44 0.497,1.268 0.236,2.765 -0.789,3.79 -1.381,1.381 -3.619,1.381 -5,0 -1.025,-1.025 -1.285,-2.521 -0.789,-3.79 -2.52,1.133 -4.358,3.527 -4.665,6.44 -0.046,0.441 -0.379,0.799 -0.815,0.878 -0.713,0.13 -1.23,0.749 -1.23,1.472 0,0.827 0.673,1.5 1.5,1.5 0.086,0 0.17,-0.014 0.252,-0.027 0.482,-0.09 0.954,0.19 1.117,0.651 0.916,2.598 4.001,5.378 7.131,5.378s6.215,-2.78 7.131,-5.378c0.163,-0.461 0.632,-0.741 1.117,-0.651 0.082,0.014 0.166,0.027 0.252,0.027zM9.5,10c-0.828,0 -1.5,0.672 -1.5,1.5s0.672,1.5 1.5,1.5 1.5,-0.672 1.5,-1.5 -0.672,-1.5 -1.5,-1.5zM14.5,10c-0.828,0 -1.5,0.672 -1.5,1.5s0.672,1.5 1.5,1.5 1.5,-0.672 1.5,-1.5 -0.672,-1.5 -1.5,-1.5z"/>
</vector>

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="120dp"
android:height="120dp"
android:viewportWidth="120"
android:viewportHeight="120">
<path
android:fillColor="#FFFFFF"
android:pathData="M60,10C32.4,10 10,32.4 10,60s22.4,50 50,50s50,-22.4 50,-50S87.6,10 60,10z" />
<path
android:fillColor="#FFD6A5"
android:pathData="M60,25C68.3,25 75,31.7 75,40s-6.7,15 -15,15s-15,-6.7 -15,-15S51.7,25 60,25z" />
<path
android:fillColor="#2D3748"
android:pathData="M55,36C56.1,36 57,36.9 57,38C57,39.1 56.1,40 55,40C53.9,40 53,39.1 53,38C53,36.9 53.9,36 55,36z" />
<path
android:fillColor="#2D3748"
android:pathData="M65,36C66.1,36 67,36.9 67,38C67,39.1 66.1,40 65,40C63.9,40 63,39.1 63,38C63,36.9 63.9,36 65,36z" />
<path
android:fillColor="@android:color/transparent"
android:strokeColor="#2D3748"
android:strokeWidth="2"
android:strokeLineCap="round"
android:pathData="M55,45C57,47 63,47 65,45" />
<path
android:fillColor="#8B4513"
android:pathData="M45,30C45,25 50,20 60,20s15,5 15,10c0,3 -2,6 -5,8c-1,1 -2,2 -2,3v2c0,2 -3,2 -3,0v-2c0,-1 -1,-2 -2,-3C65,36 63,33 63,30h-6c0,3 -2,6 -5,8c-1,1 -2,2 -2,3v2c0,2 -3,2 -3,0v-2c0,-1 -1,-2 -2,-3z" />
<path
android:fillColor="#7C3AED"
android:pathData="M45,55L75,55C77,55 78,57 78,59L78,85C78,87 76,89 74,89L46,89C44,89 42,87 42,85L42,59C42,57 43,55 45,55z" />
<path
android:fillColor="#FFD6A5"
android:pathData="M40,60C38,60 36,62 36,64L36,74C36,76 38,78 40,78C42,78 44,76 44,74L44,64C44,62 42,60 40,60z" />
<path
android:fillColor="#FFD6A5"
android:pathData="M80,60C82,60 84,62 84,64L84,74C84,76 82,78 80,78C78,78 76,76 76,74L76,64C76,62 78,60 80,60z" />
<path
android:fillColor="#FF6B9D"
android:pathData="M55,65C55,63 56,62 58,62C59,62 60,63 60,64C60,63 61,62 62,62C64,62 65,63 65,65C65,67 60,72 60,72S55,67 55,65z" />
</vector>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="m0.473,7.687C1.889,4.953 6.44,3 12,3c5.386,0 9.831,1.832 11.393,4.431 -0.12,1.352 -1.226,2.67 -3.05,3.686 -1.926,-1.186 -5.193,-2.117 -8.343,-2.117 -3.171,0 -6.533,0.883 -8.446,2.071 -1.818,-1.008 -2.9,-2.261 -3.081,-3.384ZM12,15c-5.404,0 -9.941,-1.667 -12,-4.115v3.615c0,3.645 5.271,6.5 12,6.5s12,-2.855 12,-6.5v-3.615c-2.059,2.448 -6.596,4.115 -12,4.115Z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="#FF000000" android:pathData="M22.5,21h-1.5L21,6.5c0,-2.481 -2.019,-4.5 -4.5,-4.5h-1.258c-0.243,-0.363 -0.541,-0.694 -0.889,-0.979C13.307,0.163 11.946,-0.179 10.617,0.089l-3.598,0.72c-2.329,0.466 -4.02,2.527 -4.02,4.902v15.289L1.5,21c-0.829,0 -1.5,0.672 -1.5,1.5s0.671,1.5 1.5,1.5L22.5,24c0.829,0 1.5,-0.672 1.5,-1.5s-0.671,-1.5 -1.5,-1.5ZM18,6.5v14.5h-2L16,5h0.5c0.827,0 1.5,0.673 1.5,1.5ZM6,5.711c0,-0.95 0.676,-1.774 1.608,-1.961l3.598,-0.72c0.444,-0.087 0.897,0.025 1.246,0.312 0.349,0.285 0.549,0.708 0.549,1.159L13.001,21L6,21L6,5.711ZM12,12.5c0,0.828 -0.672,1.5 -1.5,1.5s-1.5,-0.672 -1.5,-1.5 0.672,-1.5 1.5,-1.5 1.5,0.672 1.5,1.5Z"/>
</vector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:fillColor="#F44336"
android:pathData="M256,0C114.61,0 0,114.61 0,256s114.61,256 256,256s256,-114.61 256,-256C511.85,114.68 397.32,0.15 256,0zM341.33,311.19c8.67,7.98 9.23,21.48 1.25,30.14c-7.98,8.67 -21.48,9.23 -30.14,1.25c-0.43,-0.4 -0.85,-0.82 -1.25,-1.25L256,286.17l-55.17,55.17c-8.48,8.19 -21.98,7.95 -30.17,-0.52c-7.98,-8.27 -7.98,-21.37 0,-29.64L225.84,256l-55.17,-55.17c-8.19,-8.48 -7.95,-21.98 0.52,-30.17c8.27,-7.98 21.37,-7.98 29.64,0L256,225.84l55.19,-55.17c7.98,-8.67 21.48,-9.23 30.14,-1.25c8.67,7.98 9.23,21.48 1.25,30.14c-0.4,0.43 -0.82,0.85 -1.25,1.25L286.17,256L341.33,311.19z" />
</vector>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M20,4H4C2.89,4 2.01,4.89 2.01,6L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2V6C22,4.89 21.11,4 20,4zM20,18H4V6h16V18zM6,10h2v2H6V10zM10,10h8v2h-8V10zM6,14h2v2H6V14zM10,14h8v2h-8V14z" />
</vector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#2196F3"
android:pathData="M12,24A12,12 0,1 0,0 12,12.013 12.013,0 0,0 12,24ZM12,5a1.5,1.5 0,1 1,-1.5 1.5A1.5,1.5 0,0 1,12 5ZM11,10h1a2,2 0,0 1,2 2v6a1,1 0,0 1,-2 0L12,12L11,12a1,1 0,0 1,0 -2Z" />
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="m19.776,3.208c-2.076,-2.068 -4.839,-3.208 -7.776,-3.208s-5.701,1.139 -7.777,3.208c-2.078,2.069 -3.223,4.821 -3.223,7.747s1.145,5.678 3.236,7.761l4.151,3.818c0.971,0.945 2.254,1.466 3.612,1.466s2.641,-0.521 3.602,-1.457l4.175,-3.842c2.078,-2.069 3.223,-4.821 3.223,-7.747s-1.145,-5.678 -3.223,-7.747ZM19.085,17.979l-4.171,3.838c-1.566,1.526 -4.253,1.536 -5.839,-0.01l-4.147,-3.814c-1.888,-1.88 -2.928,-4.379 -2.928,-7.038s1.04,-5.158 2.928,-7.038c1.889,-1.881 4.4,-2.917 7.072,-2.917s5.183,1.036 7.071,2.917c1.888,1.88 2.928,4.379 2.928,7.038s-1.04,5.158 -2.914,7.024ZM10,7.5v7c0,0.276 -0.224,0.5 -0.5,0.5s-0.5,-0.224 -0.5,-0.5v-7c0,-0.276 0.224,-0.5 0.5,-0.5s0.5,0.224 0.5,0.5ZM14.5,7h-1.5c-0.552,0 -1,0.449 -1,1v6.5c0,0.276 0.224,0.5 0.5,0.5s0.5,-0.224 0.5,-0.5v-2.5h1.5c1.379,0 2.5,-1.122 2.5,-2.5s-1.121,-2.5 -2.5,-2.5ZM14.5,11h-1.5v-3h1.5c0.827,0 1.5,0.673 1.5,1.5s-0.673,1.5 -1.5,1.5Z"/>
</vector>

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
</vector>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="512dp"
android:viewportWidth="512.29"
android:viewportHeight="512.29">
<path
android:fillColor="#FF000000"
android:pathData="M256.06,0L256.06,0c17.67,0 32,14.33 32,32v106.67c0,17.67 -14.33,32 -32,32l0,0c-17.67,0 -32,-14.33 -32,-32V32C224.06,14.33 238.39,0 256.06,0z"/>
<path
android:fillColor="#FF000000"
android:pathData="M330.73,105.39L330.73,105.39c-0.16,10.74 5.26,20.8 14.31,26.58c80.43,49.14 105.8,154.18 56.65,234.62S247.51,472.38 167.08,423.24S61.28,269.05 110.43,188.62c14.04,-22.98 33.32,-42.31 56.27,-56.42c9.21,-5.78 14.77,-15.92 14.7,-26.8l0,0c0.05,-17.67 -14.24,-32.04 -31.91,-32.09c-6.07,-0.02 -12.02,1.69 -17.16,4.93C22.23,146.63 -11.58,291.32 56.8,401.41s213.07,143.91 323.16,75.52s143.91,-213.07 75.52,-323.16c-19.03,-30.65 -44.88,-56.49 -75.52,-75.52c-15,-9.46 -34.82,-4.97 -44.28,10.02C332.45,93.4 330.73,99.33 330.73,105.39z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="m21,14c1.654,0 3,-1.346 3,-3s-1.346,-3 -3,-3c-1.595,0 -2.891,1.255 -2.983,2.828l-3.28,0.647c-0.278,-0.861 -0.775,-1.616 -1.438,-2.203l2.475,-3.541c0.375,0.169 0.788,0.268 1.226,0.268 1.654,0 3,-1.346 3,-3s-1.346,-3 -3,-3 -3,1.346 -3,3c0,0.857 0.366,1.626 0.944,2.173l-2.457,3.515c-0.735,-0.426 -1.577,-0.688 -2.487,-0.688s-1.751,0.262 -2.487,0.688l-2.457,-3.515c0.578,-0.547 0.944,-1.316 0.944,-2.173 0,-1.654 -1.346,-3 -3,-3S0,1.346 0,3s1.346,3 3,3c0.437,0 0.85,-0.099 1.226,-0.268l2.475,3.541c-1.034,0.917 -1.701,2.24 -1.701,3.727 0,1.308 0.516,2.491 1.341,3.383l-1.802,2.056c-0.452,-0.272 -0.975,-0.439 -1.539,-0.439 -1.654,0 -3,1.346 -3,3s1.346,3 3,3 3,-1.346 3,-3c0,-0.728 -0.271,-1.387 -0.704,-1.908l1.792,-2.045c0.822,0.593 1.823,0.953 2.911,0.953 1.284,0 2.445,-0.501 3.332,-1.299l3.114,2.747c-0.277,0.454 -0.446,0.982 -0.446,1.553 0,1.654 1.346,3 3,3s3,-1.346 3,-3 -1.346,-3 -3,-3c-0.723,0 -1.377,0.267 -1.896,0.694l-3.098,-2.733c0.616,-0.831 0.994,-1.85 0.994,-2.962 0,-0.187 -0.035,-0.364 -0.055,-0.546l3.185,-0.628c0.361,1.251 1.504,2.175 2.87,2.175ZM17,1c1.103,0 2,0.897 2,2s-0.897,2 -2,2 -2,-0.897 -2,-2 0.897,-2 2,-2ZM1,3c0,-1.103 0.897,-2 2,-2s2,0.897 2,2 -0.897,2 -2,2 -2,-0.897 -2,-2ZM3,23c-1.103,0 -2,-0.897 -2,-2s0.897,-2 2,-2 2,0.897 2,2 -0.897,2 -2,2ZM10,17c-2.206,0 -4,-1.794 -4,-4s1.794,-4 4,-4 4,1.794 4,4 -1.794,4 -4,4ZM19,19c1.103,0 2,0.897 2,2s-0.897,2 -2,2 -2,-0.897 -2,-2 0.897,-2 2,-2ZM21,9c1.103,0 2,0.897 2,2s-0.897,2 -2,2 -2,-0.897 -2,-2 0.897,-2 2,-2Z"/>
</vector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M15.045,0c-0.027,-0.001 -6.063,-0.001 -6.09,0 -2.736,0.024 -4.955,2.258 -4.955,4.999v14c0,2.757 2.243,5 5,5h6c2.757,0 5,-2.243 5,-5L20,5C20,2.258 17.781,0.025 15.045,0ZM18,18.999c0,1.654 -1.346,3 -3,3h-6c-1.654,0 -3,-1.346 -3,-3L6,5c0,-1.453 1.038,-2.667 2.411,-2.942l0.694,1.389c0.169,0.339 0.516,0.553 0.895,0.553h4c0.379,0 0.725,-0.214 0.895,-0.553l0.694,-1.389c1.373,0.274 2.411,1.489 2.411,2.942v14ZM13,19.999h-2c-0.552,0 -1,-0.448 -1,-1h0c0,-0.552 0.448,-1 1,-1h2c0.552,0 1,0.448 1,1h0c0,0.552 -0.448,1 -1,1Z" />
</vector>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M6.62,10.79c1.44,2.83 3.76,5.14 6.59,6.59l2.2,-2.2c0.27,-0.27 0.67,-0.36 1.02,-0.24 1.12,0.37 2.33,0.57 3.57,0.57 0.55,0 1,0.45 1,1V20c0,0.55 -0.45,1 -1,1 -9.39,0 -17,-7.61 -17,-17 0,-0.55 0.45,-1 1,-1h3.5c0.55,0 1,0.45 1,1 0,1.25 0.2,2.45 0.57,3.57 0.11,0.35 0.03,0.74 -0.25,1.02l-2.2,2.2z" />
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="#FF000000" android:pathData="M24,11a3,3 0,0 0,-2.841 -2.984,9.918 9.918,0 0,0 -5.175,-5.175A3,3 0,0 0,13 0L11,0A3,3 0,0 0,8.016 2.841,9.921 9.921,0 0,0 2.841,8.016a2.985,2.985 0,0 0,-0.659 5.856,9.964 9.964,0 0,0 5.332,7.069l-0.48,1.8a1,1 0,0 0,0.708 1.224A1.011,1.011 0,0 0,8 24a1,1 0,0 0,0.966 -0.742L9.4,21.652a9.928,9.928 0,0 0,5.211 0l0.428,1.606a1,1 0,0 0,1.932 -0.516l-0.48,-1.8a9.974,9.974 0,0 0,5.333 -7.07A2.994,2.994 0,0 0,24 11ZM12,20a8.021,8.021 0,0 1,-2.09 -0.28l1.7,-6.364a0.407,0.407 0,0 1,0.787 0l1.7,6.364A8.034,8.034 0,0 1,12 20ZM15.955,18.951 L14.325,12.841A2.38,2.38 0,0 0,12 11.054h0a2.38,2.38 0,0 0,-2.326 1.787l-1.629,6.11A7.962,7.962 0,0 1,4.26 14L5,14a3,3 0,0 0,0.081 -5.992A8.1,8.1 0,0 1,8.588 4.767,2.989 2.989,0 0,0 11,6h2a2.988,2.988 0,0 0,2.412 -1.233,8.094 8.094,0 0,1 3.506,3.241A3,3 0,0 0,19 14h0.74A7.968,7.968 0,0 1,15.955 18.951Z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="m12.263,22.634c0.12,0.25 0.015,0.549 -0.234,0.668 -0.431,0.207 -0.676,0.304 -0.96,0.429 -0.156,0.069 -0.531,0.268 -0.761,0.268s-0.512,-0.196 -0.65,-0.266c-2.191,-1.116 -9.375,-5.2 -9.375,-11.825l0.002,-5.357c0,-1.948 1.242,-3.667 3.091,-4.281l6.774,-2.245c0.102,-0.033 0.213,-0.033 0.315,0l6.774,2.245c1.849,0.614 3.091,2.333 3.091,4.281v2.917c0,0.277 -0.225,0.501 -0.502,0.501s-0.501,-0.224 -0.501,-0.501v-2.917c0,-1.514 -0.965,-2.853 -2.403,-3.33l-6.616,-2.193 -6.616,2.193c-1.438,0.477 -2.404,1.815 -2.404,3.33l-0.002,5.357c0,6.05 6.751,9.895 8.82,10.929l0.224,0.113 0.232,-0.094c0.272,-0.11 0.624,-0.26 1.032,-0.456 0.251,-0.12 0.549,-0.015 0.669,0.234zM20.33,17.987c0,1.105 -0.899,2.004 -2.004,2.004s-2.004,-0.899 -2.004,-2.004 0.899,-2.004 2.004,-2.004 2.004,0.899 2.004,2.004zM19.327,17.987c0,-0.553 -0.449,-1.002 -1.002,-1.002s-1.002,0.449 -1.002,1.002 0.449,1.002 1.002,1.002 1.002,-0.449 1.002,-1.002zM23.668,19.894c0.101,0.39 0.044,0.794 -0.16,1.14 -0.203,0.345 -0.528,0.592 -0.917,0.693 -0.39,0.103 -0.794,0.045 -1.141,-0.16l-0.223,-0.131c-0.408,0.341 -0.883,0.608 -1.399,0.788v0.272c0,0.829 -0.674,1.503 -1.503,1.503s-1.503,-0.674 -1.503,-1.503v-0.272c-0.516,-0.18 -0.99,-0.447 -1.399,-0.788l-0.223,0.131c-0.69,0.426 -1.663,0.172 -2.059,-0.534 -0.424,-0.687 -0.17,-1.662 0.534,-2.057l0.234,-0.138c-0.064,-0.299 -0.094,-0.576 -0.094,-0.851s0.03,-0.552 0.095,-0.852l-0.235,-0.138c-0.705,-0.394 -0.958,-1.37 -0.533,-2.057 0.395,-0.706 1.368,-0.958 2.058,-0.533l0.223,0.131c0.408,-0.341 0.883,-0.608 1.399,-0.788v-0.272c0,-0.829 0.674,-1.503 1.503,-1.503s1.503,0.674 1.503,1.503v0.272c0.516,0.18 0.99,0.447 1.399,0.788l0.223,-0.131c0.689,-0.425 1.663,-0.172 2.059,0.534 0.423,0.687 0.171,1.663 -0.535,2.057l-0.234,0.137c0.065,0.3 0.095,0.577 0.095,0.852s-0.03,0.552 -0.094,0.851l0.233,0.137c0.346,0.205 0.593,0.53 0.694,0.919zM21.909,16.461 L22.466,16.133c0.236,-0.132 0.32,-0.456 0.179,-0.685 -0.131,-0.237 -0.457,-0.32 -0.686,-0.179l-0.541,0.319c-0.198,0.116 -0.449,0.085 -0.609,-0.077 -0.432,-0.433 -0.987,-0.745 -1.607,-0.905 -0.221,-0.058 -0.376,-0.257 -0.376,-0.485v-0.643c0,-0.276 -0.225,-0.501 -0.501,-0.501s-0.501,0.225 -0.501,0.501v0.643c0,0.228 -0.155,0.428 -0.376,0.485 -0.62,0.161 -1.175,0.473 -1.607,0.905 -0.161,0.163 -0.411,0.195 -0.609,0.077l-0.541,-0.319c-0.228,-0.143 -0.555,-0.057 -0.685,0.178 -0.142,0.229 -0.058,0.554 0.177,0.685l0.558,0.329c0.197,0.115 0.29,0.349 0.227,0.569 -0.103,0.362 -0.151,0.666 -0.151,0.957s0.048,0.595 0.151,0.957c0.063,0.219 -0.031,0.453 -0.228,0.57l-0.557,0.327c-0.235,0.13 -0.319,0.457 -0.178,0.685 0.13,0.236 0.46,0.322 0.686,0.179l0.541,-0.319c0.195,-0.116 0.447,-0.085 0.609,0.077 0.432,0.433 0.987,0.745 1.607,0.905 0.221,0.058 0.376,0.257 0.376,0.485v0.643c0,0.276 0.225,0.501 0.501,0.501s0.501,-0.225 0.501,-0.501v-0.643c0,-0.228 0.155,-0.428 0.376,-0.485 0.62,-0.161 1.175,-0.473 1.607,-0.905 0.161,-0.161 0.413,-0.193 0.609,-0.077l0.541,0.319c0.227,0.14 0.555,0.058 0.685,-0.178 0.142,-0.23 0.058,-0.555 -0.178,-0.687l-0.556,-0.326c-0.197,-0.116 -0.291,-0.35 -0.228,-0.57 0.103,-0.362 0.151,-0.666 0.151,-0.957s-0.048,-0.595 -0.151,-0.957c-0.063,-0.219 0.03,-0.453 0.227,-0.569zM9.306,12.976c0,0.553 0.449,1.002 1.002,1.002s1.002,-0.449 1.002,-1.002 -0.449,-1.002 -1.002,-1.002 -1.002,0.449 -1.002,1.002zM12.312,9.969h-4.009c-1.105,0 -2.004,0.899 -2.004,2.004v2.004c0,1.105 0.899,2.004 2.004,2.004h2.505c0.277,0 0.501,0.224 0.501,0.501s-0.224,0.501 -0.501,0.501h-2.505c-1.658,0 -3.007,-1.349 -3.007,-3.007v-2.004c0,-1.305 0.84,-2.407 2.004,-2.822v-1.187c0,-1.658 1.349,-3.007 3.007,-3.007s3.007,1.349 3.007,3.007v1.187c1.164,0.415 2.004,1.517 2.004,2.822 0,0.277 -0.224,0.501 -0.501,0.501s-0.501,-0.224 -0.501,-0.501c0,-1.105 -0.899,-2.004 -2.004,-2.004zM12.312,8.967v-1.002c0,-1.105 -0.899,-2.004 -2.004,-2.004s-2.004,0.899 -2.004,2.004v1.002h4.009z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="m6.343,13.343c-0.155,0.155 -0.303,0.315 -0.443,0.48 -0.099,0.116 -0.24,0.176 -0.381,0.176 -0.115,0 -0.229,-0.039 -0.324,-0.119 -0.21,-0.179 -0.236,-0.495 -0.058,-0.705 0.157,-0.185 0.324,-0.365 0.499,-0.54 2.631,-2.632 6.677,-3.373 10.07,-1.84 0.251,0.113 0.363,0.41 0.25,0.661 -0.114,0.252 -0.409,0.363 -0.662,0.25 -3.014,-1.362 -6.611,-0.704 -8.951,1.636ZM20.709,6.784c0.227,0.16 0.537,0.109 0.698,-0.116 0.16,-0.225 0.108,-0.537 -0.116,-0.697C14.908,1.409 6.242,2.128 0.687,7.686c-0.192,0.192 -0.378,0.387 -0.558,0.586 -0.185,0.205 -0.169,0.521 0.036,0.707 0.096,0.086 0.216,0.129 0.335,0.129 0.136,0 0.272,-0.056 0.371,-0.165 0.168,-0.187 0.342,-0.37 0.522,-0.549 5.208,-5.21 13.332,-5.887 19.315,-1.609ZM23.854,7.853l-10.137,10.137c0.176,0.297 0.283,0.64 0.283,1.01 0,1.103 -0.897,2 -2,2s-2,-0.897 -2,-2 0.897,-2 2,-2c0.37,0 0.712,0.108 1.01,0.283l10.137,-10.137c0.195,-0.195 0.512,-0.195 0.707,0s0.195,0.512 0,0.707ZM13,18.999c0,-0.551 -0.449,-1 -1,-1s-1,0.449 -1,1 0.449,1 1,1 1,-0.449 1,-1Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M15,1H9v2h6V1zM11,14h2V8h-2v6zM19.03,7.39l1.42,-1.42c-0.43,-0.51 -0.9,-0.99 -1.41,-1.41l-1.42,1.42C16.07,4.74 14.12,4 12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9 9,-4.03 9,-9c0,-2.12 -0.74,-4.07 -1.97,-5.61zM12,20c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF9800"
android:pathData="M23.08,15.33L15,2.57c-0.68,-0.98 -1.81,-1.57 -3,-1.57s-2.32,0.58 -3.03,1.6L0.93,15.31c-1.02,1.46 -1.21,3.21 -0.5,4.56 0.7,1.35 2.17,2.12 4.01,2.12h15.12c1.85,0 3.31,-0.77 4.01,-2.12 0.7,-1.35 0.51,-3.09 -0.49,-4.54ZM11,7c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v6c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1L11,7ZM12,19c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5Z"/>
</vector>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true">
<shape>
<solid android:color="@color/background_white" />
<corners android:radius="16dp" />
<stroke
android:width="2dp"
android:color="@color/border_focus" />
</shape>
</item>
<item>
<shape>
<solid android:color="@color/background_white" />
<corners android:radius="16dp" />
<stroke
android:width="1dp"
android:color="@color/border_light" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#F3F4F6" />
<corners android:radius="12dp" />
<stroke android:width="1dp" android:color="#E5E7EB" />
</shape>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<gradient
android:startColor="@color/pressed_state"
android:endColor="@color/button_gradient_end"
android:angle="45" />
<corners android:radius="16dp" />
</shape>
</item>
<item android:state_enabled="false">
<shape>
<solid android:color="@color/disabled_color" />
<corners android:radius="16dp" />
</shape>
</item>
<item>
<shape>
<gradient
android:startColor="@color/button_gradient_start"
android:centerColor="@color/button_gradient_middle"
android:endColor="@color/button_gradient_end"
android:angle="45" />
<corners android:radius="16dp" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="@color/button_gradient_start"
android:centerColor="@color/button_gradient_middle"
android:endColor="@color/button_gradient_end"
android:angle="45" />
<corners android:radius="12dp" />
<stroke
android:width="2dp"
android:color="@color/purple_primary" />
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/transparent" />
<corners android:radius="12dp" />
<stroke
android:width="1dp"
android:color="@color/purple_light" />
</shape>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<gradient
android:startColor="@color/purple_light"
android:endColor="@color/purple_extra_light"
android:angle="45" />
<stroke
android:width="3dp"
android:color="@color/background_white" />
</shape>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:angle="135"
android:startColor="#FFFFFF"
android:centerColor="#FFEBEE"
android:endColor="#FFFFFF"
android:type="linear" />
<corners android:radius="24dp" />
</shape>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<solid android:color="#E0E7FF" />
<corners android:radius="8dp" />
</shape>
</item>
<item>
<shape>
<solid android:color="@android:color/transparent" />
<corners android:radius="8dp" />
<stroke android:width="1dp" android:color="#C7D2FE" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/ripple_color">
<item>
<shape android:shape="rectangle">
<corners android:radius="12dp" />
</shape>
</item>
</ripple>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="@color/purple_primary"
android:endColor="@color/primary_purple"
android:angle="90" />
<corners android:radius="2dp" />
</shape>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/text_secondary" />
<corners android:radius="1dp" />
</shape>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<solid android:color="#E0E7FF" />
<corners android:radius="20dp" />
</shape>
</item>
<item>
<shape>
<solid android:color="#F8FAFC" />
<corners android:radius="20dp" />
<stroke android:width="1dp" android:color="#E2E8F0" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/success_color" />
<size
android:width="6dp"
android:height="6dp" />
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@android:color/white" />
<size
android:width="6dp"
android:height="6dp" />
</shape>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<solid android:color="#F3F4F6" />
<corners android:radius="8dp" />
</shape>
</item>
<item>
<shape>
<solid android:color="@android:color/white" />
<corners android:radius="8dp" />
<stroke android:width="1dp" android:color="#D1D5DB" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#F9FAFB" />
<corners android:radius="12dp" />
<stroke android:width="1dp" android:color="#E5E7EB" />
</shape>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More