diff --git a/frontend/app/build.gradle.kts b/frontend/app/build.gradle.kts
index f0d6aa0f..f1b4305f 100644
--- a/frontend/app/build.gradle.kts
+++ b/frontend/app/build.gradle.kts
@@ -73,6 +73,12 @@ dependencies {
implementation("androidx.test.ext:junit-ktx:1.1.5")
implementation("androidx.compose.foundation:foundation-layout-android:1.5.4")
implementation("io.coil-kt:coil-compose:2.5.0")
+
+ implementation("androidx.work:work-runtime-ktx:2.8.0")
+ androidTestImplementation("androidx.work:work-testing:2.8.0")
+
+ implementation("com.squareup.picasso:picasso:2.8")
+
testImplementation("junit:junit:4.13.2")
testImplementation("org.mockito:mockito-core:4.11.0")
testImplementation("org.junit.jupiter:junit-jupiter:5.8.1")
@@ -86,8 +92,8 @@ dependencies {
androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.5.4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
- implementation("com.google.android.gms:play-services-location:17.0.0")
- implementation("com.google.android.gms:play-services-maps:17.0.0")
+ implementation("com.google.android.gms:play-services-location:21.0.1")
+ implementation("com.google.android.gms:play-services-maps:18.2.0")
implementation("com.google.maps.android:maps-compose:2.11.4")
implementation("androidx.activity:activity-ktx:1.3.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1")
diff --git a/frontend/app/src/main/AndroidManifest.xml b/frontend/app/src/main/AndroidManifest.xml
index 64a09753..63bf218f 100644
--- a/frontend/app/src/main/AndroidManifest.xml
+++ b/frontend/app/src/main/AndroidManifest.xml
@@ -36,7 +36,7 @@
android:theme="@style/Theme.Frontend" />
diff --git a/frontend/app/src/main/java/com/example/frontend/MapActivity.kt b/frontend/app/src/main/java/com/example/frontend/MainActivity.kt
similarity index 74%
rename from frontend/app/src/main/java/com/example/frontend/MapActivity.kt
rename to frontend/app/src/main/java/com/example/frontend/MainActivity.kt
index 83a611f2..81fbe33d 100644
--- a/frontend/app/src/main/java/com/example/frontend/MapActivity.kt
+++ b/frontend/app/src/main/java/com/example/frontend/MainActivity.kt
@@ -10,54 +10,46 @@ import android.os.Bundle
import android.provider.Settings
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
-import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
-import androidx.compose.material3.FloatingActionButton
-import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Scaffold
-import androidx.compose.material3.SnackbarHost
-import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
-import androidx.lifecycle.viewmodel.compose.viewModel
-import com.example.frontend.ui.component.BottomBar
-import com.example.frontend.ui.component.MapWithMarker
+import androidx.lifecycle.lifecycleScope
+import com.example.frontend.ui.map.FriendsMapUI
import com.example.frontend.ui.theme.FrontendTheme
-import com.example.frontend.viewmodel.FriendsViewModel
+import com.example.frontend.usecase.CheckInUseCase
+import com.example.frontend.usecase.PeriodicCheckInUseCase
+import com.example.frontend.usecase.SaveMyInfoUseCase
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
-import com.google.android.gms.location.LocationServices
import com.google.android.gms.maps.model.LatLng
import dagger.hilt.android.AndroidEntryPoint
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
+import javax.inject.Inject
@AndroidEntryPoint
-class MapActivity : ComponentActivity() {
- private lateinit var fusedLocationClient: FusedLocationProviderClient
+class MainActivity : ComponentActivity() {
+ @Inject
+ lateinit var fusedLocationClient: FusedLocationProviderClient
var currentLocation by mutableStateOf(null)
+ @Inject
+ lateinit var checkInUseCase: CheckInUseCase
+
+ @Inject
+ lateinit var saveMyInfoUseCase: SaveMyInfoUseCase
+
private fun hasBackgroundLocationPermission(context: Context): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
ContextCompat.checkSelfPermission(
@@ -108,7 +100,6 @@ class MapActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
setContent {
FrontendTheme {
@@ -127,11 +118,16 @@ class MapActivity : ComponentActivity() {
}
checkAndRequestLocationPermissions()
+
+ PeriodicCheckInUseCase(this).execute()
+
+ lifecycleScope.launch {
+ saveMyInfoUseCase.execute()
+ }
}
override fun onResume() {
super.onResume()
- fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
setContent {
FrontendTheme {
@@ -222,45 +218,3 @@ class MapActivity : ComponentActivity() {
}
}
-@Composable
-fun FriendsMapUI(currentLocation: LatLng?, onClick: () -> Unit) {
- val viewModel: FriendsViewModel = viewModel()
- val friendsList by viewModel.friendsList.observeAsState(emptyList())
- val snackbarHostState = remember { SnackbarHostState() }
- LaunchedEffect(Unit) {
- while (isActive) {
- viewModel.fetchFriends()
- delay(3000)
- }
- }
-
- Scaffold(
- snackbarHost = {
- SnackbarHost(hostState = snackbarHostState)
- },
- bottomBar = {
- BottomBar(currentLocation)
- }
- ) { paddingValues ->
- Box(
- modifier = Modifier
- .padding(paddingValues)
- .fillMaxSize(),
- contentAlignment = Alignment.BottomEnd
- ) {
- MapWithMarker(currentLocation, friendsList)
- FloatingActionButton(
- onClick = { onClick() },
- modifier = Modifier.padding(16.dp)
- ) {
- Icon(Icons.Filled.Add, contentDescription = null)
- }
-
- }
-
- }
-
-
-}
-
-
diff --git a/frontend/app/src/main/java/com/example/frontend/MeetupListUI.kt b/frontend/app/src/main/java/com/example/frontend/MeetupListUI.kt
index 79edbd6a..db78b1b8 100644
--- a/frontend/app/src/main/java/com/example/frontend/MeetupListUI.kt
+++ b/frontend/app/src/main/java/com/example/frontend/MeetupListUI.kt
@@ -159,7 +159,7 @@ fun ShowMeetUpUIPreview() {
modifier = Modifier
.size(46.dp)
.clickable {
- val nextIntent = Intent(context, MapActivity::class.java)
+ val nextIntent = Intent(context, MainActivity::class.java)
context.startActivity(nextIntent)
// finish current activity
if (context is Activity) {
diff --git a/frontend/app/src/main/java/com/example/frontend/SplashScreenActivity.kt b/frontend/app/src/main/java/com/example/frontend/SplashScreenActivity.kt
index 8e1266b7..dfaba1b5 100644
--- a/frontend/app/src/main/java/com/example/frontend/SplashScreenActivity.kt
+++ b/frontend/app/src/main/java/com/example/frontend/SplashScreenActivity.kt
@@ -17,7 +17,7 @@ class SplashScreenActivity : AppCompatActivity() {
if (userContextRepository.getIsLoggedIn()) {
// User is already logged in, skip the login activity
- startActivity(Intent(this, MapActivity::class.java))
+ startActivity(Intent(this, MainActivity::class.java))
} else {
// User is not logged in, go to the login activity
startActivity(Intent(this, LoginActivity::class.java))
diff --git a/frontend/app/src/main/java/com/example/frontend/api/CheckInService.kt b/frontend/app/src/main/java/com/example/frontend/api/CheckInService.kt
new file mode 100644
index 00000000..d7f01a5f
--- /dev/null
+++ b/frontend/app/src/main/java/com/example/frontend/api/CheckInService.kt
@@ -0,0 +1,41 @@
+package com.example.frontend.api
+
+import android.content.Context
+import com.example.frontend.interceptor.AuthInterceptor
+import com.example.frontend.model.CheckInModel
+import com.example.frontend.utilities.BASE_URL
+import com.example.frontend.utilities.GsonProvider
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import retrofit2.Call
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+import retrofit2.http.Body
+import retrofit2.http.POST
+
+/**
+ * Used to connect to the server
+ * About check in domain
+ */
+interface CheckInService {
+ @POST("/check_ins")
+ fun login(@Body checkInModel: CheckInModel): Call?
+
+ companion object {
+ fun create(context: Context): CheckInService {
+ val logger = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BASIC }
+
+ val client = OkHttpClient.Builder()
+ .addInterceptor(logger)
+ .addInterceptor(AuthInterceptor(context))
+ .build()
+
+ return Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(client)
+ .addConverterFactory(GsonConverterFactory.create(GsonProvider.gson))
+ .build()
+ .create(CheckInService::class.java)
+ }
+ }
+}
diff --git a/frontend/app/src/main/java/com/example/frontend/api/UserService.kt b/frontend/app/src/main/java/com/example/frontend/api/UserService.kt
index 73935121..83c03373 100644
--- a/frontend/app/src/main/java/com/example/frontend/api/UserService.kt
+++ b/frontend/app/src/main/java/com/example/frontend/api/UserService.kt
@@ -11,6 +11,9 @@ interface UserService {
@GET("/users")
suspend fun getAllUsers(): List
+ @GET("/users/me")
+ suspend fun getMyInfo(): UserModel
+
@GET("/friends")
suspend fun getAllFriends(): List
@@ -22,4 +25,4 @@ interface UserService {
@POST("/friends/{id}/confirm")
suspend fun confirmRequest(@Path("id") friendId: Long)
-}
\ No newline at end of file
+}
diff --git a/frontend/app/src/main/java/com/example/frontend/di/AppModule.kt b/frontend/app/src/main/java/com/example/frontend/di/AppModule.kt
new file mode 100644
index 00000000..0117efcb
--- /dev/null
+++ b/frontend/app/src/main/java/com/example/frontend/di/AppModule.kt
@@ -0,0 +1,52 @@
+package com.example.frontend.di
+
+import android.content.Context
+import com.example.frontend.api.FriendService
+import com.example.frontend.api.UserService
+import com.example.frontend.interceptor.AuthInterceptor
+import com.example.frontend.repository.FriendsRepository
+import com.example.frontend.utilities.BASE_URL
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import okhttp3.OkHttpClient
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+
+@Module
+@InstallIn(SingletonComponent::class)
+object AppModule {
+
+ @Provides
+ fun provideRetrofit(@ApplicationContext context: Context): Retrofit {
+ val authInterceptor = AuthInterceptor(context)
+
+ val okHttpClient = OkHttpClient.Builder()
+ .addInterceptor(authInterceptor)
+ .build()
+
+ return Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ }
+
+ @Provides
+ fun provideFriendsRepository(api: FriendService): FriendsRepository {
+ return FriendsRepository(api)
+ }
+
+ @Provides
+ fun provideFriendAPI(retrofit: Retrofit): FriendService {
+ return retrofit.create(FriendService::class.java)
+ }
+
+ @Provides
+ fun provideUserService(retrofit: Retrofit): UserService {
+ return retrofit.create(UserService::class.java)
+ }
+
+}
diff --git a/frontend/app/src/main/java/com/example/frontend/di/LocationModule.kt b/frontend/app/src/main/java/com/example/frontend/di/LocationModule.kt
new file mode 100644
index 00000000..cd242dc7
--- /dev/null
+++ b/frontend/app/src/main/java/com/example/frontend/di/LocationModule.kt
@@ -0,0 +1,26 @@
+package com.example.frontend.di
+
+import android.content.Context
+import com.google.android.gms.location.FusedLocationProviderClient
+import com.google.android.gms.location.LocationServices
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+
+/*
+ * 지리정보 관련 데이터를 제공하는 모듈
+ */
+@Module
+@InstallIn(SingletonComponent::class)
+object LocationModule {
+
+ @Provides
+ @Singleton
+ fun provideFusedLocationProviderClient(@ApplicationContext context: Context): FusedLocationProviderClient {
+ return LocationServices.getFusedLocationProviderClient(context)
+ }
+}
diff --git a/frontend/app/src/main/java/com/example/frontend/model/CheckInModel.kt b/frontend/app/src/main/java/com/example/frontend/model/CheckInModel.kt
new file mode 100644
index 00000000..c1d6bb59
--- /dev/null
+++ b/frontend/app/src/main/java/com/example/frontend/model/CheckInModel.kt
@@ -0,0 +1,6 @@
+package com.example.frontend.model
+
+data class CheckInModel(
+ val latitude: Double,
+ val longitude: Double,
+)
diff --git a/frontend/app/src/main/java/com/example/frontend/repository/FriendsRepository.kt b/frontend/app/src/main/java/com/example/frontend/repository/FriendsRepository.kt
index 4a316374..78614bc2 100644
--- a/frontend/app/src/main/java/com/example/frontend/repository/FriendsRepository.kt
+++ b/frontend/app/src/main/java/com/example/frontend/repository/FriendsRepository.kt
@@ -1,23 +1,11 @@
package com.example.frontend.repository
-import android.content.Context
import com.example.frontend.api.FriendService
-import com.example.frontend.api.UserService
import com.example.frontend.callback.FriendLocationCallback
-import com.example.frontend.interceptor.AuthInterceptor
import com.example.frontend.model.UserWithLocationModel
-import com.example.frontend.utilities.BASE_URL
-import dagger.Module
-import dagger.Provides
-import dagger.hilt.InstallIn
-import dagger.hilt.android.qualifiers.ApplicationContext
-import dagger.hilt.components.SingletonComponent
-import okhttp3.OkHttpClient
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
-import retrofit2.Retrofit
-import retrofit2.converter.gson.GsonConverterFactory
class FriendsRepository(private val friendService: FriendService) {
@@ -43,39 +31,3 @@ class FriendsRepository(private val friendService: FriendService) {
})
}
}
-
-@Module
-@InstallIn(SingletonComponent::class)
-object AppModule {
-
- @Provides
- fun provideRetrofit(@ApplicationContext context: Context): Retrofit {
- val authInterceptor = AuthInterceptor(context)
-
- val okHttpClient = OkHttpClient.Builder()
- .addInterceptor(authInterceptor)
- .build()
-
- return Retrofit.Builder()
- .baseUrl(BASE_URL)
- .client(okHttpClient)
- .addConverterFactory(GsonConverterFactory.create())
- .build()
- }
-
- @Provides
- fun provideFriendsRepository(api: FriendService): FriendsRepository {
- return FriendsRepository(api)
- }
-
- @Provides
- fun provideFriendAPI(retrofit: Retrofit): FriendService {
- return retrofit.create(FriendService::class.java)
- }
-
- @Provides
- fun provideUserService(retrofit: Retrofit): UserService {
- return retrofit.create(UserService::class.java)
- }
-
-}
diff --git a/frontend/app/src/main/java/com/example/frontend/repository/UserContextRepository.kt b/frontend/app/src/main/java/com/example/frontend/repository/UserContextRepository.kt
index 194c83b4..7eeb6964 100644
--- a/frontend/app/src/main/java/com/example/frontend/repository/UserContextRepository.kt
+++ b/frontend/app/src/main/java/com/example/frontend/repository/UserContextRepository.kt
@@ -52,6 +52,14 @@ class UserContextRepository(
return store.getBoolean(IS_LOGGED_IN) ?: false
}
+ fun getUserId(): Int? {
+ return store.getInt(USER_ID)
+ }
+
+ fun saveUserId(userId: Int) {
+ store.putInt(USER_ID, userId)
+ }
+
fun clear() {
store.clear()
}
@@ -75,5 +83,6 @@ class UserContextRepository(
private const val AUTH_TOKEN = "AUTH_TOKEN"
private const val SELECTED_PREDEFINED_IMAGE = "SELECTED_PREDEFINED_IMAGE"
private const val IS_LOGGED_IN = "IS_LOGGED_IN"
+ private const val USER_ID = "USER_ID"
}
}
diff --git a/frontend/app/src/main/java/com/example/frontend/repository/UsersRepository.kt b/frontend/app/src/main/java/com/example/frontend/repository/UsersRepository.kt
index 6c357a04..8cc27440 100644
--- a/frontend/app/src/main/java/com/example/frontend/repository/UsersRepository.kt
+++ b/frontend/app/src/main/java/com/example/frontend/repository/UsersRepository.kt
@@ -10,6 +10,10 @@ class UsersRepository @Inject constructor(private val userService: UserService)
return userService.getAllUsers()
}
+ suspend fun getMyInfo(): UserModel {
+ return userService.getMyInfo()
+ }
+
suspend fun getFriends(): List {
return userService.getAllFriends()
}
diff --git a/frontend/app/src/main/java/com/example/frontend/ui/component/BottomBar.kt b/frontend/app/src/main/java/com/example/frontend/ui/component/BottomBar.kt
index 36f1e4e6..41c9c78d 100644
--- a/frontend/app/src/main/java/com/example/frontend/ui/component/BottomBar.kt
+++ b/frontend/app/src/main/java/com/example/frontend/ui/component/BottomBar.kt
@@ -22,15 +22,14 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import com.example.frontend.MeetupActivity
+import com.example.frontend.MainActivity
import com.example.frontend.MeetupListUI
import com.example.frontend.MissionActivity
import com.example.frontend.ui.friend.FriendActivity
import com.example.frontend.ui.settings.UserInfoActivity
-import com.google.android.gms.maps.model.LatLng
@Composable
-fun BottomBar(currentLocation: LatLng?) {
+fun BottomBar() {
val context = LocalContext.current
val icons = listOf(
Icons.Default.Star,
@@ -57,10 +56,14 @@ fun BottomBar(currentLocation: LatLng?) {
IconToggleButton(icon = icon) {
when (icon) {
icons[0] -> {
- // MeetUp 생성으로 이동
- val nextIntent = Intent(context, MeetupActivity::class.java)
- nextIntent.putExtra("currentLocation", currentLocation)
- context.startActivity(nextIntent)
+ // If current activity is MainActivity, do nothing
+ if (context.javaClass.simpleName == "MainActivity") {
+ return@IconToggleButton
+ } else {
+ // MainActivity 이동
+ val nextIntent = Intent(context, MainActivity::class.java)
+ context.startActivity(nextIntent)
+ }
}
icons[1] -> {
@@ -96,5 +99,5 @@ fun BottomBar(currentLocation: LatLng?) {
@Composable
@Preview
private fun BottomBarPreview() {
- BottomBar(null)
+ BottomBar()
}
diff --git a/frontend/app/src/main/java/com/example/frontend/ui/component/MapUI.kt b/frontend/app/src/main/java/com/example/frontend/ui/component/MapUI.kt
index bbe55060..c758592d 100644
--- a/frontend/app/src/main/java/com/example/frontend/ui/component/MapUI.kt
+++ b/frontend/app/src/main/java/com/example/frontend/ui/component/MapUI.kt
@@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import com.example.frontend.model.UserWithLocationModel
import com.example.frontend.ui.theme.FrontendTheme
@@ -12,15 +13,18 @@ import com.google.android.gms.maps.model.BitmapDescriptorFactory
import com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.LatLng
import com.google.maps.android.compose.GoogleMap
+import com.google.maps.android.compose.MapUiSettings
import com.google.maps.android.compose.Marker
import com.google.maps.android.compose.MarkerState
+import com.google.maps.android.compose.Polygon
import com.google.maps.android.compose.rememberCameraPositionState
import kotlin.random.Random
@Composable
fun MapWithMarker(
currentLocation: LatLng?,
- friends: List
+ friends: List,
+ polygon: List? = null
) {
Box(
modifier = Modifier
@@ -33,7 +37,8 @@ fun MapWithMarker(
GoogleMap(
modifier = Modifier.fillMaxSize(),
- cameraPositionState = cameraPositionState
+ cameraPositionState = cameraPositionState,
+ uiSettings = MapUiSettings(zoomControlsEnabled = false)
) {
Marker(
state = MarkerState(position = location),
@@ -41,6 +46,13 @@ fun MapWithMarker(
snippet = "You are here"
)
+ polygon?.let { locations ->
+ Polygon(
+ points = locations,
+ fillColor = Color(0x89CFF0FF)
+ )
+ }
+
friends.forEach { buildMarkerIcon(it) }
}
}
diff --git a/frontend/app/src/main/java/com/example/frontend/ui/map/FriendsMapUI.kt b/frontend/app/src/main/java/com/example/frontend/ui/map/FriendsMapUI.kt
new file mode 100644
index 00000000..d5dcc140
--- /dev/null
+++ b/frontend/app/src/main/java/com/example/frontend/ui/map/FriendsMapUI.kt
@@ -0,0 +1,65 @@
+package com.example.frontend.ui.map
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material3.FloatingActionButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.SnackbarHost
+import androidx.compose.material3.SnackbarHostState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.example.frontend.ui.component.BottomBar
+import com.example.frontend.ui.component.MapWithMarker
+import com.example.frontend.utilities.SNU_POLYGON
+import com.example.frontend.viewmodel.FriendsViewModel
+import com.google.android.gms.maps.model.LatLng
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.isActive
+
+@Composable
+fun FriendsMapUI(currentLocation: LatLng?, onClick: () -> Unit) {
+ val viewModel: FriendsViewModel = viewModel()
+ val friendsList by viewModel.friendsList.observeAsState(emptyList())
+ val snackbarHostState = remember { SnackbarHostState() }
+ LaunchedEffect(Unit) {
+ while (isActive) {
+ viewModel.fetchFriends()
+ delay(3000)
+ }
+ }
+
+ Scaffold(
+ snackbarHost = {
+ SnackbarHost(hostState = snackbarHostState)
+ },
+ bottomBar = {
+ BottomBar()
+ }
+ ) { paddingValues ->
+ Box(
+ modifier = Modifier
+ .padding(paddingValues)
+ .fillMaxSize(),
+ contentAlignment = Alignment.BottomEnd
+ ) {
+ MapWithMarker(currentLocation, friendsList, SNU_POLYGON)
+ FloatingActionButton(
+ onClick = { onClick() },
+ modifier = Modifier.padding(16.dp)
+ ) {
+ Icon(Icons.Filled.Add, contentDescription = null)
+ }
+ }
+ }
+}
diff --git a/frontend/app/src/main/java/com/example/frontend/ui/map/ProfileDialog.kt b/frontend/app/src/main/java/com/example/frontend/ui/map/ProfileDialog.kt
new file mode 100644
index 00000000..a5611244
--- /dev/null
+++ b/frontend/app/src/main/java/com/example/frontend/ui/map/ProfileDialog.kt
@@ -0,0 +1,87 @@
+package com.example.frontend.ui.map
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.DialogProperties
+import com.example.frontend.data.predefinedImages
+
+/*
+ * 유저의 프로필을 보여주는 Dialog
+ */
+@Composable
+internal fun ProfileDialog(
+ title: String,
+ description: String,
+ onDismiss: () -> Unit = {},
+ painter: Painter,
+) {
+ return AlertDialog(
+ properties = DialogProperties(usePlatformDefaultWidth = false),
+ onDismissRequest = { onDismiss() },
+ title = {
+ Row(
+ modifier = Modifier.padding(bottom = 16.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ )
+ {
+ Image(
+ painter = painter,
+ contentDescription = "Profile Image",
+ modifier = Modifier
+ .size(80.dp)
+ .clip(RoundedCornerShape(40.dp))
+ )
+ Text(
+ text = title,
+ style = MaterialTheme.typography.headlineLarge,
+ modifier = Modifier.padding(start = 16.dp),
+ )
+ }
+ },
+ text = {
+ HorizontalDivider()
+ Text(
+ text = description,
+ style = MaterialTheme.typography.bodyMedium,
+ modifier = Modifier.padding(vertical = 16.dp),
+ )
+ },
+ confirmButton = {
+ Text(
+ text = "닫기",
+ style = MaterialTheme.typography.labelLarge,
+ color = MaterialTheme.colorScheme.primary,
+ modifier = Modifier
+ .padding(horizontal = 8.dp)
+ .clickable { onDismiss() },
+ )
+ },
+ )
+}
+
+
+@Composable
+@Preview
+private fun ProfileDialogPreview() {
+ ProfileDialog(
+ title = "호에엥맨",
+ description = "큭큭",
+ painter = painterResource(id = predefinedImages[0]),
+ )
+}
diff --git a/frontend/app/src/main/java/com/example/frontend/usecase/CheckInUseCase.kt b/frontend/app/src/main/java/com/example/frontend/usecase/CheckInUseCase.kt
new file mode 100644
index 00000000..f6e8df1f
--- /dev/null
+++ b/frontend/app/src/main/java/com/example/frontend/usecase/CheckInUseCase.kt
@@ -0,0 +1,38 @@
+package com.example.frontend.usecase
+
+import android.content.Context
+import android.util.Log
+import com.example.frontend.api.CheckInService
+import com.example.frontend.model.CheckInModel
+import dagger.hilt.android.qualifiers.ApplicationContext
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+import javax.inject.Inject
+
+/*
+ * 체크인을 수행하는 메서드
+ */
+class CheckInUseCase @Inject constructor(
+ @ApplicationContext context: Context
+) {
+ private val checkInService = CheckInService.create(context)
+
+ fun execute(latitude: Double, longitude: Double) {
+ val checkInModel = CheckInModel(latitude, longitude)
+ checkInService.login(checkInModel)?.enqueue(object : Callback {
+ override fun onResponse(call: Call, response: Response) {
+ if (response.isSuccessful) {
+ val body = response.body()
+ Log.d("CheckInUseCase", "Check in successful: $body")
+ } else {
+ Log.d("CheckInUseCase", "Check in failed: ${response.message()}")
+ }
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ Log.d("CheckInUseCase", "Check in failed: ${t.message}")
+ }
+ })
+ }
+}
diff --git a/frontend/app/src/main/java/com/example/frontend/usecase/PeriodicCheckInUseCase.kt b/frontend/app/src/main/java/com/example/frontend/usecase/PeriodicCheckInUseCase.kt
new file mode 100644
index 00000000..596f8625
--- /dev/null
+++ b/frontend/app/src/main/java/com/example/frontend/usecase/PeriodicCheckInUseCase.kt
@@ -0,0 +1,46 @@
+package com.example.frontend.usecase
+
+import android.Manifest
+import android.content.Context
+import android.content.pm.PackageManager
+import android.util.Log
+import androidx.core.app.ActivityCompat
+import com.example.frontend.utilities.PeriodicTask
+import com.google.android.gms.location.LocationServices
+import com.google.android.gms.location.Priority
+
+class PeriodicCheckInUseCase(private val context: Context) {
+ private val checkInUseCase = CheckInUseCase(context)
+ private val locationClient = LocationServices.getFusedLocationProviderClient(context)
+
+ fun execute() {
+
+ PeriodicTask(
+ command = { recordLocation() },
+ intervalInMinutes = INTERVAL_IN_MINUTES
+ ).execute()
+ }
+
+ private fun recordLocation() {
+ Log.i(TAG, "execute recordLocation")
+ if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
+ && ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED
+ ) {
+ Log.e(TAG, "No location permission")
+ return
+ }
+
+ locationClient.getCurrentLocation(PRIORITY, null).addOnSuccessListener { location ->
+ location?.let {
+ Log.i(TAG, "Location: $location")
+ checkInUseCase.execute(location.latitude, location.longitude)
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "PeriodicCheckInUseCase"
+ private const val PRIORITY = Priority.PRIORITY_HIGH_ACCURACY
+ private const val INTERVAL_IN_MINUTES = 5L
+ }
+}
diff --git a/frontend/app/src/main/java/com/example/frontend/usecase/SaveMyInfoUseCase.kt b/frontend/app/src/main/java/com/example/frontend/usecase/SaveMyInfoUseCase.kt
new file mode 100644
index 00000000..7757caed
--- /dev/null
+++ b/frontend/app/src/main/java/com/example/frontend/usecase/SaveMyInfoUseCase.kt
@@ -0,0 +1,28 @@
+package com.example.frontend.usecase
+
+import android.content.Context
+import android.util.Log
+import com.example.frontend.repository.UserContextRepository
+import com.example.frontend.repository.UsersRepository
+import dagger.hilt.android.qualifiers.ApplicationContext
+import javax.inject.Inject
+
+/*
+ * users/me API를 바탕으로 사용자 정보를 저장하는 UseCase
+ */
+class SaveMyInfoUseCase @Inject constructor(
+ private val repository: UsersRepository,
+ @ApplicationContext context: Context
+) {
+ private val userContextRepository = UserContextRepository.ofContext(context)
+
+ suspend fun execute() {
+ Log.i("SaveMyInfoUseCase", "execute")
+
+ repository.getMyInfo().apply {
+ userContextRepository.saveUserName(name)
+ userContextRepository.saveUserMail(email)
+ userContextRepository.saveUserId(id.toInt())
+ }
+ }
+}
diff --git a/frontend/app/src/main/java/com/example/frontend/usecase/login/LoginUseCase.kt b/frontend/app/src/main/java/com/example/frontend/usecase/login/LoginUseCase.kt
index a1623e8c..444ba84d 100644
--- a/frontend/app/src/main/java/com/example/frontend/usecase/login/LoginUseCase.kt
+++ b/frontend/app/src/main/java/com/example/frontend/usecase/login/LoginUseCase.kt
@@ -5,7 +5,7 @@ import android.content.Context
import android.content.Intent
import android.widget.Toast
import androidx.compose.runtime.MutableState
-import com.example.frontend.MapActivity
+import com.example.frontend.MainActivity
import com.example.frontend.api.AuthService
import com.example.frontend.model.AuthResponse
import com.example.frontend.model.LoginModel
@@ -29,7 +29,7 @@ class LoginUseCase(
val loginModel = LoginModel(email, password)
if (BYPASS_LOGIN) { // TODO(heka1024): Remove this flag
- val nextIntent = Intent(context, MapActivity::class.java)
+ val nextIntent = Intent(context, MainActivity::class.java)
context.startActivity(nextIntent)
if (context is Activity) {
context.finish()
@@ -44,7 +44,7 @@ class LoginUseCase(
saveAuthToken(authToken)
result.value = "Logged in successfully"
- val nextIntent = Intent(context, MapActivity::class.java)
+ val nextIntent = Intent(context, MainActivity::class.java)
context.startActivity(nextIntent)
if (context is Activity) {
diff --git a/frontend/app/src/main/java/com/example/frontend/utilities/Location.kt b/frontend/app/src/main/java/com/example/frontend/utilities/Location.kt
new file mode 100644
index 00000000..3856a635
--- /dev/null
+++ b/frontend/app/src/main/java/com/example/frontend/utilities/Location.kt
@@ -0,0 +1,24 @@
+package com.example.frontend.utilities
+
+import com.google.android.gms.maps.model.LatLng
+
+/*
+ * 서울대 폴리곤모양을 적어놓은 것
+ */
+val SNU_POLYGON = listOf(
+ LatLng(37.469020, 126.952321),
+ LatLng(37.467113, 126.947597),
+ LatLng(37.461482, 126.948941),
+ LatLng(37.459540, 126.947481),
+ LatLng(37.455836, 126.948220),
+ LatLng(37.450913, 126.949656),
+ LatLng(37.447540, 126.949270),
+ LatLng(37.447097, 126.951417),
+ LatLng(37.453400, 126.953478),
+ LatLng(37.457326, 126.956584),
+ LatLng(37.462478, 126.959963),
+ LatLng(37.467095, 126.960976),
+ LatLng(37.468567, 126.957310),
+)
+
+val BUILDING_302 = LatLng(37.466967, 126.950302)
diff --git a/frontend/app/src/main/java/com/example/frontend/utilities/PeriodicTask.kt b/frontend/app/src/main/java/com/example/frontend/utilities/PeriodicTask.kt
new file mode 100644
index 00000000..669a18d2
--- /dev/null
+++ b/frontend/app/src/main/java/com/example/frontend/utilities/PeriodicTask.kt
@@ -0,0 +1,28 @@
+package com.example.frontend.utilities
+
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+
+/*
+ * Run a periodic task
+ */
+class PeriodicTask(
+ private val command: Runnable,
+ private val intervalInMinutes: Long = 5,
+) {
+
+ fun execute() {
+ Log.i("PeriodicTask", "execute")
+
+ val service = Executors.newSingleThreadScheduledExecutor()
+ val handler = Handler(Looper.getMainLooper())
+ service.scheduleAtFixedRate(
+ handler.run { command },
+ 0, intervalInMinutes, TimeUnit.MINUTES
+ )
+ }
+}
+
diff --git a/frontend/app/src/main/res/drawable/map_pin_svgrepo_com.xml b/frontend/app/src/main/res/drawable/map_pin_svgrepo_com.xml
new file mode 100644
index 00000000..90f1f34d
--- /dev/null
+++ b/frontend/app/src/main/res/drawable/map_pin_svgrepo_com.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/frontend/app/src/main/res/drawable/white_circle.xml b/frontend/app/src/main/res/drawable/white_circle.xml
new file mode 100644
index 00000000..522eb166
--- /dev/null
+++ b/frontend/app/src/main/res/drawable/white_circle.xml
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/frontend/settings.gradle.kts b/frontend/settings.gradle.kts
index 08a0d6ae..9cee02fb 100644
--- a/frontend/settings.gradle.kts
+++ b/frontend/settings.gradle.kts
@@ -15,4 +15,3 @@ dependencyResolutionManagement {
rootProject.name = "frontend"
include(":app")
-
\ No newline at end of file