This commit is contained in:
202310715128 ARIF NURKHAYAN 2025-12-28 20:22:07 +07:00
parent 68246777fd
commit 92c76b21ea
42 changed files with 749 additions and 406 deletions

3
.idea/.gitignore generated vendored
View File

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

1
.idea/.name generated Normal file
View File

@ -0,0 +1 @@
WeatherDemo

View File

@ -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
View 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
View File

@ -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
View File

@ -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
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@ -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

View File

@ -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")

View File

@ -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>

View File

@ -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
)

View File

@ -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
)

View 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)
}
}
}
}
}

View File

@ -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()
}
}
}
}

View File

@ -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 {

View File

@ -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
}
}
}

View File

@ -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}")
}
})
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -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>

View 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" />

View 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>

View 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>

View File

@ -1,29 +1,43 @@
<?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">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<LinearLayout
android:id="@+id/mainLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:background="@color/solid_background_color">
<!-- Header -->
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
app:cardCornerRadius="16dp"
app:cardElevation="8dp"
app:cardBackgroundColor="@color/welcome_card_background">
<!-- Header Section -->
<TextView
android:layout_width="match_parent"
android:id="@+id/tvWelcomeMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textSize="32sp"
android:textStyle="bold"
android:padding="16dp"
android:text="Welcome!"
android:textColor="@color/white"
android:gravity="center"
android:layout_marginBottom="32dp" />
android:textSize="24sp"
android:textStyle="bold" />
</androidx.cardview.widget.CardView>
<!-- Search Section -->
<!-- Search -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -46,8 +60,7 @@
android:hint="Enter city name"
android:textColor="@color/white"
android:textColorHint="@color/hint_color"
android:background="@android:color/transparent"
android:padding="12dp" />
android:background="@android:color/transparent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnSearch"
@ -56,13 +69,11 @@
android:text="Search"
android:textColor="@color/white"
app:icon="@drawable/ic_search"
app:cornerRadius="12dp"
android:backgroundTint="@color/button_color" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Loading Indicator -->
<!-- Progress -->
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
@ -70,7 +81,7 @@
android:layout_gravity="center"
android:visibility="gone" />
<!-- Error Message -->
<!-- Error -->
<TextView
android:id="@+id/tvError"
android:layout_width="match_parent"
@ -80,7 +91,7 @@
android:visibility="gone"
android:layout_marginBottom="16dp" />
<!-- Weather Data Card -->
<!-- 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"
@ -96,7 +107,6 @@
android:orientation="vertical"
android:padding="24dp">
<!-- Location -->
<TextView
android:id="@+id/tvLocation"
android:layout_width="match_parent"
@ -108,7 +118,6 @@
android:gravity="center"
android:layout_marginBottom="8dp" />
<!-- Temperature -->
<TextView
android:id="@+id/tvTemperature"
android:layout_width="match_parent"
@ -120,7 +129,6 @@
android:gravity="center"
android:layout_marginBottom="16dp" />
<!-- Weather Condition -->
<TextView
android:id="@+id/tvCondition"
android:layout_width="match_parent"
@ -131,14 +139,11 @@
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">
android:columnCount="2">
<!-- Feels Like -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -155,7 +160,6 @@
android:textSize="14sp"
android:layout_gravity="end" />
<!-- Humidity -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -172,7 +176,6 @@
android:textSize="14sp"
android:layout_gravity="end" />
<!-- Wind Speed -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -188,11 +191,23 @@
android:textColor="@color/white"
android:textSize="14sp"
android:layout_gravity="end" />
</GridLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</ScrollView>
</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>

View File

@ -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>

View File

@ -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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

@ -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>

View File

@ -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")
}
}