28-12-25
3
.idea/.gitignore
generated
vendored
@ -1,3 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
1
.idea/.name
generated
Normal file
@ -0,0 +1 @@
|
||||
WeatherDemo
|
||||
4
.idea/deploymentTargetSelector.xml
generated
@ -4,10 +4,10 @@
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DropdownSelection timestamp="2025-10-31T13:55:47.201920Z">
|
||||
<DropdownSelection timestamp="2025-12-18T06:25:18.162928Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/khaliqozil/.android/avd/Pixel_6_API_34.avd" />
|
||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=325F50919896" />
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
|
||||
13
.idea/deviceManager.xml
generated
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DeviceTable">
|
||||
<option name="columnSorters">
|
||||
<list>
|
||||
<ColumnSorterState>
|
||||
<option name="column" value="Name" />
|
||||
<option name="order" value="ASCENDING" />
|
||||
</ColumnSorterState>
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/kotlinc.xml
generated
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="2.0.21" />
|
||||
</component>
|
||||
</project>
|
||||
1
.idea/misc.xml
generated
@ -1,3 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
|
||||
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@ -1,4 +0,0 @@
|
||||
kotlin version: 2.0.21
|
||||
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
|
||||
1. Kotlin compile daemon is ready
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
id("com.google.gms.google-services")
|
||||
}
|
||||
|
||||
android {
|
||||
@ -37,6 +38,7 @@ android {
|
||||
|
||||
dependencies {
|
||||
|
||||
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.material)
|
||||
@ -46,6 +48,9 @@ dependencies {
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
|
||||
implementation("com.google.android.material:material:1.8.0")
|
||||
implementation("androidx.cardview:cardview:1.0.0")
|
||||
|
||||
|
||||
implementation ("com.google.android.material:material:1.8.0")
|
||||
|
||||
@ -57,4 +62,14 @@ dependencies {
|
||||
implementation ("com.squareup.okhttp3:logging-interceptor:4.10.0")
|
||||
|
||||
implementation ("androidx.activity:activity-ktx:1.7.2")
|
||||
|
||||
implementation("androidx.recyclerview:recyclerview:1.2.1")
|
||||
|
||||
// Firebase SDK
|
||||
implementation("com.google.firebase:firebase-auth:21.1.0") // Firebase Authentication untuk login
|
||||
implementation("com.google.firebase:firebase-database:20.0.5") // Firebase Realtime Database
|
||||
|
||||
// Firebase BoM (Bill of Materials)
|
||||
implementation(platform("com.google.firebase:firebase-bom:30.3.1")) // Pastikan semua Firebase SDK menggunakan versi yang kompatibel
|
||||
}
|
||||
apply(plugin = "com.google.gms.google-services")
|
||||
@ -2,32 +2,39 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<!-- Internet permission for API calls -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<!-- Network state permission for checking connectivity -->
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:icon="@drawable/ic_launcher_foreground"
|
||||
android:roundIcon="@drawable/ic_launcher_foreground"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.WeatherDemo"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
tools:targetApi="31">
|
||||
|
||||
<activity
|
||||
android:name=".ui.MainActivity"
|
||||
android:name=".ui.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=".ui.MainActivity"
|
||||
android:exported="true" />
|
||||
|
||||
<!-- WAJIB: ChatActivity -->
|
||||
<activity
|
||||
android:name=".ui.ChatActivity"
|
||||
android:exported="true" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@ -0,0 +1,9 @@
|
||||
package com.example.weatherdemo.data
|
||||
|
||||
data class Message(
|
||||
var id: String = "",
|
||||
var username: String = "",
|
||||
var message: String = "",
|
||||
val time: String = "",
|
||||
var timestamp: Long = 0L
|
||||
)
|
||||
@ -20,7 +20,7 @@ class WeatherRepository(private val apiService: WeatherApiService) {
|
||||
return try {
|
||||
// Make API call
|
||||
val response: Response<WeatherResponse> = apiService.getCurrentWeather(
|
||||
key = "YOUR_API_KEY", // API key
|
||||
key = "822615b3cef1437bb0202739251712", // API key
|
||||
query = location
|
||||
)
|
||||
|
||||
|
||||
84
app/src/main/java/com/example/weatherdemo/ui/ChatActivity.kt
Normal file
@ -0,0 +1,84 @@
|
||||
package com.example.weatherdemo.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.example.weatherdemo.R
|
||||
import com.example.weatherdemo.ui.adapter.ChatAdapter
|
||||
import com.example.weatherdemo.utils.FirebaseUtils
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
class ChatActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var etMessage: EditText
|
||||
private lateinit var btnSend: Button
|
||||
private lateinit var recyclerView: RecyclerView
|
||||
private lateinit var chatAdapter: ChatAdapter
|
||||
private lateinit var username: String // username user aktif
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_chat)
|
||||
|
||||
// Toolbar + tombol back
|
||||
val toolbar = findViewById<Toolbar>(R.id.toolbarChat)
|
||||
setSupportActionBar(toolbar)
|
||||
toolbar.setNavigationOnClickListener { finish() }
|
||||
|
||||
// ✅ Ambil username dari Intent
|
||||
username = intent.getStringExtra("username") ?: "Guest"
|
||||
|
||||
etMessage = findViewById(R.id.etMessage)
|
||||
btnSend = findViewById(R.id.btnSend)
|
||||
recyclerView = findViewById(R.id.recyclerView)
|
||||
|
||||
val layoutManager = LinearLayoutManager(this).apply {
|
||||
stackFromEnd = true
|
||||
}
|
||||
recyclerView.layoutManager = layoutManager
|
||||
|
||||
// 🔥 WAJIB kirim username ke ChatAdapter
|
||||
chatAdapter = ChatAdapter(username)
|
||||
recyclerView.adapter = chatAdapter
|
||||
|
||||
btnSend.setOnClickListener {
|
||||
val messageText = etMessage.text.toString().trim()
|
||||
|
||||
if (messageText.isNotEmpty()) {
|
||||
|
||||
val time = SimpleDateFormat(
|
||||
"HH:mm",
|
||||
Locale.getDefault()
|
||||
).format(Date())
|
||||
|
||||
FirebaseUtils.sendMessage(
|
||||
username = username,
|
||||
message = messageText,
|
||||
time = time
|
||||
)
|
||||
|
||||
etMessage.setText("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
FirebaseUtils.getMessages { messages ->
|
||||
chatAdapter.submitList(messages)
|
||||
|
||||
if (messages.isNotEmpty()) {
|
||||
recyclerView.post {
|
||||
recyclerView.scrollToPosition(messages.size - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package com.example.weatherdemo.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.example.weatherdemo.R
|
||||
|
||||
class LoginActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var etUsername: EditText
|
||||
private lateinit var btnLogin: Button
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_login)
|
||||
|
||||
etUsername = findViewById(R.id.etUsername)
|
||||
btnLogin = findViewById(R.id.btnLogin)
|
||||
|
||||
btnLogin.setOnClickListener {
|
||||
val username = etUsername.text.toString().trim()
|
||||
|
||||
if (username.isNotEmpty()) {
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
|
||||
// ✅ FIX: key KONSISTEN
|
||||
intent.putExtra("username", username)
|
||||
|
||||
startActivity(intent)
|
||||
finish()
|
||||
} else {
|
||||
Toast.makeText(this, "Please enter a username", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,18 @@
|
||||
package com.example.weatherdemo.ui
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.Observer
|
||||
import com.example.weatherdemo.R
|
||||
import com.example.weatherdemo.data.api.RetrofitInstance
|
||||
@ -19,38 +22,36 @@ import com.example.weatherdemo.viewmodel.WeatherViewModel
|
||||
import com.example.weatherdemo.viewmodel.WeatherViewModelFactory
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
|
||||
/**
|
||||
* Main Activity class that handles UI and user interactions
|
||||
* Observes ViewModel LiveData and updates UI accordingly
|
||||
*/
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
// Initialize ViewModel using viewModels delegate
|
||||
private val viewModel: WeatherViewModel by viewModels {
|
||||
// Create ViewModel with repository dependency
|
||||
WeatherViewModelFactory(
|
||||
WeatherRepository(RetrofitInstance.apiService)
|
||||
)
|
||||
WeatherViewModelFactory(WeatherRepository(RetrofitInstance.apiService))
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
// Initialize UI components
|
||||
// ✅ FIX DI SINI SAJA
|
||||
val username = intent.getStringExtra("username") ?: "Guest"
|
||||
|
||||
val tvWelcomeMessage = findViewById<TextView>(R.id.tvWelcomeMessage)
|
||||
tvWelcomeMessage.text = "Welcome, $username!"
|
||||
|
||||
// FAB chat → kirim username ke ChatActivity
|
||||
findViewById<FloatingActionButton>(R.id.fabChat).setOnClickListener {
|
||||
val i = Intent(this, ChatActivity::class.java)
|
||||
i.putExtra("username", username)
|
||||
startActivity(i)
|
||||
}
|
||||
|
||||
initViews()
|
||||
|
||||
// Set up observers for LiveData
|
||||
setupObservers()
|
||||
|
||||
// Load default location weather
|
||||
viewModel.fetchWeatherData("London")
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes UI components and sets up click listeners
|
||||
*/
|
||||
private fun initViews() {
|
||||
val btnSearch = findViewById<MaterialButton>(R.id.btnSearch)
|
||||
val etSearch = findViewById<EditText>(R.id.etSearch)
|
||||
@ -58,9 +59,7 @@ class MainActivity : AppCompatActivity() {
|
||||
btnSearch.setOnClickListener {
|
||||
val location = etSearch.text.toString().trim()
|
||||
if (location.isNotEmpty()) {
|
||||
// Hide keyboard
|
||||
hideKeyboard()
|
||||
// Fetch weather data for entered location
|
||||
viewModel.fetchWeatherData(location)
|
||||
} else {
|
||||
Toast.makeText(this, "Please enter a location", Toast.LENGTH_SHORT).show()
|
||||
@ -68,31 +67,21 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up observers for ViewModel LiveData
|
||||
* Observes weather data, loading state, and error messages
|
||||
*/
|
||||
private fun setupObservers() {
|
||||
// Observe weather data changes
|
||||
viewModel.weatherData.observe(this, Observer { weatherData ->
|
||||
weatherData?.let {
|
||||
updateWeatherUI(it)
|
||||
}
|
||||
weatherData?.let { updateWeatherUI(it) }
|
||||
})
|
||||
|
||||
// Observe loading state
|
||||
viewModel.isLoading.observe(this, Observer { isLoading ->
|
||||
val progressBar = findViewById<ProgressBar>(R.id.progressBar)
|
||||
progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
|
||||
findViewById<ProgressBar>(R.id.progressBar).visibility =
|
||||
if (isLoading) View.VISIBLE else View.GONE
|
||||
})
|
||||
|
||||
// Observe error messages
|
||||
viewModel.errorMessage.observe(this, Observer { errorMessage ->
|
||||
val tvError = findViewById<TextView>(R.id.tvError)
|
||||
if (errorMessage.isNotEmpty()) {
|
||||
tvError.text = errorMessage
|
||||
tvError.visibility = View.VISIBLE
|
||||
// Hide weather card on error
|
||||
findViewById<MaterialCardView>(R.id.weatherCard).visibility = View.GONE
|
||||
Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show()
|
||||
} else {
|
||||
@ -101,10 +90,6 @@ class MainActivity : AppCompatActivity() {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates UI with weather data
|
||||
* @param weatherResponse The weather data to display
|
||||
*/
|
||||
private fun updateWeatherUI(weatherResponse: WeatherResponse) {
|
||||
val weatherCard = findViewById<MaterialCardView>(R.id.weatherCard)
|
||||
val tvLocation = findViewById<TextView>(R.id.tvLocation)
|
||||
@ -114,7 +99,6 @@ class MainActivity : AppCompatActivity() {
|
||||
val tvHumidity = findViewById<TextView>(R.id.tvHumidity)
|
||||
val tvWind = findViewById<TextView>(R.id.tvWind)
|
||||
|
||||
// Update UI with weather data
|
||||
tvLocation.text = "${weatherResponse.location.name}, ${weatherResponse.location.country}"
|
||||
tvTemperature.text = "${weatherResponse.current.temp_c}°C"
|
||||
tvCondition.text = weatherResponse.current.condition.text
|
||||
@ -122,13 +106,35 @@ class MainActivity : AppCompatActivity() {
|
||||
tvHumidity.text = "${weatherResponse.current.humidity}%"
|
||||
tvWind.text = "${weatherResponse.current.wind_kph} km/h"
|
||||
|
||||
// Show weather card
|
||||
val condition = weatherResponse.current.condition.text.lowercase()
|
||||
val mainLayout = findViewById<LinearLayout>(R.id.mainLayout)
|
||||
|
||||
when {
|
||||
condition.contains("clear") || condition.contains("sunny") -> {
|
||||
weatherCard.setCardBackgroundColor(ContextCompat.getColor(this, R.color.sunny))
|
||||
mainLayout.setBackgroundColor(ContextCompat.getColor(this, R.color.sunny_background))
|
||||
}
|
||||
condition.contains("rain") -> {
|
||||
weatherCard.setCardBackgroundColor(ContextCompat.getColor(this, R.color.rainy))
|
||||
mainLayout.setBackgroundColor(ContextCompat.getColor(this, R.color.rainy_background))
|
||||
}
|
||||
condition.contains("cloudy") -> {
|
||||
weatherCard.setCardBackgroundColor(ContextCompat.getColor(this, R.color.cloudy))
|
||||
mainLayout.setBackgroundColor(ContextCompat.getColor(this, R.color.cloudy_background))
|
||||
}
|
||||
condition.contains("snow") -> {
|
||||
weatherCard.setCardBackgroundColor(ContextCompat.getColor(this, R.color.snowy))
|
||||
mainLayout.setBackgroundColor(ContextCompat.getColor(this, R.color.snowy_background))
|
||||
}
|
||||
else -> {
|
||||
weatherCard.setCardBackgroundColor(ContextCompat.getColor(this, R.color.default_weather))
|
||||
mainLayout.setBackgroundColor(ContextCompat.getColor(this, R.color.default_weather_background))
|
||||
}
|
||||
}
|
||||
|
||||
weatherCard.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the soft keyboard
|
||||
*/
|
||||
private fun hideKeyboard() {
|
||||
val view = this.currentFocus
|
||||
view?.let {
|
||||
|
||||
@ -0,0 +1,91 @@
|
||||
package com.example.weatherdemo.ui.adapter
|
||||
|
||||
import android.graphics.Color
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.example.weatherdemo.R
|
||||
import com.example.weatherdemo.data.Message
|
||||
|
||||
class ChatAdapter(
|
||||
private val currentUsername: String
|
||||
) : RecyclerView.Adapter<ChatAdapter.MessageViewHolder>() {
|
||||
|
||||
private val messageList = mutableListOf<Message>()
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder {
|
||||
val view = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_message, parent, false)
|
||||
return MessageViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: MessageViewHolder, position: Int) {
|
||||
holder.bind(messageList[position])
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = messageList.size
|
||||
|
||||
fun submitList(messages: List<Message>) {
|
||||
messageList.clear()
|
||||
messageList.addAll(messages)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
inner class MessageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
|
||||
private val container: LinearLayout =
|
||||
itemView.findViewById(R.id.container)
|
||||
|
||||
private val cardMessage: CardView =
|
||||
itemView.findViewById(R.id.cardMessage)
|
||||
|
||||
private val tvUsername: TextView =
|
||||
itemView.findViewById(R.id.tvUsername)
|
||||
|
||||
private val tvMessage: TextView =
|
||||
itemView.findViewById(R.id.tvMessage)
|
||||
|
||||
private val tvTime: TextView =
|
||||
itemView.findViewById(R.id.tvTime)
|
||||
|
||||
fun bind(message: Message) {
|
||||
tvUsername.text = message.username
|
||||
tvMessage.text = message.message
|
||||
tvTime.text = message.time
|
||||
|
||||
val params = container.layoutParams as RecyclerView.LayoutParams
|
||||
|
||||
if (message.username == currentUsername) {
|
||||
// ================= USER SENDIRI (KANAN) =================
|
||||
container.gravity = Gravity.END
|
||||
params.marginStart = 100
|
||||
params.marginEnd = 0
|
||||
|
||||
cardMessage.setCardBackgroundColor(
|
||||
Color.parseColor("#DCF8C6")
|
||||
)
|
||||
|
||||
tvUsername.visibility = View.VISIBLE // ✅ FIX UTAMA
|
||||
|
||||
} else {
|
||||
// ================= USER LAIN (KIRI) =================
|
||||
container.gravity = Gravity.START
|
||||
params.marginStart = 0
|
||||
params.marginEnd = 100
|
||||
|
||||
cardMessage.setCardBackgroundColor(
|
||||
Color.parseColor("#E3F2FD")
|
||||
)
|
||||
|
||||
tvUsername.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
container.layoutParams = params
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package com.example.weatherdemo.utils
|
||||
|
||||
import android.util.Log
|
||||
import com.example.weatherdemo.data.Message
|
||||
import com.google.firebase.database.*
|
||||
|
||||
object FirebaseUtils {
|
||||
|
||||
private val database = FirebaseDatabase.getInstance()
|
||||
|
||||
// ✅ node khusus untuk chat global
|
||||
private val messagesRef = database.getReference("global_chat")
|
||||
|
||||
fun sendMessage(username: String, message: String, time: String) {
|
||||
val id = messagesRef.push().key ?: return
|
||||
val msg = Message(
|
||||
id = id,
|
||||
username = username,
|
||||
message = message,
|
||||
time = time,
|
||||
timestamp = System.currentTimeMillis()
|
||||
)
|
||||
|
||||
messagesRef.child(id).setValue(msg)
|
||||
.addOnFailureListener { e ->
|
||||
Log.e("FirebaseUtils", "sendMessage failed: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
|
||||
fun getMessages(callback: (List<Message>) -> Unit) {
|
||||
messagesRef.addValueEventListener(object : ValueEventListener {
|
||||
override fun onDataChange(snapshot: DataSnapshot) {
|
||||
val messages = mutableListOf<Message>()
|
||||
for (data in snapshot.children) {
|
||||
val msg = data.getValue(Message::class.java)
|
||||
if (msg != null) messages.add(msg)
|
||||
}
|
||||
|
||||
// ✅ urutkan biar chat rapi
|
||||
messages.sortBy { it.timestamp }
|
||||
|
||||
callback(messages)
|
||||
}
|
||||
|
||||
override fun onCancelled(error: DatabaseError) {
|
||||
Log.e("FirebaseUtils", "getMessages cancelled: ${error.message}")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
BIN
app/src/main/res/chat_icon.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
@ -1,170 +1,74 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
<vector
|
||||
android:height="108dp"
|
||||
android:width="108dp"
|
||||
android:viewportHeight="108"
|
||||
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" />
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
</vector>
|
||||
|
||||
6
app/src/main/res/drawable/welcome_background.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<gradient xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:startColor="#87CEEB"
|
||||
android:endColor="#1E90FF"
|
||||
android:angle="45"
|
||||
android:type="linear" />
|
||||
53
app/src/main/res/layout/activity_chat.xml
Normal file
@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/background_gradient">
|
||||
|
||||
<!-- TOOLBAR + TOMBOL BACK -->
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbarChat"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/button_color"
|
||||
app:title="Global Chat"
|
||||
app:titleTextColor="@android:color/white"
|
||||
app:navigationIcon="@android:drawable/ic_media_previous" />
|
||||
|
||||
<!-- RecyclerView -->
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="8dp"
|
||||
android:scrollbars="vertical"
|
||||
android:paddingBottom="8dp"
|
||||
android:clipToPadding="false"
|
||||
/>
|
||||
|
||||
<!-- Input -->
|
||||
<EditText
|
||||
android:id="@+id/etMessage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Type a message"
|
||||
android:inputType="text"
|
||||
android:padding="10dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@android:color/white" />
|
||||
|
||||
<!-- Send -->
|
||||
<Button
|
||||
android:id="@+id/btnSend"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Send"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textColor="@android:color/white"
|
||||
android:background="@color/button_color" />
|
||||
|
||||
</LinearLayout>
|
||||
28
app/src/main/res/layout/activity_login.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
android:gravity="center"
|
||||
android:background="@drawable/background_gradient">
|
||||
|
||||
<!-- Username Input Field -->
|
||||
<EditText
|
||||
android:id="@+id/etUsername"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter username"
|
||||
android:textColor="@color/black"
|
||||
android:textColorHint="@color/gray"
|
||||
android:background="@android:color/white"
|
||||
android:padding="12dp" />
|
||||
|
||||
<!-- Login Button -->
|
||||
<Button
|
||||
android:id="@+id/btnLogin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Login"
|
||||
android:layout_marginTop="20dp" />
|
||||
</LinearLayout>
|
||||
@ -1,198 +1,213 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="16dp"
|
||||
android:background="@drawable/background_gradient">
|
||||
|
||||
<LinearLayout
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
android:layout_height="match_parent"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Header Section -->
|
||||
<TextView
|
||||
<LinearLayout
|
||||
android:id="@+id/mainLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="32sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/white"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="32dp" />
|
||||
android:orientation="vertical"
|
||||
android:background="@color/solid_background_color">
|
||||
|
||||
<!-- Search Section -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="8dp"
|
||||
app:cardBackgroundColor="@color/card_background">
|
||||
|
||||
<LinearLayout
|
||||
<!-- Header -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp">
|
||||
android:layout_marginBottom="32dp"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="8dp"
|
||||
app:cardBackgroundColor="@color/welcome_card_background">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSearch"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:hint="Enter city name"
|
||||
android:textColor="@color/white"
|
||||
android:textColorHint="@color/hint_color"
|
||||
android:background="@android:color/transparent"
|
||||
android:padding="12dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnSearch"
|
||||
<TextView
|
||||
android:id="@+id/tvWelcomeMessage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Search"
|
||||
android:padding="16dp"
|
||||
android:text="Welcome!"
|
||||
android:textColor="@color/white"
|
||||
app:icon="@drawable/ic_search"
|
||||
app:cornerRadius="12dp"
|
||||
android:backgroundTint="@color/button_color" />
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Loading Indicator -->
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- Error Message -->
|
||||
<TextView
|
||||
android:id="@+id/tvError"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/error_color"
|
||||
android:gravity="center"
|
||||
android:visibility="gone"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<!-- Weather Data Card -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/weatherCard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:cardCornerRadius="24dp"
|
||||
app:cardElevation="12dp"
|
||||
app:cardBackgroundColor="@color/weather_card_background">
|
||||
|
||||
<LinearLayout
|
||||
<!-- Search -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
android:layout_marginBottom="24dp"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="8dp"
|
||||
app:cardBackgroundColor="@color/card_background">
|
||||
|
||||
<!-- Location -->
|
||||
<TextView
|
||||
android:id="@+id/tvLocation"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Location"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/white"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="8dp" />
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Temperature -->
|
||||
<TextView
|
||||
android:id="@+id/tvTemperature"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="--°C"
|
||||
android:textSize="48sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/white"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="16dp" />
|
||||
<EditText
|
||||
android:id="@+id/etSearch"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:hint="Enter city name"
|
||||
android:textColor="@color/white"
|
||||
android:textColorHint="@color/hint_color"
|
||||
android:background="@android:color/transparent" />
|
||||
|
||||
<!-- Weather Condition -->
|
||||
<TextView
|
||||
android:id="@+id/tvCondition"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Condition"
|
||||
android:textSize="18sp"
|
||||
android:textColor="@color/white"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="24dp" />
|
||||
|
||||
<!-- Weather Details Grid -->
|
||||
<GridLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:columnCount="2"
|
||||
android:rowCount="2">
|
||||
|
||||
<!-- Feels Like -->
|
||||
<TextView
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnSearch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Feels Like:"
|
||||
android:textColor="@color/label_color"
|
||||
android:textSize="14sp" />
|
||||
android:text="Search"
|
||||
android:textColor="@color/white"
|
||||
app:icon="@drawable/ic_search"
|
||||
android:backgroundTint="@color/button_color" />
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- Progress -->
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- Error -->
|
||||
<TextView
|
||||
android:id="@+id/tvError"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/error_color"
|
||||
android:gravity="center"
|
||||
android:visibility="gone"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<!-- Weather Data Card (ID WAJIB sama seperti di MainActivity.kt) -->
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/weatherCard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:cardCornerRadius="24dp"
|
||||
app:cardElevation="12dp"
|
||||
app:cardBackgroundColor="@color/weather_card_background">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvFeelsLike"
|
||||
android:layout_width="wrap_content"
|
||||
android:id="@+id/tvLocation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Location"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/white"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTemperature"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="--°C"
|
||||
android:textSize="48sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp"
|
||||
android:layout_gravity="end" />
|
||||
|
||||
<!-- Humidity -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Humidity:"
|
||||
android:textColor="@color/label_color"
|
||||
android:textSize="14sp" />
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvHumidity"
|
||||
android:layout_width="wrap_content"
|
||||
android:id="@+id/tvCondition"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="--%"
|
||||
android:text="Condition"
|
||||
android:textSize="18sp"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp"
|
||||
android:layout_gravity="end" />
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="24dp" />
|
||||
|
||||
<!-- Wind Speed -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
<GridLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Wind:"
|
||||
android:textColor="@color/label_color"
|
||||
android:textSize="14sp" />
|
||||
android:columnCount="2">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvWind"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="-- km/h"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp"
|
||||
android:layout_gravity="end" />
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Feels Like:"
|
||||
android:textColor="@color/label_color"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</GridLayout>
|
||||
<TextView
|
||||
android:id="@+id/tvFeelsLike"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="--°C"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp"
|
||||
android:layout_gravity="end" />
|
||||
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Humidity:"
|
||||
android:textColor="@color/label_color"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
<TextView
|
||||
android:id="@+id/tvHumidity"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="--%"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp"
|
||||
android:layout_gravity="end" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Wind:"
|
||||
android:textColor="@color/label_color"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvWind"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="-- km/h"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp"
|
||||
android:layout_gravity="end" />
|
||||
</GridLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<!-- FAB CHAT GLOBAL -->
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabChat"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="20dp"
|
||||
android:contentDescription="Chat Global"
|
||||
app:tint="@android:color/white"
|
||||
app:backgroundTint="@color/button_color" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.2 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 982 B After Width: | Height: | Size: 2.1 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 3.0 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 4.4 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 5.9 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 9.7 KiB |
@ -8,11 +8,34 @@
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
|
||||
<!-- Custom colors for our weather app -->
|
||||
<!-- Custom colors for weather app (Updated - No Transparent White) -->
|
||||
<color name="button_color">#2196F3</color>
|
||||
<color name="error_color">#FF5252</color>
|
||||
<color name="hint_color">#B3FFFFFF</color>
|
||||
<color name="label_color">#CCFFFFFF</color>
|
||||
<color name="card_background">#4DFFFFFF</color>
|
||||
<color name="weather_card_background">#803496DB</color>
|
||||
|
||||
<!-- CHANGED: Ganti dari transparan putih ke warna solid -->
|
||||
<color name="hint_color">#78909C</color> <!-- Blue Grey 400 - untuk hint text -->
|
||||
<color name="label_color">#ECEFF1</color> <!-- Blue Grey 50 - untuk label -->
|
||||
<color name="card_background">#37474F</color> <!-- Blue Grey 800 - untuk search card -->
|
||||
<color name="weather_card_background">#546E7A</color> <!-- Blue Grey 600 - untuk weather card -->
|
||||
|
||||
<color name="blogin">#2196F3</color>
|
||||
<color name="gray">#808080</color>
|
||||
<color name="welcome_card_background">#1E90FF</color>
|
||||
|
||||
<!-- Weather condition colors -->
|
||||
<color name="sunny">#FFEB3B</color> <!-- Yellow for sunny -->
|
||||
<color name="rainy">#1976D2</color> <!-- Blue for rainy -->
|
||||
<color name="cloudy">#90A4AE</color> <!-- Gray for cloudy -->
|
||||
<color name="snowy">#FFFFFF</color> <!-- White for snowy -->
|
||||
<color name="default_weather">#00BCD4</color>
|
||||
|
||||
<!-- Background colors for weather conditions -->
|
||||
<color name="sunny_background">#FFFAE3</color> <!-- Light Yellow -->
|
||||
<color name="rainy_background">#A7C7E7</color> <!-- Light Blue -->
|
||||
<color name="cloudy_background">#D3D3D3</color> <!-- Light Gray -->
|
||||
<color name="snowy_background">#F0F8FF</color> <!-- Alice Blue -->
|
||||
<color name="default_weather_background">#E0E0E0</color> <!-- Very Light Gray -->
|
||||
|
||||
<color name="solid_background_color">#ADD8E6</color>
|
||||
|
||||
</resources>
|
||||
@ -1,5 +1,12 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.kotlin.android) apply false
|
||||
// build.gradle.kts (Proyek Level)
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Tambahkan plugin Google Services
|
||||
classpath("com.google.gms:google-services:4.3.15")
|
||||
}
|
||||
}
|
||||