Skip to content

Commit

Permalink
Kotlin app now displays all reported alarms, updated README
Browse files Browse the repository at this point in the history
  • Loading branch information
TMDStudios committed Nov 9, 2024
1 parent 313c474 commit a048878
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 47 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ __pycache__/
*.pyd

# Static files collected by Django

staticfiles/
static/

# Django migrations
!*/migrations/__init__.py
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ MingSec is a basic home security system designed to leverage OpenCV for motion d
- **Alarm Response**: Captures an image and records a video if the alarm is triggered. Both are uploaded to Dropbox.
- **Offline Operation**: Logs all videos and images when the internet connection is down, and uploads them once the connection is restored.
- **REST API**: Provides endpoints for users to check the status of each camera and request images or videos.
- **Kotlin Notification App**: A companion mobile app developed in Kotlin that receives notifications each time an alarm is triggered.

## Project Structure:

- **docs/** - Contains a Single Page Application (SPA) for the project demo. (Coming soon)
- **core/** - Includes the REST API and user interface for remote control and system management.
- **local/** - Contains the MingSec application, including configuration files and local scripts.
- **app/** - Contains the Kotlin notification app that receives alerts for triggered alarms.

## License

Expand All @@ -24,6 +26,7 @@ MingSec is a basic home security system designed to leverage OpenCV for motion d

- OpenCV for motion detection capabilities.
- Dropbox for storage solutions.
- Firebase for alarm notifications.

## You May Also Like...

Expand Down
21 changes: 17 additions & 4 deletions app/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugins {
id("com.android.application")
id("com.google.gms.google-services") // Ensure you have the Google services plugin
id("com.google.gms.google-services")
id("org.jetbrains.kotlin.android")
}

Expand Down Expand Up @@ -41,8 +41,7 @@ android {
compose = true
}
composeOptions {
// Match this to the Compose version you're using
kotlinCompilerExtensionVersion = "1.4.3" // Update if necessary based on the Compose BOM
kotlinCompilerExtensionVersion = "1.4.3"
}
packaging {
resources {
Expand All @@ -65,7 +64,21 @@ dependencies {

// Use the Firebase BOM for version management
implementation(platform("com.google.firebase:firebase-bom:33.5.1"))
implementation("com.google.firebase:firebase-messaging-ktx") // Removed version to use BOM
implementation("com.google.firebase:firebase-messaging-ktx")

// Retrofit for network requests
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")

// OkHttp for HTTP requests
implementation("com.squareup.okhttp3:okhttp:4.10.0")

// Coroutines for background tasks
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.2")

// Jetpack Compose dependencies
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")

testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
Expand Down
8 changes: 8 additions & 0 deletions app/app/src/main/java/com/tmdstudios/mingsec/AlarmReport.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.tmdstudios.mingsec

import com.google.gson.annotations.SerializedName

data class AlarmReport(
@SerializedName("camera") val camera: String,
@SerializedName("time") val time: String?
)
14 changes: 14 additions & 0 deletions app/app/src/main/java/com/tmdstudios/mingsec/ApiService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.tmdstudios.mingsec

import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Header

interface ApiService {

// GET request to fetch a list of alarms (using AlarmReport class)
@GET("ENDPOINT")
suspend fun getAlarms(
@Header("Authorization") apiKey: String // Authorization header
): Response<List<AlarmReport>> // A list of AlarmReport objects
}
122 changes: 83 additions & 39 deletions app/app/src/main/java/com/tmdstudios/mingsec/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,56 +1,103 @@
package com.tmdstudios.mingsec

import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.tmdstudios.mingsec.ui.theme.MingSecTheme
import kotlinx.coroutines.launch
import retrofit2.Response

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
createNotificationChannel() // Create the notification channel

setContent {
MingSecTheme {
// Surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Greeting("Android")
// Main Screen UI
MainScreen()
}
}
}
}

@Composable
fun MainScreen() {
// State to hold the list of alarm reports
var alarmReports by remember { mutableStateOf<List<AlarmReport>>(emptyList()) }
var isLoading by remember { mutableStateOf(true) }
var errorMessage by remember { mutableStateOf<String?>(null) }

// The API key to be used for authorization
val apiKey = "API_KEY" // Replace with your actual API key

// Launch a coroutine for network request
val scope = rememberCoroutineScope()

// Fetch data from API using LaunchedEffect
LaunchedEffect(Unit) {
scope.launch {
try {
// Call the suspend function to fetch alarm reports from the API
val response: Response<List<AlarmReport>> = RetrofitClient.apiService.getAlarms("Bearer $apiKey")

// Check if the response is successful
if (response.isSuccessful) {
alarmReports = response.body() ?: emptyList()
isLoading = false
} else {
errorMessage = "Error: ${response.message()}"
isLoading = false
}
} catch (e: Exception) {
// Log the exception to identify the issue
errorMessage = "Exception: ${e.localizedMessage}"
isLoading = false
}
}
}

private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
"default_channel",
"Default Channel",
NotificationManager.IMPORTANCE_DEFAULT
)
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel)
// Surface to display the UI
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
Column(modifier = Modifier.padding(16.dp)) {
Header()

// Display loading or error message
if (isLoading) {
Text(text = "Loading alarm reports...", modifier = Modifier.padding(16.dp))
} else if (errorMessage != null) {
// Show the error message
Text(text = "Error: $errorMessage", modifier = Modifier.padding(16.dp))
} else {
// Display the alarm reports list
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(alarmReports) { report ->
AlarmCard(alarmReport = report)
}
}
}
}
}
}

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
// State variable to hold the FCM token
fun Header() {
var token by remember { mutableStateOf<String?>(null) }

LaunchedEffect(Unit) {
Expand All @@ -60,22 +107,19 @@ fun Greeting(name: String, modifier: Modifier = Modifier) {
}
}

// Display the UI based on whether the token has been retrieved
if (token != null) {
GreetingWithToken(name, token!!, modifier) // Use !! to assert that token is not null
} else {
// Display a loading message or similar while the token is being fetched
Text(
text = "Hello $name!\nFetching FCM Token...",
modifier = modifier.padding(16.dp) // Add some padding for better readability
)
}
}
token?.let { Log.d("FCM_TOKEN", it) }

@Composable
fun GreetingWithToken(name: String, token: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!\nFCM Token: $token",
modifier = modifier.padding(16.dp) // Add some padding for better readability
text = "Reported Alarms",
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(16.dp)
)
}

@Composable
fun AlarmCard(alarmReport: AlarmReport) {
Column(modifier = Modifier.padding(8.dp)) {
Text(text = "Camera: ${alarmReport.camera}", style = MaterialTheme.typography.bodyMedium)
Text(text = "Time: ${alarmReport.time}", style = MaterialTheme.typography.bodyLarge)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package com.tmdstudios.mingsec

import android.app.NotificationManager
import android.content.Context
import androidx.core.app.NotificationCompat
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import androidx.core.app.NotificationCompat

class MyFirebaseMessagingService : FirebaseMessagingService() {
override fun onMessageReceived(remoteMessage: RemoteMessage) {
Expand All @@ -18,7 +18,7 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {
val notificationId = 1

val builder = NotificationCompat.Builder(this, "default_channel")
.setSmallIcon(R.mipmap.ic_launcher) // Ensure you have a drawable icon
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(title)
.setContentText(message)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
Expand All @@ -28,6 +28,5 @@ class MyFirebaseMessagingService : FirebaseMessagingService() {

override fun onNewToken(token: String) {
super.onNewToken(token)
// Send the token to your server for later use
}
}
16 changes: 16 additions & 0 deletions app/app/src/main/java/com/tmdstudios/mingsec/RetrofitClient.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.tmdstudios.mingsec

import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object RetrofitClient {
private const val BASE_URL = "BASE_URL"

val apiService: ApiService by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
}

0 comments on commit a048878

Please sign in to comment.