diff --git a/app/build.gradle b/app/build.gradle index bf461b0..40bcdf0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,7 +3,7 @@ plugins { id 'org.jetbrains.kotlin.android' id 'com.google.gms.google-services' // Google Services Gradle Plugin id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' // Secrets Gradle Plugin - id 'androidx.navigation.safeargs.kotlin' // Navigation Safe Args + id "androidx.navigation.safeargs" } android { @@ -11,7 +11,7 @@ android { defaultConfig { applicationId "jp.ac.okinawa_ct.nitoc_ict.aroa" - minSdk 31 + minSdk 26 targetSdk 32 versionCode 1 versionName "1.0" @@ -42,8 +42,6 @@ dependencies { // 基本的なライブラリ implementation 'androidx.core:core-ktx:1.8.0' implementation 'androidx.appcompat:appcompat:1.5.0' - implementation 'androidx.activity:activity-ktx:1.5.1' - implementation 'androidx.fragment:fragment-ktx:1.5.2' // レイアウト系 implementation 'com.google.android.material:material:1.6.1' @@ -52,16 +50,11 @@ dependencies { // ライフサイクル対応コンポーネント implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1' - implementation 'androidx.legacy:legacy-support-v4:1.0.0' // Navigation - def nav_version = "2.5.2" - implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" - implementation "androidx.navigation:navigation-ui-ktx:$nav_version" - implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version" - - // Preferences DataStore - implementation "androidx.datastore:datastore-preferences:1.0.0" + implementation 'androidx.navigation:navigation-fragment-ktx:2.5.1' + implementation 'androidx.navigation:navigation-ui-ktx:2.5.1' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' @@ -69,10 +62,18 @@ dependencies { // Google Maps SDK implementation 'com.google.android.gms:play-services-maps:18.1.0' + implementation "com.google.maps:google-maps-services:0.2.11" + implementation 'com.google.maps.android:android-maps-utils:2.2.0' + + //位置情報関係 + implementation 'com.google.android.gms:play-services-location:16.0.0' +// implementation 'com.google.android.libraries.places:places:1.1.0' // Firebase implementation platform('com.google.firebase:firebase-bom:30.2.0') // Firebase BoM implementation 'com.google.firebase:firebase-analytics-ktx' // Firebase Analytics implementation 'com.google.firebase:firebase-firestore-ktx' // Firebase FireStore + implementation "androidx.datastore:datastore-preferences:1.0.0" + } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 58135cd..99a7b8d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,13 +3,8 @@ xmlns:tools="http://schemas.android.com/tools" package="jp.ac.okinawa_ct.nitoc_ict.aroa"> - - - - - + + + tools:targetApi="31"> + + android:exported="true" + android:screenOrientation="portrait"> - - - + + - - - - - - \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/data/dto/Record.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/data/dto/Record.kt index 7c47530..9eecea6 100644 --- a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/data/dto/Record.kt +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/data/dto/Record.kt @@ -1,5 +1,7 @@ package jp.ac.okinawa_ct.nitoc_ict.aroa.data.dto +import java.util.* + /** * トライアルの記録を表現するクラス * @@ -9,7 +11,10 @@ package jp.ac.okinawa_ct.nitoc_ict.aroa.data.dto sealed class Record { abstract val userId: String abstract val trialId: String + abstract val trialName: String + abstract val date: String abstract val rank: Int + abstract val recordId: String /** * マラソンのトライアルの記録を表現するクラス @@ -19,8 +24,12 @@ sealed class Record { data class MarathonRecord( override val userId: String, override val trialId: String, + override val trialName: String, + override val date: String, + val distance: Long, val time: Long, override val rank: Int = -1, + override val recordId: String = UUID.randomUUID().toString(), ): Record() /** @@ -31,7 +40,10 @@ sealed class Record { data class DanglingRecord( override val userId: String, override val trialId: String, + override val trialName: String, + override val date: String, val time: Long, override val rank: Int = -1, + override val recordId: String, ): Record() } diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/data/dto/Trial.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/data/dto/Trial.kt index 9882019..b14f23e 100644 --- a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/data/dto/Trial.kt +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/data/dto/Trial.kt @@ -15,6 +15,7 @@ sealed class Trial { abstract val name: String abstract val authorUserId: String abstract val position: LatLng + abstract val createDate: String abstract val id: String /** @@ -27,6 +28,7 @@ sealed class Trial { override val authorUserId: String, override val position: LatLng, val course: List, + override val createDate: String, override val id: String = UUID.randomUUID().toString(), ) : Trial() @@ -40,6 +42,7 @@ sealed class Trial { override val authorUserId: String, override val position: LatLng, val goalCount: Int, + override val createDate: String, override val id: String = UUID.randomUUID().toString(), ) : Trial() } diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/data/repository/RecordRepository.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/data/repository/RecordRepository.kt index 3fb8b01..32032cc 100644 --- a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/data/repository/RecordRepository.kt +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/data/repository/RecordRepository.kt @@ -26,7 +26,7 @@ interface RecordRepository { * @param startRank 何位から取得するか. 1以上である必要がある。 * @param maxResult startRankで指定された順位から最大でいくつの[Record]を取得するか */ - fun getRecords(trial: Trial, startRank: Int, maxResult: Int): Flow>> + fun getRecords(trialId: String, startRank: Int, maxResult: Int): Flow>> /** * 指定された[Trial]のランキングに[Record]を追加する.すでに同じユーザーの記録がある場合は上書きする @@ -37,4 +37,8 @@ interface RecordRepository { * @return データベースへの送信委成功した時は実際のランキングが保存された[Record] */ fun createOrUpdateRecord(trial: Trial, record: Record): Flow> + + fun getMyRecords(userId: String): Flow>> + + fun getRecordByTrialIdAndRecordId(trialId: String, recordId: String): Flow> } \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/data/repository/RecordRepositoryDummy.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/data/repository/RecordRepositoryDummy.kt new file mode 100644 index 0000000..ffbdc87 --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/data/repository/RecordRepositoryDummy.kt @@ -0,0 +1,141 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.data.repository + +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.dto.Record +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.dto.Result +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.dto.Trial +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.* + +class RecordRepositoryDummy: RecordRepository { + companion object { + private val trial1RecordList = mutableListOf( + Record.MarathonRecord( + "サイス", "testTrialID_1","沖縄高専外周コース1","2022年10月15日",487,140,2,"testRecordId1" + ), + Record.MarathonRecord( + "知念","testTrialID_1","沖縄高専外周コース1","2022年10月14日",487,150,3,"testRecordId2" + ), + Record.MarathonRecord( + "ひじかた","testTrialID_1","沖縄高専外周コース1","2022年10月13日",487,110,1,"testRecordId3" + ), + ) + private val trial2RecordList = mutableListOf( + Record.MarathonRecord( + "サイス", "testTrialID_2","沖縄高専外周コース2","2022年10月15日",975,310,2,"testRecordId1" + ), + Record.MarathonRecord( + "知念","testTrialID_2","沖縄高専外周コース2","2022年10月14日",975,340,3,"testRecordId2" + ), + Record.MarathonRecord( + "ひじかた","testTrialID_2","沖縄高専外周コース2","2022年10月13日",975,250,1,"testRecordId3" + ), + ) + private val trial3RecordList = mutableListOf( + Record.MarathonRecord( + "サイス", "testTrialID_3","沖縄高専外周コース3","2022年10月15日",616,210,2,"testRecordId1" + ), + Record.MarathonRecord( + "知念","testTrialID_3","沖縄高専外周コース3","2022年10月14日",616,290,3,"testRecordId2" + ), + Record.MarathonRecord( + "ひじかた","testTrialID_3","沖縄高専外周コース3","2022年10月13日",616,180,1,"testRecordId3" + ), + ) + + private val myRecordList = mutableListOf( + Record.MarathonRecord( + "ひじかた","testTrialID_1","沖縄高専外周コース1","2022年10月13日",487,110,1,"testRecordId1" + ), + Record.MarathonRecord( + "ひじかた","testTrialID_2","沖縄高専外周コース2","2022年10月13日",975,250,1,"testRecordId2" + ), + Record.MarathonRecord( + "ひじかた","testTrialID_3","沖縄高専外周コース3","2022年10月13日",616,180,1,"testRecordId3" + ), + ) + } + + + override fun getRecordByUserId(trial: Trial, userId: String): Flow> { + TODO("Not yet implemented") + } + + override fun getRecords( + trialId: String, + startRank: Int, + maxResult: Int + ): Flow>> = + flow>> { + delay(1000) + val comparator : Comparator = compareBy { it.rank } + when(trialId) { + trial1RecordList.get(0).trialId -> emit(Result.Success(trial1RecordList.sortedWith(comparator))) + trial2RecordList.get(0).trialId -> emit(Result.Success(trial2RecordList.sortedWith(comparator))) + trial3RecordList.get(0).trialId -> emit(Result.Success(trial3RecordList.sortedWith(comparator))) + } + }.catch { + when(it) { + is Exception -> { + emit(Result.Error(true, it)) + } + } + }.onStart { + emit(Result.Loading) + }.flowOn(Dispatchers.IO) + + override fun createOrUpdateRecord(trial: Trial, record: Record): Flow> { + TODO("Not yet implemented") + } + + override fun getMyRecords(userId: String): Flow>> = + flow>> { + delay(1000) + emit(Result.Success(myRecordList)) + }.catch { + when(it) { + is Exception -> { + emit(Result.Error(true, it)) + } + } + }.onStart { + emit(Result.Loading) + }.flowOn(Dispatchers.IO) + + override fun getRecordByTrialIdAndRecordId(trialId: String, recordId: String): Flow> = + flow> { + delay(1000) + when(trialId) { + trial1RecordList.get(0).trialId -> { + for (data in trial1RecordList) { + if (data.recordId == recordId) { + emit(Result.Success(data)) + } + } + } + trial2RecordList.get(0).trialId -> { + for (data in trial2RecordList) { + if (data.recordId == recordId) { + emit(Result.Success(data)) + } + } + } + trial3RecordList.get(0).trialId -> { + for (data in trial3RecordList) { + if (data.recordId == recordId) { + emit(Result.Success(data)) + } + } + } + } + }.catch { + when(it) { + is Exception -> { + emit(Result.Error(true, it)) + } + } + }.onStart { + emit(Result.Loading) + }.flowOn(Dispatchers.IO) +} + diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/data/repository/TrialRepositoryDummy.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/data/repository/TrialRepositoryDummy.kt index b0baa28..e526fea 100644 --- a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/data/repository/TrialRepositoryDummy.kt +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/data/repository/TrialRepositoryDummy.kt @@ -13,55 +13,63 @@ import kotlinx.coroutines.flow.* */ class TrialRepositoryDummy : TrialRepository { - private val testDataList = mutableListOf( - Trial.Marathon( - "テストマラソントライアル_1", - "testUserID", - LatLng(26.526230, 128.030372), // 沖縄高専 - listOf( - LatLng(26.526230, 128.030372), - LatLng(26.526227, 128.030645), - LatLng(26.526424, 128.030645), - LatLng(26.526424, 128.030372), - LatLng(26.526230, 128.030372), - ), // 沖縄高専の敷地内を回るコース - "testTrialID_1", - ), - Trial.Marathon( - "テストマラソントライアル_2", - "testUserID", - LatLng(26.526347, 128.029209), // 沖縄高専 - listOf( - LatLng(26.526347, 128.029209), - LatLng(26.526230, 128.030372), - LatLng(26.526227, 128.030645), - LatLng(26.526424, 128.030645), - LatLng(26.526424, 128.030372), - LatLng(26.526347, 128.029209), - ), // 沖縄高専の敷地内を回るコース - "testTrialID_2", - ), - Trial.Marathon( - "テストマラソントライアル_3", - "testUserID", - LatLng(26.525823, 128.031147), // 沖縄高専 - listOf( - LatLng(26.525823, 128.031147), - LatLng(26.526230, 128.030372), - LatLng(26.526227, 128.030645), - LatLng(26.526424, 128.030645), - LatLng(26.526424, 128.030372), - LatLng(26.525823, 128.031147), - ), // 沖縄高専の敷地内を回るコース - "testTrialID_3", - ), - + companion object { + private val testDataList = mutableListOf( + Trial.Marathon( + "沖縄高専外周コース1", + "ひじかた", + LatLng(26.526230, 128.030372), // 沖縄高専 + listOf( + LatLng(26.526230, 128.030372), + LatLng(26.526227, 128.030645), + LatLng(26.526424, 128.030645), + LatLng(26.526424, 128.030372), + LatLng(26.526230, 128.030372), + ), // 沖縄高専の敷地内を回るコース + "2022月10月15日", + "testTrialID_1" + ), + Trial.Marathon( + "沖縄高専外周コース2", + "ひじかた", + LatLng(26.526347, 128.029209), // 沖縄高専 + listOf( + LatLng(26.526347, 128.029209), + LatLng(26.526230, 128.030372), + LatLng(26.526227, 128.030645), + LatLng(26.526424, 128.030645), + LatLng(26.526424, 128.030372), + LatLng(26.526347, 128.029209), + ), // 沖縄高専の敷地内を回るコース + "2022月10月15日", + "testTrialID_2", + ), + Trial.Marathon( + "沖縄高専外周コース3", + "ひじかた", + LatLng(26.525823, 128.031147), // 沖縄高専 + listOf( + LatLng(26.525823, 128.031147), + LatLng(26.526230, 128.030372), + LatLng(26.526227, 128.030645), + LatLng(26.526424, 128.030645), + LatLng(26.526424, 128.030372), + LatLng(26.525823, 128.031147), + ), // 沖縄高専の敷地内を回るコース + "2022月10月15日", + "testTrialID_3", + ), ) + } override fun getTrialById(id: String): Flow> = flow> { delay(1000) // ネットワーク処理の遅延の際限の為、1000ms 待つ - emit(Result.Success(testDataList[0])) + for (data in testDataList) { + if (data.id == id) { + emit(Result.Success(data)) + } + } Log.d("TrialRepositoryDummy", testDataList[0].toString()) }.catch { // 本来はここでエラー処理をする @@ -79,7 +87,7 @@ class TrialRepositoryDummy : TrialRepository { flow>> { delay(1000) // ネットワーク処理の遅延の際限の為、1000ms 待つ emit(Result.Success(testDataList)) - Log.d("TrialRepositoryDummy", testDataList.toString()) + Log.d("TrialRepositoryDummy", "AddTrialFragment:${testDataList}") }.catch { // 本来はここでエラー処理をする when (it) { @@ -99,9 +107,9 @@ class TrialRepositoryDummy : TrialRepository { override fun createTrial(trial: Trial): Flow> = flow> { testDataList.add(trial) - delay(500) + delay(50) emit(Result.Success(trial)) - Log.d("TrialRepositoryDummy", testDataList.toString()) + Log.d("TrialRepositoryDummy", "createTrial:${testDataList}") }.onStart { emit(Result.Loading) Log.d("TrialRepositoryDummy", "Updating...") diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/MainActivity.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/MainActivity.kt index 3f871ea..5a7bf20 100644 --- a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/MainActivity.kt +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/MainActivity.kt @@ -3,7 +3,6 @@ package jp.ac.okinawa_ct.nitoc_ict.aroa.ui import android.os.Bundle import com.google.android.material.bottomnavigation.BottomNavigationView import androidx.appcompat.app.AppCompatActivity -import androidx.navigation.findNavController import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.setupActionBarWithNavController @@ -30,7 +29,7 @@ class MainActivity : AppCompatActivity() { // menu should be considered as top level destinations. val appBarConfiguration = AppBarConfiguration( setOf( - R.id.navigation_home, R.id.navigation_add_trial, R.id.navigation_check_record + R.id.navigation_home, R.id.navigation_create_trial, R.id.navigation_check_record ) ) setupActionBarWithNavController(navController, appBarConfiguration) diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/AddTrialFragment.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/AddTrialFragment.kt deleted file mode 100644 index f116bcd..0000000 --- a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/AddTrialFragment.kt +++ /dev/null @@ -1,42 +0,0 @@ -package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.add_trial - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import androidx.fragment.app.Fragment -import androidx.lifecycle.ViewModelProvider -import jp.ac.okinawa_ct.nitoc_ict.aroa.databinding.FragmentAddTrialBinding - -class AddTrialFragment : Fragment() { - - private var _binding: FragmentAddTrialBinding? = null - - // This property is only valid between onCreateView and - // onDestroyView. - private val binding get() = _binding!! - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - val dashboardViewModel = - ViewModelProvider(this).get(AddTrialViewModel::class.java) - - _binding = FragmentAddTrialBinding.inflate(inflater, container, false) - val root: View = binding.root - - val textView: TextView = binding.textAddTrial - dashboardViewModel.text.observe(viewLifecycleOwner) { - textView.text = it - } - return root - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } -} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/AddTrialViewModel.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/AddTrialViewModel.kt deleted file mode 100644 index 9bee1e0..0000000 --- a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/AddTrialViewModel.kt +++ /dev/null @@ -1,13 +0,0 @@ -package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.add_trial - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel - -class AddTrialViewModel : ViewModel() { - - private val _text = MutableLiveData().apply { - value = "This is addTrial Fragment" - } - val text: LiveData = _text -} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/CreatedTrialAdapter.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/CreatedTrialAdapter.kt new file mode 100644 index 0000000..b1f5724 --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/CreatedTrialAdapter.kt @@ -0,0 +1,58 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.add_trial + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.dto.Trial +import jp.ac.okinawa_ct.nitoc_ict.aroa.databinding.ListItemTrialBinding + +typealias OnItemClickListener = (view: View, position: Int) -> Unit + +class CreatedTrialAdapter( + context: Context +) : ListAdapter(ITEM_CALLBACK) { + private val inflater = LayoutInflater.from(context) + + private var onItemClickListener: OnItemClickListener? = null + + class BindingHolder( + val binding: ListItemTrialBinding + ) : RecyclerView.ViewHolder(binding.root) + companion object { + val ITEM_CALLBACK = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: Trial, + newItem: Trial + ): Boolean = oldItem.id == newItem.id + + override fun areContentsTheSame( + oldItem: Trial, + newItem: Trial + ): Boolean = oldItem == newItem + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder { + + val binding = ListItemTrialBinding.inflate(inflater, parent, false) + return BindingHolder(binding) + } + + override fun onBindViewHolder(bindingHolder: BindingHolder, position: Int) { + val current = getItem(position) + bindingHolder.binding.trialName.text = current.name + bindingHolder.binding.trialMakeDate.text = current.createDate + bindingHolder.binding.itemViewGroup.setOnClickListener { + onItemClickListener?.invoke(it,position) + } + } + + fun setOnItemClickListener(onItemClickListener: OnItemClickListener) { + this.onItemClickListener = onItemClickListener + } +} + diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/DirectionsApiHelper.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/DirectionsApiHelper.kt new file mode 100644 index 0000000..11f6700 --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/DirectionsApiHelper.kt @@ -0,0 +1,68 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.add_trial + +import android.util.Log +import androidx.annotation.Nullable +import com.google.maps.DirectionsApi +import com.google.maps.GeoApiContext +import com.google.maps.model.DirectionsResult +import com.google.maps.model.TravelMode +import com.google.maps.model.Unit +import jp.ac.okinawa_ct.nitoc_ict.aroa.BuildConfig +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.util.* +import com.google.maps.model.LatLng as MapsLatLng + +class DirectionsApiHelper { + /** + * 経路APIを実行する. + * + * @param context コンテキスト + * @param origin 出発地点 + * @param destination 到着地点 + * @return 取得成功: [com.google.maps.model.DirectionsResult] 失敗: null + */ + @Nullable + suspend fun execute(origin: MapsLatLng?, destination: MapsLatLng?, waypoints: String): DirectionsResult? { + return withContext(Dispatchers.IO) { + // Mapキーの取得. + val apiContext = GeoApiContext.Builder() + .apiKey(BuildConfig.MAPS_API_KEY).build() + + // API実行. + kotlin.runCatching { + DirectionsApi + .newRequest(apiContext) + .mode(TravelMode.WALKING) + .units(Unit.METRIC) + .language(Locale.JAPAN.language) + .origin(origin) + .destination(destination) + .waypoints(waypoints) + .await() + }.getOrNull() + } + } + + @Nullable + suspend fun onlyOriginDestExecute(origin: MapsLatLng?, destination: MapsLatLng?): DirectionsResult? { + return withContext(Dispatchers.IO) { + // Mapキーの取得. + val apiContext = GeoApiContext.Builder() + .apiKey(BuildConfig.MAPS_API_KEY).build() + + Log.i("onlyOriginDestExecute","${origin.toString()},${destination.toString()}") + // API実行. + kotlin.runCatching { + DirectionsApi + .newRequest(apiContext) + .mode(TravelMode.WALKING) + .units(Unit.METRIC) + .language(Locale.JAPAN.language) + .origin(origin) + .destination(destination) + .await() + }.getOrNull() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/dest/AddTrialDestFragment.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/dest/AddTrialDestFragment.kt new file mode 100644 index 0000000..70916d4 --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/dest/AddTrialDestFragment.kt @@ -0,0 +1,208 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.add_trial.dest + +import android.app.AlertDialog +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider +import androidx.navigation.fragment.findNavController +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.OnMapReadyCallback +import com.google.android.gms.maps.SupportMapFragment +import com.google.android.gms.maps.model.* +import com.google.maps.android.PolyUtil +import com.google.maps.model.DirectionsResult +import jp.ac.okinawa_ct.nitoc_ict.aroa.R +import jp.ac.okinawa_ct.nitoc_ict.aroa.databinding.FragmentAddTrialDestBinding +import jp.ac.okinawa_ct.nitoc_ict.aroa.util.ConverterVectorToBitmap +import java.util.* + +class AddTrialDestFragment : Fragment() { + + companion object { + private const val ZOOM_SIZE = 14f + private const val POLYLINE_WIDTH = 12f + } + + private var map: GoogleMap? = null + private lateinit var viewModel: AddTrialDestViewModel + private var polyline: Polyline? = null + + private lateinit var _binding: FragmentAddTrialDestBinding + + private val binding get() = _binding + + private val callback = OnMapReadyCallback { googleMap -> + map = googleMap + moveCamera() + setMapLongClick(googleMap) + setOnMarkerDrag(googleMap) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + _binding = FragmentAddTrialDestBinding.inflate(inflater, container, false) + val args = AddTrialDestFragmentArgs.fromBundle( + requireArguments() + ) + viewModel = ViewModelProvider(this).get(AddTrialDestViewModel::class.java) + viewModel.setOrigin(args.originLatLng) + binding.nextButton.setOnClickListener { + if (viewModel.dest.value != null) { + viewModel.navStart() + } + } + + val builder = AlertDialog.Builder(requireContext()) + builder.setTitle("トライアルを作成").setMessage("ゴール地点を設定") + .setPositiveButton("ok",null) + builder.show() + + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val mapFragment = childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment? + mapFragment?.getMapAsync(callback) + observeLiveData() + } + + private fun observeLiveData() { + viewModel.directionsResult.observe(viewLifecycleOwner, androidx.lifecycle.Observer{ + Log.i("DestFragment", "directionsResult:${it.toString()}") + updatePolyline(it, map) + }) + + viewModel.navFrag.observe(viewLifecycleOwner, androidx.lifecycle.Observer { + if (it) { + val action = + AddTrialDestFragmentDirections.actionNavigationAddTrialDestToNavigationAddTrialMaps( + LatLng( + viewModel.origin.value!!.latitude, + viewModel.origin.value!!.longitude + ), + LatLng(viewModel.dest.value!!.latitude, viewModel.dest.value!!.longitude) + ) + this.findNavController().navigate(action) + viewModel.navCompleted() + } + }) + } + + //カメラを移動 + private fun moveCamera() { + // Add a marker in Sydney and move the camera + val origin = LatLng(viewModel.origin.value!!.latitude, viewModel.origin.value!!.longitude) + map?.apply { + addMarker(MarkerOptions() + .position(origin) + .title("Marker in Origin") + .icon(BitmapDescriptorFactory.fromBitmap( + ConverterVectorToBitmap().getBitmapFromVectorDrawable( + requireContext(),R.drawable.ic_baseline_flag_circle_origin_36))) + .anchor(0.5F,0.5F)) + moveCamera(CameraUpdateFactory.newLatLngZoom(origin, ZOOM_SIZE)) + } + } + + //マップをロングクリック時にマーカーを追加 + private fun setMapLongClick(map: GoogleMap) { + map.setOnMapLongClickListener{latLng -> + val snippet = String.format( + Locale.getDefault(), + "Lat: %1$.5f, Long: %2$.5f", + latLng.latitude, + latLng.longitude + ) + //マップをクリア、originのマーカーを追加 + map.clear() + + map.addMarker( + MarkerOptions() + .position( + LatLng( + viewModel.origin.value!!.latitude, + viewModel.origin.value!!.longitude)) + .title("Marker in Origin") + .icon(BitmapDescriptorFactory.fromBitmap( + ConverterVectorToBitmap().getBitmapFromVectorDrawable( + requireContext(),R.drawable.ic_baseline_flag_circle_origin_36))) + .anchor(0.5F,0.5F)) + + val marker = map.addMarker( + MarkerOptions() + .position(latLng) + .title("Marker in Dest") + .snippet(snippet) + .draggable(true) + .icon(BitmapDescriptorFactory.fromBitmap( + ConverterVectorToBitmap().getBitmapFromVectorDrawable( + requireContext(),R.drawable.ic_baseline_flag_circle_dest_36))) + .anchor(0.5F,0.5F) + ) + + if (marker != null) { + viewModel.setDest(marker.position) + } + } + } + + + //マーカーをドラッグ時に、マーカーのLatLngを更新 + private fun setOnMarkerDrag(map: GoogleMap) { + map.setOnMarkerDragListener(object : GoogleMap.OnMarkerDragListener { + private var start: com.google.maps.model.LatLng? = null + private var end: com.google.maps.model.LatLng? = null + + override fun onMarkerDragStart(marker: Marker) { + marker.position.let { start = + com.google.maps.model.LatLng(it.latitude, it.longitude) + } + } + + override fun onMarkerDrag(marker: Marker) { + // Do Nothing. + } + + override fun onMarkerDragEnd(marker: Marker) { + marker.position.let { end = + com.google.maps.model.LatLng(it.latitude, it.longitude) + } + viewModel.setDest(marker.position) + } + }) + } + + //Polylineを更新 + private fun updatePolyline(directionsResult: DirectionsResult?, googleMap: GoogleMap?) { + googleMap ?: return + directionsResult ?: return + removePolyline() + addPolyline(directionsResult, googleMap) + } + + // 線を消す. + private fun removePolyline() { + if (map != null && polyline != null) { + polyline?.remove() + } + } + + // 線を引く + private fun addPolyline(directionsResult: DirectionsResult, map: GoogleMap) { + val polylineOptions = PolylineOptions() + polylineOptions.width(POLYLINE_WIDTH) + // ARGB32bit形式. + polylineOptions.color(R.color.map_polyline_stroke) + val decodedPath = PolyUtil.decode(directionsResult.routes[0].overviewPolyline.encodedPath) + polyline = map.addPolyline(polylineOptions.addAll(decodedPath)) + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/dest/AddTrialDestViewModel.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/dest/AddTrialDestViewModel.kt new file mode 100644 index 0000000..670ae21 --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/dest/AddTrialDestViewModel.kt @@ -0,0 +1,62 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.add_trial.dest + +import android.app.Application +import android.util.Log +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.google.android.gms.maps.model.LatLng +import com.google.maps.model.DirectionsResult +import jp.ac.okinawa_ct.nitoc_ict.aroa.ui.add_trial.DirectionsApiHelper +import kotlinx.coroutines.launch + +class AddTrialDestViewModel(application: Application) : AndroidViewModel(application) { + private val _directionsResult = MutableLiveData() + val directionsResult: LiveData = _directionsResult + + private val _origin = MutableLiveData() + val origin: LiveData get() = _origin + + private val _dest = MutableLiveData() + val dest: LiveData get() = _dest + + private val _navFrag = MutableLiveData() + val navFrag: LiveData get() = _navFrag + + fun setOrigin(latLng: LatLng) { + _origin.value = latLng + } + + fun setDest(latLng: LatLng) { + Log.i("DestViewModel", "setDest:${latLng.toString()}") + _dest.value = latLng + directionApiExecute() + } + + fun removeDest() { + if (_dest != null) { +// _dest.value = null + } + } + + fun navStart() { + _navFrag.value = true + } + + fun navCompleted() { + _navFrag.value = false + } + + //DirectionAPIを実行 + fun directionApiExecute() { + viewModelScope.launch { + val result = DirectionsApiHelper().onlyOriginDestExecute( + com.google.maps.model.LatLng(_origin.value!!.latitude, _origin.value!!.longitude), + com.google.maps.model.LatLng(_dest.value!!.latitude, _dest.value!!.longitude) + ) + Log.i("DestViewModel", "result:${result.toString()}") + _directionsResult.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/origin/AddTrialOriginFragment.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/origin/AddTrialOriginFragment.kt new file mode 100644 index 0000000..894c1d5 --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/origin/AddTrialOriginFragment.kt @@ -0,0 +1,197 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.add_trial.origin + +import android.app.AlertDialog +import android.content.DialogInterface +import android.content.pm.PackageManager +import android.location.Location +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider +import androidx.navigation.fragment.findNavController +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationServices +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.OnMapReadyCallback +import com.google.android.gms.maps.SupportMapFragment +import com.google.android.gms.maps.model.BitmapDescriptorFactory +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.Marker +import com.google.android.gms.maps.model.MarkerOptions +import jp.ac.okinawa_ct.nitoc_ict.aroa.R +import jp.ac.okinawa_ct.nitoc_ict.aroa.databinding.FragmentAddTrialOriginBinding +import jp.ac.okinawa_ct.nitoc_ict.aroa.util.ConverterVectorToBitmap +import java.util.* + +class AddTrialOriginFragment : Fragment() { + + companion object { + private const val ZOOM_SIZE = 14f + } + private val REQUEST_LOCATION_PERMISSION = 1 + + private var map: GoogleMap? = null + private lateinit var viewModel: AddTrialOriginViewModel + + private lateinit var _binding: FragmentAddTrialOriginBinding + private val binding get() = _binding + + private lateinit var fusedLocationClient: FusedLocationProviderClient + private var currentPosition: LatLng? = null + + private val callback = OnMapReadyCallback { googleMap -> + map = googleMap + enableMyLocation() + setMapLongClick(googleMap) + setOnMarkerDrag(googleMap) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + _binding = FragmentAddTrialOriginBinding.inflate(inflater, container, false) + viewModel = ViewModelProvider(this).get(AddTrialOriginViewModel::class.java) + binding.nextButton.setOnClickListener { + if (viewModel.origin.value != null) { + viewModel.navStart() + } + } + + val builder = AlertDialog.Builder(requireContext()) + builder.setTitle("トライアルを作成").setMessage("スタート地点を設定") + .setPositiveButton("ok",null) + builder.show() + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val mapFragment = childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment? + mapFragment?.getMapAsync(callback) + observeLiveData() + } + + private fun observeLiveData() { + viewModel.navFrag.observe(viewLifecycleOwner, androidx.lifecycle.Observer { + if (it == true) { + Log.i("originValue", viewModel.origin.value.toString()) + val action = + AddTrialOriginFragmentDirections.actionNavigationAddTrialOriginToNavigationAddTrialDest( + LatLng( + viewModel.origin.value!!.latitude, + viewModel.origin.value!!.longitude + ) + ) + this.findNavController().navigate(action) + viewModel.navCompleted() + } + }) + } + + private fun isPermissionGranted() : Boolean { + return ContextCompat.checkSelfPermission( + requireContext(), + android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED + } + + private fun enableMyLocation() { + if (isPermissionGranted()) { + map!!.isMyLocationEnabled = true + + fusedLocationClient = LocationServices.getFusedLocationProviderClient(requireActivity()) + fusedLocationClient.lastLocation + .addOnSuccessListener { location : Location? -> + currentPosition = LatLng(location!!.latitude,location.longitude) + Log.i("HomeFragment","FusedLocationClient:${location.latitude.toString()}") + map?.apply { + Log.i("HomeFragment","currentPosition:${currentPosition.toString()}") + moveCamera(CameraUpdateFactory.newLatLngZoom( + currentPosition!!, 14f + )) +// LatLng(26.40005132585051, 127.74633288655508) + } + } + } + else { + ActivityCompat.requestPermissions( + this.requireActivity(), + arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION), + REQUEST_LOCATION_PERMISSION + ) + } + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (requestCode == REQUEST_LOCATION_PERMISSION) { + if (grantResults.contains(PackageManager.PERMISSION_GRANTED)) { + enableMyLocation() + } + } + } + + //マップをロングクリック時にマーカーを追加 + private fun setMapLongClick(map: GoogleMap) { + map.setOnMapLongClickListener{latLng -> + val snippet = String.format( + Locale.getDefault(), + "Lat: %1$.5f, Long: %2$.5f", + latLng.latitude, + latLng.longitude + ) + //マップをクリア + map.clear() + + val marker = map.addMarker( + MarkerOptions() + .position(latLng) + .title("Marker in Dest") + .snippet(snippet) + .draggable(true) + .icon(BitmapDescriptorFactory.fromBitmap(ConverterVectorToBitmap().getBitmapFromVectorDrawable( + requireContext(),R.drawable.ic_baseline_flag_circle_origin_36))) + .anchor(0.5F,0.5F) + ) + + if (marker != null) { + viewModel.setOrigin(marker.position) + } + } + } + + //マーカーをドラッグ時に、マーカーのLatLngを更新 + private fun setOnMarkerDrag(map: GoogleMap) { + map.setOnMarkerDragListener(object : GoogleMap.OnMarkerDragListener { + private var start: com.google.maps.model.LatLng? = null + private var end: com.google.maps.model.LatLng? = null + + override fun onMarkerDragStart(marker: Marker) { + marker.position.let { start = + com.google.maps.model.LatLng(it.latitude, it.longitude) + } + } + + override fun onMarkerDrag(marker: Marker) { + // Do Nothing. + } + + override fun onMarkerDragEnd(marker: Marker) { + marker.position.let { end = + com.google.maps.model.LatLng(it.latitude, it.longitude) + } + viewModel.setOrigin(marker.position) + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/origin/AddTrialOriginViewModel.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/origin/AddTrialOriginViewModel.kt new file mode 100644 index 0000000..af518fb --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/origin/AddTrialOriginViewModel.kt @@ -0,0 +1,33 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.add_trial.origin + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.google.android.gms.maps.model.LatLng + +class AddTrialOriginViewModel(application: Application) : AndroidViewModel(application) { + private val _origin = MutableLiveData() + val origin: LiveData get() = _origin + + private val _navFrag = MutableLiveData() + val navFrag: LiveData get() = _navFrag + + fun setOrigin(latLng: LatLng) { + _origin.value = latLng + } + + fun removeOrigin() { + if (_origin != null) { +// _origin.value = null + } + } + + fun navStart() { + _navFrag.value = true + } + + fun navCompleted() { + _navFrag.value = false + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/start/AddTrialFragment.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/start/AddTrialFragment.kt new file mode 100644 index 0000000..a008764 --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/start/AddTrialFragment.kt @@ -0,0 +1,66 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.add_trial.start + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import jp.ac.okinawa_ct.nitoc_ict.aroa.databinding.FragmentAddTrialBinding +import jp.ac.okinawa_ct.nitoc_ict.aroa.ui.add_trial.CreatedTrialAdapter + +class AddTrialFragment : Fragment() { + + private lateinit var binding: FragmentAddTrialBinding + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val viewModel = + ViewModelProvider(this).get(AddTrialViewModel::class.java) + binding = FragmentAddTrialBinding.inflate(layoutInflater) + + viewModel.getTrialList("testUserID") + + val adapter = CreatedTrialAdapter(binding.createdTrialList.context) + binding.createdTrialList.adapter = adapter + + val dividerItemDecoration = DividerItemDecoration( + requireContext(), LinearLayoutManager(requireContext()).getOrientation()) + binding.createdTrialList.addItemDecoration(dividerItemDecoration) + + //RecyclerViewのclickListener + adapter.setOnItemClickListener { view, position -> + val action = AddTrialFragmentDirections.actionNavigationCreateTrialToTrialDetailFragment( + viewModel.testData.value!!.get(position).id,false + ) + this.findNavController().navigate(action) + } + + viewModel.testData.observe(viewLifecycleOwner, Observer{ + it?.let { + adapter.submitList(it) + } + }) + + //トライアルの作成開始 + viewModel.navFrag.observe(viewLifecycleOwner, Observer { + if (it == true) { + this.findNavController().navigate( + AddTrialFragmentDirections.actionNavigationAddTrialToNavigationAddTrialOrigin() + ) + viewModel.navCompleted() + } + }) + + binding.startButton.setOnClickListener { viewModel.navStart() } + + return binding.root + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/start/AddTrialViewModel.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/start/AddTrialViewModel.kt new file mode 100644 index 0000000..965b4dd --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/start/AddTrialViewModel.kt @@ -0,0 +1,49 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.add_trial.start + +import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.dto.Result +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.dto.Trial +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.repository.TrialRepositoryDummy +import kotlinx.coroutines.launch + +class AddTrialViewModel : ViewModel() { + private val trialRepositoryDummy = TrialRepositoryDummy() + + private val _testData : MutableLiveData> = MutableLiveData(listOf()) + val testData: LiveData> get() = _testData + + private val _navFrag = MutableLiveData() + val navFrag: LiveData get() = _navFrag + + private val _collectState = MutableLiveData() + val collectState: LiveData get() = _collectState + + fun navStart() { + _navFrag.value = true + } + + fun navCompleted() { + _navFrag.value = null + } + + //リポジトリからテストデータを持ってくる + fun getTrialList(userId: String) { + viewModelScope.launch { + trialRepositoryDummy.getTriedTrialByUserId(userId).collect{ + when(it) { + is Result.Loading -> _collectState.value = "Loading" + is Result.Success -> { + _collectState.value = "Success" + _testData.value = it.data!! + Log.i("getTrialList","testData:${it}") + } + is Result.Error -> _collectState.value = "Error" + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/waypoints/AddTrialMapsFragment.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/waypoints/AddTrialMapsFragment.kt new file mode 100644 index 0000000..e1e920c --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/waypoints/AddTrialMapsFragment.kt @@ -0,0 +1,247 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.add_trial.waypoints + +import android.app.AlertDialog +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.widget.AppCompatEditText +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.Observer +import androidx.navigation.fragment.findNavController +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.OnMapReadyCallback +import com.google.android.gms.maps.SupportMapFragment +import com.google.android.gms.maps.model.* +import com.google.maps.android.PolyUtil +import com.google.maps.model.DirectionsResult +import jp.ac.okinawa_ct.nitoc_ict.aroa.R +import jp.ac.okinawa_ct.nitoc_ict.aroa.databinding.FragmentAddTrialMapsBinding +import jp.ac.okinawa_ct.nitoc_ict.aroa.util.ConverterVectorToBitmap +import java.util.* + +class AddTrialMapsFragment : Fragment() { + companion object { + private const val ZOOM_SIZE = 14f + private const val POLYLINE_WIDTH = 12f + } + + private var map: GoogleMap? = null + private var polyline: Polyline? = null + private val overview = 0 + private lateinit var viewModel: AddTrialMapsViewModel + + private lateinit var _binding: FragmentAddTrialMapsBinding + + // This property is only valid between onCreateView and + // onDestroyView. + private val binding get() = _binding + + private val callback = OnMapReadyCallback { googleMap -> + map = googleMap + viewModel.directionApiExecute() + moveCamera() + setMapLongClick(googleMap) + setMarkerClick(googleMap) + setOnMarkerDrag(googleMap) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + _binding = FragmentAddTrialMapsBinding.inflate(inflater, container, false) + viewModel = ViewModelProvider(this).get(AddTrialMapsViewModel::class.java) + val args = AddTrialMapsFragmentArgs.fromBundle( + requireArguments() + ) + viewModel.setOrigin(args.originLatLng) + viewModel.setDest(args.destLatLng) + + val builder = AlertDialog.Builder(requireContext()) + builder.setTitle("トライアルを作成").setMessage("中継地点を設定") + .setPositiveButton("ok",null) + builder.show() + + val dialogLayout = LayoutInflater.from(requireContext()).inflate(R.layout.trial_name_edit_dialog, null) + val editText = dialogLayout.findViewById(R.id.editTextDialog) + + val dialog = AlertDialog.Builder(requireContext()) + .setTitle("トライアル名の設定") + .setMessage("トライアル名を入力してください。") + .setView(dialogLayout) + .setPositiveButton("OK") { dialog, _ -> + // OKボタンを押したときの処理 + viewModel.setTrialName(editText.text.toString()) + viewModel.createNewTrial() + dialog.dismiss() + } + .setNegativeButton("キャンセル") { dialog, _ -> + // キャンセルボタンを押したときの処理 + + dialog.dismiss() + } + .create() + + // AppCompatEditTextにTextChangedListenerをセット + editText.addTextChangedListener( object : TextWatcher { + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} + + override fun afterTextChanged(s: Editable?) { + // 1~32文字の時だけOKボタンを有効化する + dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = + !(s.isNullOrEmpty() || s.length > 16) + } + }) + + binding.saveButton.setOnClickListener { + dialog.show() + } + + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val mapFragment = childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment? + mapFragment?.getMapAsync(callback) + observeLiveData() + } + + private fun observeLiveData() { + viewModel.directionsResult.observe(viewLifecycleOwner, androidx.lifecycle.Observer{ + updatePolyline(it, map) + }) + + viewModel.navFrag.observe(viewLifecycleOwner, Observer { + if (it) { + val action = + AddTrialMapsFragmentDirections.actionNavigationAddTrialMapsToNavigationAddTrial() + this.findNavController().navigate(action) + viewModel.navCompleted() + } + }) + } + + //カメラを移動 + private fun moveCamera() { + // Add a marker in Sydney and move the camera + val origin = LatLng(viewModel.origin.value!!.latitude, viewModel.origin.value!!.longitude) + val dest = LatLng(viewModel.dest.value!!.latitude, viewModel.dest.value!!.longitude) + map?.apply { + addMarker(MarkerOptions().position(origin).title("Marker in Origin") + .icon(BitmapDescriptorFactory.fromBitmap( + ConverterVectorToBitmap().getBitmapFromVectorDrawable( + requireContext(),R.drawable.ic_baseline_flag_circle_origin_36))) + .anchor(0.5F,0.5F)) + addMarker(MarkerOptions().position(dest).title("Marker in Dest") + .icon(BitmapDescriptorFactory.fromBitmap( + ConverterVectorToBitmap().getBitmapFromVectorDrawable( + requireContext(),R.drawable.ic_baseline_flag_circle_dest_36))) + .anchor(0.5F,0.5F)) + moveCamera(CameraUpdateFactory.newLatLngZoom(origin, ZOOM_SIZE)) + } + } + + //マップをロングクリック時にマーカーを追加 + private fun setMapLongClick(map: GoogleMap) { + map.setOnMapLongClickListener{latLng -> + val snippet = String.format( + Locale.getDefault(), + "Lat: %1$.5f, Long: %2$.5f", + latLng.latitude, + latLng.longitude + ) + + val marker = map.addMarker( + MarkerOptions() + .position(latLng) + .title("drop") + .snippet(snippet) + .draggable(true) + .icon(BitmapDescriptorFactory.fromBitmap(ConverterVectorToBitmap().getBitmapFromVectorDrawable( + requireContext(),R.drawable.ic_baseline_waypoints_circle_36))) + .anchor(0.5F,0.5F) + ) + + + Log.i("MapsActivity", "doAddMarker") + if (marker != null) { + viewModel.addWaypointMarker(marker) + } + Log.i("MapsActivity", "didAddMarker") + } + } + + //マーカーをクリック時にそのマーカーを削除 + private fun setMarkerClick(map: GoogleMap) { + map.setOnMarkerClickListener{marker -> + if(marker.title != "Marker in Origin") { + if (marker.title != "Marker in Dest") { + viewModel.removeWaypointMarker(marker) + marker.remove() + } + } + return@setOnMarkerClickListener true + } + } + + //マーカーをドラッグ時に、マーカーのLatLngを更新 + private fun setOnMarkerDrag(map: GoogleMap) { + map.setOnMarkerDragListener(object : GoogleMap.OnMarkerDragListener { + private var start: com.google.maps.model.LatLng? = null + private var end: com.google.maps.model.LatLng? = null + + override fun onMarkerDragStart(marker: Marker) { + marker.position.let { start = + com.google.maps.model.LatLng(it.latitude, it.longitude) + } + } + + override fun onMarkerDrag(marker: Marker) { + // Do Nothing. + } + + override fun onMarkerDragEnd(marker: Marker) { + marker.position.let { end = + com.google.maps.model.LatLng(it.latitude, it.longitude) + } + viewModel.changeWaypointMarker(marker) + } + }) + } + + //Polylineを更新 + private fun updatePolyline(directionsResult: DirectionsResult?, googleMap: GoogleMap?) { + googleMap ?: return + directionsResult ?: return + removePolyline() + addPolyline(directionsResult, googleMap) + } + + // 線を消す. + private fun removePolyline() { + if (map != null && polyline != null) { + polyline?.remove() + } + } + + // 線を引く + private fun addPolyline(directionsResult: DirectionsResult, map: GoogleMap) { + val polylineOptions = PolylineOptions() + polylineOptions.width(POLYLINE_WIDTH) + // ARGB32bit形式. + polylineOptions.color(R.color.map_polyline_stroke) + val decodedPath = + PolyUtil.decode(directionsResult.routes[overview].overviewPolyline.encodedPath) + polyline = map.addPolyline(polylineOptions.addAll(decodedPath)) + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/waypoints/AddTrialMapsViewModel.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/waypoints/AddTrialMapsViewModel.kt new file mode 100644 index 0000000..1e58c67 --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/add_trial/waypoints/AddTrialMapsViewModel.kt @@ -0,0 +1,147 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.add_trial.waypoints + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.Marker +import com.google.maps.model.DirectionsResult +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.dto.Result +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.dto.Trial +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.repository.TrialRepositoryDummy +import jp.ac.okinawa_ct.nitoc_ict.aroa.ui.add_trial.DirectionsApiHelper +import kotlinx.coroutines.launch + +class AddTrialMapsViewModel(application: Application) : AndroidViewModel(application) { + private val _directionsResult = MutableLiveData() + val directionsResult: LiveData = _directionsResult + + private val trialRepositoryDummy = TrialRepositoryDummy() + + private val _navFrag = MutableLiveData() + val navFrag: LiveData get() = _navFrag + + private val _origin = MutableLiveData() + val origin: LiveData get() = _origin + + private val _dest = MutableLiveData() + val dest: LiveData get() = _dest + + private val _waypointMarkers = MutableLiveData>().apply { + value = ArrayList() + } + val waypointMarkers: LiveData> get() = _waypointMarkers + + private val _trialCourse = MutableLiveData>().apply { + value = ArrayList() + } + val trialCourse: LiveData> get() = _trialCourse + + private val _trialName = MutableLiveData() + val trialName: LiveData get() = _trialName + + fun setTrialName(name: String) { + _trialName.value = name + } + + fun setOrigin(latLng: LatLng) { + _origin.value = latLng + } + + fun setDest(latLng: LatLng) { + _dest.value = latLng + } + + //waypointを追加する + fun addWaypointMarker(marker: Marker) { + _waypointMarkers.value?.add(marker) + directionApiExecute() + } + + //waypointを削除する + fun removeWaypointMarker(marker: Marker) { + _waypointMarkers.value?.remove(marker) + directionApiExecute() + } + + //waypointの値を変更する(ドラッグ&ドロップ) + fun changeWaypointMarker(marker: Marker) { + val markersId = ArrayList() + for (id in _waypointMarkers.value!!) { + markersId.add(id.id) + } + val index = markersId.indexOf(marker.id) + _waypointMarkers.value?.set(index,marker) + directionApiExecute() + } + + //Marker型のwaypointsをDirectionAPIにリクエストを送る用のString型に変更する + private fun markersToString(): String { + val sb = StringBuilder() + if (_waypointMarkers != null) { + for (waypoint in _waypointMarkers.value!!) { +// sb.append(waypoint.lat.toString() + "," + waypoint.lng.toString() + "|") + sb.append(waypoint.position.latitude.toString() + "," + waypoint.position.longitude.toString() + "|") + } + } + return sb.toString() + } + + + //DirectionAPIを実行 + fun directionApiExecute() { + viewModelScope.launch { + if (_waypointMarkers.value!!.isNotEmpty()) { + val waypointsString = markersToString() + val result = DirectionsApiHelper().execute( + com.google.maps.model.LatLng(_origin.value!!.latitude, _origin.value!!.longitude), + com.google.maps.model.LatLng(_dest.value!!.latitude, _dest.value!!.longitude), + waypointsString + ) + _directionsResult.value = result + }else { + val result = DirectionsApiHelper().onlyOriginDestExecute( + com.google.maps.model.LatLng(_origin.value!!.latitude, _origin.value!!.longitude), + com.google.maps.model.LatLng(_dest.value!!.latitude, _dest.value!!.longitude), + ) + _directionsResult.value = result + } + + } + } + + fun createNewTrial() { + _trialCourse.value!!.add(_origin.value!!) + for (waypoint in _waypointMarkers.value!!) { + _trialCourse.value!!.add(LatLng(waypoint.position.latitude,waypoint.position.longitude)) + } + _trialCourse.value!!.add(_dest.value!!) + val trial = Trial.Marathon(_trialName.value!!, + "ひじかた", + _trialCourse.value!!.toList()[0], + _trialCourse.value!!.toList(), + "2022年10月16日") + viewModelScope.launch { + trialRepositoryDummy.createTrial(trial).collect{ + when(it) { + is Result.Loading -> { + + } + is Result.Success -> { + //navigationを実装 + _navFrag.value = true + } + is Result.Error -> { + + } + } + } + } + } + + fun navCompleted() { + _navFrag.value = false + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/CheckRecordFragment.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/CheckRecordFragment.kt deleted file mode 100644 index 30f508f..0000000 --- a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/CheckRecordFragment.kt +++ /dev/null @@ -1,42 +0,0 @@ -package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.check_record - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import androidx.fragment.app.Fragment -import androidx.lifecycle.ViewModelProvider -import jp.ac.okinawa_ct.nitoc_ict.aroa.databinding.FragmentCheckRecordBinding - -class CheckRecordFragment : Fragment() { - - private var _binding: FragmentCheckRecordBinding? = null - - // This property is only valid between onCreateView and - // onDestroyView. - private val binding get() = _binding!! - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - val notificationsViewModel = - ViewModelProvider(this).get(CheckRecordViewModel::class.java) - - _binding = FragmentCheckRecordBinding.inflate(inflater, container, false) - val root: View = binding.root - - val textView: TextView = binding.textCheckRecord - notificationsViewModel.text.observe(viewLifecycleOwner) { - textView.text = it - } - return root - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } -} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/CheckRecordViewModel.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/CheckRecordViewModel.kt deleted file mode 100644 index dcce1ff..0000000 --- a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/CheckRecordViewModel.kt +++ /dev/null @@ -1,13 +0,0 @@ -package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.check_record - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel - -class CheckRecordViewModel : ViewModel() { - - private val _text = MutableLiveData().apply { - value = "This is CheckRecord Fragment" - } - val text: LiveData = _text -} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/RecordNavGraphFragment.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/RecordNavGraphFragment.kt new file mode 100644 index 0000000..15f72ca --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/RecordNavGraphFragment.kt @@ -0,0 +1,25 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.check_record + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.navigation.fragment.NavHostFragment +import jp.ac.okinawa_ct.nitoc_ict.aroa.R +import jp.ac.okinawa_ct.nitoc_ict.aroa.databinding.FragmentCheckRecordListBinding +import jp.ac.okinawa_ct.nitoc_ict.aroa.databinding.FragmentCreateTrialBinding +import jp.ac.okinawa_ct.nitoc_ict.aroa.databinding.FragmentRecordNavGraphBinding + +class RecordNavGraphFragment : Fragment() { + private lateinit var binding: FragmentRecordNavGraphBinding + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentRecordNavGraphBinding.inflate(layoutInflater) + val navHostFragment = childFragmentManager.findFragmentById(R.id.nav_host_check_record) as NavHostFragment + val navController = navHostFragment.navController + return binding.root + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_detail/RecordDetailFragment.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_detail/RecordDetailFragment.kt new file mode 100644 index 0000000..9635ceb --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_detail/RecordDetailFragment.kt @@ -0,0 +1,138 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.check_record.record_detail + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider +import androidx.navigation.fragment.findNavController +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.OnMapReadyCallback +import com.google.android.gms.maps.SupportMapFragment +import com.google.android.gms.maps.model.MarkerOptions +import com.google.android.gms.maps.model.Polyline +import com.google.android.gms.maps.model.PolylineOptions +import com.google.maps.android.PolyUtil +import com.google.maps.model.DirectionsResult +import jp.ac.okinawa_ct.nitoc_ict.aroa.R +import jp.ac.okinawa_ct.nitoc_ict.aroa.databinding.FragmentRecordDetailBinding +import jp.ac.okinawa_ct.nitoc_ict.aroa.ui.check_record.record_detail.RecordDetailFragmentArgs +import jp.ac.okinawa_ct.nitoc_ict.aroa.util.TimeFormat + +class RecordDetailFragment : Fragment() { + + companion object { + private const val ZOOM_SIZE = 16f + private const val POLYLINE_WIDTH = 12f + } + + private var _binding: FragmentRecordDetailBinding? = null + private val binding get() = _binding!! + + private lateinit var viewModel: RecordDetailViewModel + + private var map: GoogleMap? = null + private var polyline: Polyline? = null + + private lateinit var args: RecordDetailFragmentArgs + + private val callback = OnMapReadyCallback { googleMap -> + map = googleMap + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + _binding = FragmentRecordDetailBinding.inflate(layoutInflater) + viewModel = ViewModelProvider(this).get(RecordDetailViewModel::class.java) + args = RecordDetailFragmentArgs.fromBundle(requireArguments()) + + viewModel.setTrialId(args.trialId) + viewModel.getTrialById() + viewModel.setRecordId(args.recordId) + viewModel.getRecordById() + + binding.checkRankingButton.setOnClickListener { + viewModel.navStart() + } + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val mapFragment = childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment? + mapFragment?.getMapAsync(callback) + observeLiveData() + } + + private fun observeLiveData() { + viewModel.record.observe(viewLifecycleOwner, Observer { + viewModel.assignmentRecordDate() + binding.recordTime.text = TimeFormat().convertLongToTimeString(viewModel.recordTime.value!!) + + }) + + viewModel.trial.observe(viewLifecycleOwner, Observer { + viewModel.assignmentTrialData() + map?.moveCamera( + CameraUpdateFactory.newLatLngZoom((viewModel.trial.value!!.position), ZOOM_SIZE) + ) + }) + + viewModel.trialCourse.observe(viewLifecycleOwner, Observer { + viewModel.courseTranslate() + viewModel.directionApiExecute() + map?.addMarker(MarkerOptions().position(viewModel.origin.value!!).title("origin")) + map?.addMarker(MarkerOptions().position(viewModel.dest.value!!).title("dest")) + }) + + viewModel.directionsResult.observe(viewLifecycleOwner, Observer{ + updatePolyline(it, map) + viewModel.getDistance() + binding.recordDistance.text = viewModel.trialDistance.value.toString() + "m" + val speed = viewModel.trialDistance.value!! / viewModel.recordTime.value!! * 3.6 + binding.recordSpeed.text = speed.toString() + "km/h" + }) + + viewModel.navFrag.observe(viewLifecycleOwner, Observer { + if (it == true) { + val action = RecordDetailFragmentDirections.actionRecordDetailFragmentToRecordRankingFragment( + args.trialId + ) + this.findNavController().navigate(action) + viewModel.navCompleted() + } + }) + } + + //Polylineを更新 + private fun updatePolyline(directionsResult: DirectionsResult?, googleMap: GoogleMap?) { + googleMap ?: return + directionsResult ?: return + removePolyline() + addPolyline(directionsResult, googleMap) + } + + // 線を消す. + private fun removePolyline() { + if (map != null && polyline != null) { + polyline?.remove() + } + } + + // 線を引く + private fun addPolyline(directionsResult: DirectionsResult, map: GoogleMap) { + val polylineOptions = PolylineOptions() + polylineOptions.width(POLYLINE_WIDTH) + // ARGB32bit形式. + polylineOptions.color(R.color.map_polyline_stroke) + val decodedPath = + PolyUtil.decode(directionsResult.routes[0].overviewPolyline.encodedPath) + polyline = map.addPolyline(polylineOptions.addAll(decodedPath)) + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_detail/RecordDetailViewModel.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_detail/RecordDetailViewModel.kt new file mode 100644 index 0000000..d738538 --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_detail/RecordDetailViewModel.kt @@ -0,0 +1,173 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.check_record.record_detail + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.android.gms.maps.model.LatLng +import com.google.maps.model.DirectionsResult +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.dto.Record +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.dto.Result +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.dto.Trial +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.repository.RecordRepositoryDummy +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.repository.TrialRepositoryDummy +import jp.ac.okinawa_ct.nitoc_ict.aroa.ui.add_trial.DirectionsApiHelper +import kotlinx.coroutines.launch + +class RecordDetailViewModel : ViewModel() { + private val trialRepositoryDummy = TrialRepositoryDummy() + private val recordRepositoryDummy = RecordRepositoryDummy() + + private val _navFrag = MutableLiveData() + val navFrag: LiveData get() = _navFrag + + fun navStart() { + _navFrag.value = true + } + + fun navCompleted() { + _navFrag.value = false + //LiveDataの初期化を行う + } + + private val _directionsResult = MutableLiveData() + val directionsResult: LiveData = _directionsResult + + private val _origin = MutableLiveData() + val origin: LiveData get() = _origin + + private val _dest = MutableLiveData() + val dest: LiveData get() = _dest + + private val _waypoints = MutableLiveData>() + val waypoints: LiveData> get() = _waypoints + + private val _recordId = MutableLiveData() + val recordId: LiveData get() = _recordId + + private val _record = MutableLiveData() + val record: LiveData get() = _record + + private val _recordTime = MutableLiveData() + val recordTime: LiveData get() = _recordTime + + private val _trial = MutableLiveData() + val trial: LiveData get() = _trial + + private val _trialId = MutableLiveData() + val trialId: LiveData get() = _trialId + + private val _trialCourse = MutableLiveData>() + val trialCourse: LiveData> get() = _trialCourse + + private val _trialDistance = MutableLiveData().apply { + value = 0 + } + val trialDistance: LiveData get() = _trialDistance + + fun setTrialId(id: String) { + _trialId.value = id + } + + fun setRecordId(id: String) { + _recordId.value = id + } + + fun getTrialById() { + viewModelScope.launch { + trialRepositoryDummy.getTrialById(_trialId.value!!).collect{ + when(it) { + is Result.Loading -> "Loading" + is Result.Success -> { + _trial.value = it.data!! + } + is Result.Error -> "Error" + } + } + } + } + + fun getRecordById() { + viewModelScope.launch { + recordRepositoryDummy.getRecordByTrialIdAndRecordId(trialId.value!!, _recordId.value!!).collect{ + when(it) { + is Result.Loading -> "Loading" + is Result.Success -> { + _record.value = it.data!! + } + is Result.Error -> "Error" + } + } + } + } + + fun assignmentTrialData() { + _trial.value?.let { + when(it) { + is Trial.Marathon -> { + _trialCourse.value = it.course + } + else -> {} + } + } + } + + fun assignmentRecordDate() { + _record.value?.let { + when(it) { + is Record.MarathonRecord -> { + _recordTime.value = it.time + } + else -> {} + } + } + } + + fun getDistance() { + _trialDistance.value = 0L + for (route in _directionsResult.value!!.routes) { + for (leg in route.legs) { + _trialDistance.value = _trialDistance.value?.plus(leg.distance.inMeters) + } + } + } + + fun courseTranslate() { + _trialCourse.value?.let { courselatLng -> + _origin.value = courselatLng.first() + _dest.value = courselatLng.last() + _waypoints.value = ArrayList() + for (latLng in courselatLng) { + when(latLng) { + courselatLng.first() -> continue + courselatLng.last() -> continue + else -> _waypoints.value?.add(latLng) + } + } + } + } + + private fun latLngToString(): String { + val sb = StringBuilder() + if (_waypoints != null) { + for (waypoint in _waypoints.value!!) { + sb.append(waypoint.latitude.toString() + "," + waypoint.longitude.toString() + "|") + } + } + return sb.toString() + } + + fun directionApiExecute() { + viewModelScope.launch { + val waypointsString = latLngToString() + val result = DirectionsApiHelper().execute( + com.google.maps.model.LatLng(_origin.value!!.latitude, _origin.value!!.longitude), + com.google.maps.model.LatLng(_dest.value!!.latitude, _dest.value!!.longitude), + waypointsString + ) + _directionsResult.value = result + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_list/CheckRecordListFragment.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_list/CheckRecordListFragment.kt new file mode 100644 index 0000000..76f2f50 --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_list/CheckRecordListFragment.kt @@ -0,0 +1,55 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.check_record.record_list + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import jp.ac.okinawa_ct.nitoc_ict.aroa.databinding.FragmentCheckRecordListBinding + +class CheckRecordListFragment : Fragment() { + + private var _binding: FragmentCheckRecordListBinding? = null + + // This property is only valid between onCreateView and + // onDestroyView. + private val binding get() = _binding!! + private lateinit var viewModel: CheckRecordListViewModel + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + viewModel = + ViewModelProvider(this).get(CheckRecordListViewModel::class.java) + + _binding = FragmentCheckRecordListBinding.inflate(inflater, container, false) + + val adapter = RecordListAdapter(requireContext()) + binding.recordList.adapter = adapter + viewModel.testRecordList.observe(viewLifecycleOwner, Observer { + adapter.submitList(it) + }) + + val dividerItemDecoration = DividerItemDecoration( + requireContext(), LinearLayoutManager(requireContext()).getOrientation()) + binding.recordList.addItemDecoration(dividerItemDecoration) + + adapter.setOnItemClickListener { view, position -> + val action = CheckRecordListFragmentDirections + .actionCheckRecordListFragmentToRecordDetailFragment( + viewModel.testRecordList.value!!.get(position).recordId, + viewModel.testRecordList.value!!.get(position).trialId + ) + this.findNavController().navigate(action) + } + + return binding.root + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_list/CheckRecordListViewModel.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_list/CheckRecordListViewModel.kt new file mode 100644 index 0000000..2d7f75d --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_list/CheckRecordListViewModel.kt @@ -0,0 +1,39 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.check_record.record_list + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.dto.Record +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.repository.RecordRepositoryDummy +import kotlinx.coroutines.launch +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.dto.Result as Result + +class CheckRecordListViewModel : ViewModel() { + private val recordRepositoryDummy = RecordRepositoryDummy() + + private val _testRecordList = MutableLiveData>() + val testRecordList: LiveData> get() = _testRecordList + + private val _collectState = MutableLiveData() + val collectState: LiveData get() = _collectState + + init { + getRecordList() + } + + private fun getRecordList() { + viewModelScope.launch { + recordRepositoryDummy.getMyRecords("").collect{ + when(it) { + is Result.Loading -> _collectState.value = "Loading" + is Result.Success -> { + _testRecordList.value = it.data!! + } + is Result.Error -> _collectState.value = "Error" + } + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_list/RecordListAdapter.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_list/RecordListAdapter.kt new file mode 100644 index 0000000..0fdb9f8 --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_list/RecordListAdapter.kt @@ -0,0 +1,66 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.check_record.record_list + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.dto.Record +import jp.ac.okinawa_ct.nitoc_ict.aroa.databinding.ListItemRecordBinding +import jp.ac.okinawa_ct.nitoc_ict.aroa.util.TimeFormat + +typealias OnItemClickListener = (view: View, position: Int) -> Unit + +class RecordListAdapter( + context: Context +) : ListAdapter(ITEM_CALLBACK) { + private val inflater = LayoutInflater.from(context) + + private var onItemClickListener: OnItemClickListener? = null + + class BindingHolder( + val binding: ListItemRecordBinding + ) : RecyclerView.ViewHolder(binding.root) + companion object { + val ITEM_CALLBACK = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: Record, + newItem: Record + ): Boolean = oldItem.userId == newItem.userId + + override fun areContentsTheSame( + oldItem: Record, + newItem: Record + ): Boolean = oldItem == newItem + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder { + val binding = ListItemRecordBinding.inflate(inflater, parent, false) + return BindingHolder(binding) + } + + override fun onBindViewHolder(bindingHolder: BindingHolder, position: Int) { + val current = getItem(position) + when(current) { + is Record.MarathonRecord -> { + bindingHolder.binding.recordTrialDay.text = current.date + bindingHolder.binding.recordTrialName.text = current.trialName + bindingHolder.binding.recordTrialDistance.text = current.distance.toString() + "m" + bindingHolder.binding.recordTrialTime.text = TimeFormat().convertLongToTimeString(current.time) + bindingHolder.binding.recordTrialSpeed.text = (current.distance / current.time * 3.6).toString() + "km/h" + } + else -> {} + } + + bindingHolder.binding.root.setOnClickListener { + onItemClickListener?.invoke(it,position) + } + } + + fun setOnItemClickListener(onItemClickListener: OnItemClickListener) { + this.onItemClickListener = onItemClickListener + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_ranking/RecordRankingFragment.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_ranking/RecordRankingFragment.kt new file mode 100644 index 0000000..f4c7a6f --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_ranking/RecordRankingFragment.kt @@ -0,0 +1,49 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.check_record.record_ranking + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import jp.ac.okinawa_ct.nitoc_ict.aroa.databinding.FragmentRecordRankingBinding +import jp.ac.okinawa_ct.nitoc_ict.aroa.ui.check_record.record_ranking.RecordRankingFragmentArgs + +class RecordRankingFragment : Fragment() { + private var _biding: FragmentRecordRankingBinding? = null + private val binding get() = _biding!! + private lateinit var viewModel: RecordRankingViewModel + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + _biding = FragmentRecordRankingBinding.inflate(layoutInflater) + viewModel = ViewModelProvider(this).get(RecordRankingViewModel::class.java) + + val args = RecordRankingFragmentArgs.fromBundle(requireArguments()) + viewModel.setTrialId(args.trialId) + + viewModel.trialId.observe(viewLifecycleOwner, Observer { + viewModel.getRanking(it) + }) + + val adapter = RecordRankingListAdapter(requireContext()) + binding.rankingList.adapter = adapter + + val dividerItemDecoration = DividerItemDecoration( + requireContext(), LinearLayoutManager(requireContext()).getOrientation()) + binding.rankingList.addItemDecoration(dividerItemDecoration) + + viewModel.testRecordData.observe(viewLifecycleOwner, Observer { + adapter.submitList(it) + }) + + + return binding.root + } + +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_ranking/RecordRankingListAdapter.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_ranking/RecordRankingListAdapter.kt new file mode 100644 index 0000000..a6594e3 --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_ranking/RecordRankingListAdapter.kt @@ -0,0 +1,60 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.check_record.record_ranking + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.dto.Record +import jp.ac.okinawa_ct.nitoc_ict.aroa.databinding.ListItemRecordRankingBinding +import jp.ac.okinawa_ct.nitoc_ict.aroa.util.TimeFormat + +typealias OnItemClickListener = (view: View, position: Int) -> Unit + +class RecordRankingListAdapter( + context: Context +) : ListAdapter(ITEM_CALLBACK) { + private val inflater = LayoutInflater.from(context) + + private var onItemClickListener: OnItemClickListener? = null + + class BindingHolder( + val binding: ListItemRecordRankingBinding + ) : RecyclerView.ViewHolder(binding.root) + companion object { + val ITEM_CALLBACK = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: Record, + newItem: Record + ): Boolean = oldItem.userId == newItem.userId + + override fun areContentsTheSame( + oldItem: Record, + newItem: Record + ): Boolean = oldItem == newItem + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingHolder { + val binding = ListItemRecordRankingBinding.inflate(inflater, parent, false) + return BindingHolder(binding) + } + + override fun onBindViewHolder(bindingHolder: BindingHolder, position: Int) { + val current = getItem(position) + when(current) { + is Record.MarathonRecord -> { + bindingHolder.binding.rankNum.text = current.rank.toString() + bindingHolder.binding.userName.text = current.userId + bindingHolder.binding.time.text = TimeFormat().convertLongToTimeString(current.time) + } + else -> {} + } + } + + fun setOnItemClickListener(onItemClickListener: OnItemClickListener) { + this.onItemClickListener = onItemClickListener + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_ranking/RecordRankingViewModel.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_ranking/RecordRankingViewModel.kt new file mode 100644 index 0000000..a22bca7 --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/check_record/record_ranking/RecordRankingViewModel.kt @@ -0,0 +1,38 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.check_record.record_ranking + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.dto.Record +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.dto.Result +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.repository.RecordRepositoryDummy +import kotlinx.coroutines.launch + +class RecordRankingViewModel : ViewModel() { + private val recordRepositoryDummy = RecordRepositoryDummy() + + private val _testRecordData = MutableLiveData>() + val testRecordData: LiveData> get() = _testRecordData + + private val _trialId = MutableLiveData() + val trialId: LiveData get() = _trialId + + fun setTrialId(id: String) { + _trialId.value = id + } + + fun getRanking(trialId: String) { + viewModelScope.launch { + recordRepositoryDummy.getRecords(trialId,1,10).collect{ + when(it) { + is Result.Loading -> {} + is Result.Success -> { + _testRecordData.value = it.data!! + } + is Result.Error -> {} + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/create_trial/CreateTrialFragment.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/create_trial/CreateTrialFragment.kt new file mode 100644 index 0000000..c5d49c0 --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/create_trial/CreateTrialFragment.kt @@ -0,0 +1,32 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.create_trial + +import androidx.lifecycle.ViewModelProvider +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.navigation.fragment.NavHostFragment +import jp.ac.okinawa_ct.nitoc_ict.aroa.R +import jp.ac.okinawa_ct.nitoc_ict.aroa.databinding.FragmentAddTrialBinding +import jp.ac.okinawa_ct.nitoc_ict.aroa.databinding.FragmentCreateTrialBinding + +class CreateTrialFragment : Fragment() { + + companion object { + fun newInstance() = CreateTrialFragment() + } + + private lateinit var viewModel: CreateTrialViewModel + private lateinit var binding: FragmentCreateTrialBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentCreateTrialBinding.inflate(layoutInflater) + val navHostFragment = childFragmentManager.findFragmentById(R.id.nav_host_create_trial) as NavHostFragment + val navController = navHostFragment.navController + return binding.root + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/create_trial/CreateTrialViewModel.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/create_trial/CreateTrialViewModel.kt new file mode 100644 index 0000000..f460df1 --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/create_trial/CreateTrialViewModel.kt @@ -0,0 +1,7 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.create_trial + +import androidx.lifecycle.ViewModel + +class CreateTrialViewModel : ViewModel() { + // TODO: Implement the ViewModel +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/detail/TrialDetailFragment.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/detail/TrialDetailFragment.kt new file mode 100644 index 0000000..0efa7d4 --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/detail/TrialDetailFragment.kt @@ -0,0 +1,132 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.detail + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider +import androidx.navigation.fragment.findNavController +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.OnMapReadyCallback +import com.google.android.gms.maps.SupportMapFragment +import com.google.android.gms.maps.model.MarkerOptions +import com.google.android.gms.maps.model.Polyline +import com.google.android.gms.maps.model.PolylineOptions +import com.google.maps.android.PolyUtil +import com.google.maps.model.DirectionsResult +import jp.ac.okinawa_ct.nitoc_ict.aroa.R +import jp.ac.okinawa_ct.nitoc_ict.aroa.databinding.FragmentTrialDetailBinding + +class TrialDetailFragment : Fragment() { + companion object { + private const val ZOOM_SIZE = 14f + private const val POLYLINE_WIDTH = 12f + } + + private var map: GoogleMap? = null + private var polyline: Polyline? = null + private lateinit var viewModel: TrialDetailViewModel + + private lateinit var _binding: FragmentTrialDetailBinding + private val binding get() = _binding + + private lateinit var args: TrialDetailFragmentArgs + + private val callback = OnMapReadyCallback { googleMap -> + map = googleMap + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentTrialDetailBinding.inflate(layoutInflater) + viewModel = ViewModelProvider(this).get(TrialDetailViewModel::class.java) + args = TrialDetailFragmentArgs.fromBundle(requireArguments()) + if (!args.canStartTrial) { + binding.startTrialButton.visibility = View.INVISIBLE + }else { + binding.startTrialButton.setOnClickListener { + //トライアルに参加 + } + } + binding.checkRankingButton.setOnClickListener { + viewModel.navStart() + } + viewModel.setTrialId(args.trialId) + viewModel.getTrialById() + + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val mapFragment = childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment? + mapFragment?.getMapAsync(callback) + observeLiveData() + } + + private fun observeLiveData() { + viewModel.trial.observe(viewLifecycleOwner, Observer { + viewModel.assignmentTrialData() + map?.moveCamera( + CameraUpdateFactory.newLatLngZoom(viewModel.trial.value!!.position, ZOOM_SIZE)) + binding.detailTrialName.text = viewModel.trial.value!!.name + binding.detailTrialAuthorUserId.text = viewModel.trial.value!!.authorUserId + binding.detailTrialPosition.text = viewModel.trialPosition.value.toString() + }) + + viewModel.trialCourse.observe(viewLifecycleOwner, Observer { + viewModel.courseTranslate() + viewModel.directionApiExecute() + map?.addMarker(MarkerOptions().position(viewModel.origin.value!!).title("origin")) + map?.addMarker(MarkerOptions().position(viewModel.dest.value!!).title("dest")) + }) + + viewModel.directionsResult.observe(viewLifecycleOwner, Observer{ + updatePolyline(it, map) + viewModel.getDistance() + binding.detailTrialDistance.text = viewModel.trialDistance.value.toString() + "m" + }) + + viewModel.navFrag.observe(viewLifecycleOwner, Observer { + if (it == true) { + val action = TrialDetailFragmentDirections + .actionTrialDetailFragmentToRecordRankingFragment(args.trialId) + this.findNavController().navigate(action) + viewModel.navCompleted() + } + }) + } + + //Polylineを更新 + private fun updatePolyline(directionsResult: DirectionsResult?, googleMap: GoogleMap?) { + googleMap ?: return + directionsResult ?: return + removePolyline() + addPolyline(directionsResult, googleMap) + } + + // 線を消す. + private fun removePolyline() { + if (map != null && polyline != null) { + polyline?.remove() + } + } + + // 線を引く + private fun addPolyline(directionsResult: DirectionsResult, map: GoogleMap) { + val polylineOptions = PolylineOptions() + polylineOptions.width(POLYLINE_WIDTH) + // ARGB32bit形式. + polylineOptions.color(R.color.map_polyline_stroke) + val decodedPath = + PolyUtil.decode(directionsResult.routes[0].overviewPolyline.encodedPath) + polyline = map.addPolyline(polylineOptions.addAll(decodedPath)) + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/detail/TrialDetailViewModel.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/detail/TrialDetailViewModel.kt new file mode 100644 index 0000000..b76952b --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/detail/TrialDetailViewModel.kt @@ -0,0 +1,136 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.detail + +import android.app.Application +import android.util.Log +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.google.android.gms.maps.model.LatLng +import com.google.maps.model.DirectionsResult +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.dto.Trial +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.repository.TrialRepositoryDummy +import jp.ac.okinawa_ct.nitoc_ict.aroa.ui.add_trial.DirectionsApiHelper +import kotlinx.coroutines.launch +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.dto.Result as Result + +class TrialDetailViewModel(application: Application) : AndroidViewModel(application) { + private val trialRepositoryDummy = TrialRepositoryDummy() + + private val _navFrag = MutableLiveData() + val navFrag: LiveData get() = _navFrag + + fun navStart() { + _navFrag.value = true + } + + fun navCompleted() { + _navFrag.value = false + //LiveDataの初期化を行う + } + + private val _directionsResult = MutableLiveData() + val directionsResult: LiveData = _directionsResult + + private val _origin = MutableLiveData() + val origin: LiveData get() = _origin + + private val _dest = MutableLiveData() + val dest: LiveData get() = _dest + + private val _waypoints = MutableLiveData>() + val waypoints: LiveData> get() = _waypoints + + private val _trial = MutableLiveData() + val trial: LiveData get() = _trial + + private val _trialId = MutableLiveData() + val trialId: LiveData get() = _trialId + + private val _trialPosition = MutableLiveData() + val trialPosition: LiveData get() = _trialPosition + + private val _trialCourse = MutableLiveData>() + val trialCourse: LiveData> get() = _trialCourse + + private val _trialDistance = MutableLiveData().apply { + value = 0 + } + val trialDistance: LiveData get() = _trialDistance + + fun setTrialId(id: String) { + _trialId.value = id + } + + fun getTrialById() { + viewModelScope.launch { + trialRepositoryDummy.getTrialById(_trialId.value!!).collect{ + when(it) { + is Result.Loading -> {} + is Result.Success -> { + _trial.value = it.data!! + } + is Result.Error -> {} + } + } + } + } + + fun assignmentTrialData() { + _trial.value?.let { + when(it) { + is Trial.Marathon -> { + _trialPosition.value = it.position + _trialCourse.value = it.course + } + else -> {} + } + } + } + + fun getDistance() { + _trialDistance.value = 0L + for (route in _directionsResult.value!!.routes) { + for (leg in route.legs) { + _trialDistance.value = _trialDistance.value?.plus(leg.distance.inMeters) + } + } + } + + fun courseTranslate() { + _trialCourse.value?.let { courselatLngs -> + _origin.value = courselatLngs.first() + _dest.value = courselatLngs.last() + _waypoints.value = ArrayList() + for (latLng in courselatLngs) { + when(latLng) { + courselatLngs.first() -> continue + courselatLngs.last() -> continue + else -> _waypoints.value?.add(latLng) + } + } + } + } + + private fun latLngToString(): String { + val sb = StringBuilder() + if (_waypoints != null) { + for (waypoint in _waypoints.value!!) { + sb.append(waypoint.latitude.toString() + "," + waypoint.longitude.toString() + "|") + } + } + return sb.toString() + } + + fun directionApiExecute() { + viewModelScope.launch { + val waypointsString = latLngToString() + val result = DirectionsApiHelper().execute( + com.google.maps.model.LatLng(_origin.value!!.latitude, _origin.value!!.longitude), + com.google.maps.model.LatLng(_dest.value!!.latitude, _dest.value!!.longitude), + waypointsString + ) + _directionsResult.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/home/HomeFragment.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/home/HomeFragment.kt index 1cda398..0288211 100644 --- a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/home/HomeFragment.kt +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/home/HomeFragment.kt @@ -1,49 +1,144 @@ package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.home +import android.content.pm.PackageManager +import android.location.Location import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.TextView +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider +import androidx.navigation.fragment.findNavController +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationServices +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.OnMapReadyCallback +import com.google.android.gms.maps.SupportMapFragment +import com.google.android.gms.maps.model.BitmapDescriptorFactory +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.MarkerOptions +import jp.ac.okinawa_ct.nitoc_ict.aroa.R import jp.ac.okinawa_ct.nitoc_ict.aroa.databinding.FragmentHomeBinding -import jp.ac.okinawa_ct.nitoc_ict.aroa.ui.trial_detail.TrialDetailActivity +import jp.ac.okinawa_ct.nitoc_ict.aroa.util.ConverterVectorToBitmap + class HomeFragment : Fragment() { + private val REQUEST_LOCATION_PERMISSION = 1 private var _binding: FragmentHomeBinding? = null - // This property is only valid between onCreateView and - // onDestroyView. private val binding get() = _binding!! + private lateinit var map : GoogleMap + private lateinit var homeViewModel: HomeViewModel + + private lateinit var fusedLocationClient: FusedLocationProviderClient + private var currentPosition: LatLng? = null + + + private val callback = OnMapReadyCallback { googleMap -> + map = googleMap + enableMyLocation() + setMarkerClick(googleMap) + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { - val homeViewModel = - ViewModelProvider(this)[HomeViewModel::class.java] - _binding = FragmentHomeBinding.inflate(inflater, container, false) - val root: View = binding.root + homeViewModel = + ViewModelProvider(this).get(HomeViewModel::class.java) + - val textView: TextView = binding.textHome + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val mapFragment = childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment + mapFragment.getMapAsync(callback) + observeLiveData() + } - textView.setOnClickListener { - val intent = TrialDetailActivity.makeIntent(requireContext(), "TrialId") - startActivity(intent) + private fun observeLiveData() { + homeViewModel.foundTrials.observe(viewLifecycleOwner) { + for (data in it){ + map.addMarker(MarkerOptions() + .position(data.position) + .title(data.id) + .icon( + BitmapDescriptorFactory.fromBitmap( + ConverterVectorToBitmap().getBitmapFromVectorDrawable( + requireContext(),R.drawable.ic_baseline_trial_circle_36))) + .anchor(0.5F,0.5F)) + } } + } + + private fun setMarkerClick(map: GoogleMap) { + homeViewModel.foundTrials.observe(viewLifecycleOwner) { + map.setOnMarkerClickListener{marker -> + marker.title?.let { + val action = HomeFragmentDirections.actionHomeFragmentToTrialDetailFragment( + it,true + ) + this.findNavController().navigate(action) + } + return@setOnMarkerClickListener true + } + } + } + + private fun isPermissionGranted() : Boolean { + return ContextCompat.checkSelfPermission( + requireContext(), + android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED + } + + private fun enableMyLocation() { + if (isPermissionGranted()) { + map.isMyLocationEnabled = true - homeViewModel.text.observe(viewLifecycleOwner) { - textView.text = it + fusedLocationClient = LocationServices.getFusedLocationProviderClient(requireActivity()) + fusedLocationClient.lastLocation + .addOnSuccessListener { location : Location? -> + currentPosition = LatLng(location!!.latitude,location.longitude) + Log.i("HomeFragment","FusedLocationClient:${location.latitude.toString()}") + map.apply { + Log.i("HomeFragment","currentPosition:${currentPosition.toString()}") + moveCamera(CameraUpdateFactory.newLatLngZoom( + currentPosition!!, 16f + )) +// LatLng(26.526703736324663, 128.03039187339328) + binding.progressBar.visibility = View.GONE + } + } + } + else { + ActivityCompat.requestPermissions( + this.requireActivity(), + arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION), + REQUEST_LOCATION_PERMISSION + ) } - return root } - override fun onDestroyView() { - super.onDestroyView() - _binding = null + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (requestCode == REQUEST_LOCATION_PERMISSION) { + if (grantResults.contains(PackageManager.PERMISSION_GRANTED)) { + enableMyLocation() + } + } } + } \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/home/HomeNavGraphFragment.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/home/HomeNavGraphFragment.kt new file mode 100644 index 0000000..26b1bdf --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/home/HomeNavGraphFragment.kt @@ -0,0 +1,25 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.home + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.navigation.fragment.NavHostFragment +import jp.ac.okinawa_ct.nitoc_ict.aroa.R +import jp.ac.okinawa_ct.nitoc_ict.aroa.databinding.FragmentCheckRecordListBinding +import jp.ac.okinawa_ct.nitoc_ict.aroa.databinding.FragmentHomeNavGraphBinding + +class HomeNavGraphFragment : Fragment() { + private lateinit var binding: FragmentHomeNavGraphBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentHomeNavGraphBinding.inflate(layoutInflater) + val navHostFragment = childFragmentManager.findFragmentById(R.id.nav_host_home_trial) as NavHostFragment + val navController = navHostFragment.navController + return binding.root + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/home/HomeViewModel.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/home/HomeViewModel.kt index 523c1d9..943b684 100644 --- a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/home/HomeViewModel.kt +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/ui/home/HomeViewModel.kt @@ -3,11 +3,41 @@ package jp.ac.okinawa_ct.nitoc_ict.aroa.ui.home import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.android.gms.maps.model.LatLng +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.dto.Result +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.dto.Trial +import jp.ac.okinawa_ct.nitoc_ict.aroa.data.repository.TrialRepositoryDummy +import kotlinx.coroutines.launch class HomeViewModel : ViewModel() { + private val trialRepository = TrialRepositoryDummy() - private val _text = MutableLiveData().apply { - value = "This is home Fragment" + private val _foundTrials: MutableLiveData> = MutableLiveData(listOf()) + val foundTrials: LiveData> = _foundTrials + + init { + findTrialsNearMe() + } + + fun findTrialsNearMe() { + + viewModelScope.launch { + + trialRepository + .getTrialsNear(LatLng(26.526230, 128.030372), 100.0) + .collect { + when (it) { + is Result.Loading -> _foundTrials.value = listOf() + is Result.Success -> { + _foundTrials.value = it.data!! + } + + is Result.Error -> { + _foundTrials.value = listOf() + } + } + } + } } - val text: LiveData = _text } \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/util/ConverterVectorToBitmap.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/util/ConverterVectorToBitmap.kt new file mode 100644 index 0000000..d9269b7 --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/util/ConverterVectorToBitmap.kt @@ -0,0 +1,20 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.util + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import androidx.core.content.ContextCompat + +class ConverterVectorToBitmap { + fun getBitmapFromVectorDrawable(context: Context, drawableId: Int): Bitmap { + val drawable = ContextCompat.getDrawable(context, drawableId) + val bitmap = Bitmap.createBitmap( + drawable!!.intrinsicWidth, + drawable.intrinsicHeight, Bitmap.Config.ARGB_8888 + ) + val canvas = Canvas(bitmap) + drawable.setBounds(0, 0, canvas.width, canvas.height) + drawable.draw(canvas) + return bitmap + } +} \ No newline at end of file diff --git a/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/util/TimeFormat.kt b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/util/TimeFormat.kt new file mode 100644 index 0000000..a97941f --- /dev/null +++ b/app/src/main/java/jp/ac/okinawa_ct/nitoc_ict/aroa/util/TimeFormat.kt @@ -0,0 +1,17 @@ +package jp.ac.okinawa_ct.nitoc_ict.aroa.util + +import android.annotation.SuppressLint +import android.text.format.DateUtils + +class TimeFormat { + @SuppressLint("SimpleDateFormat") + fun convertLongToTimeString(time: Long): String { +// val hours = time / 3600 +// val minutes = time % 3600 / 60 +// val seconds = time % 3600 % 60 +// val formatTime = hours.toString() + ":" + minutes.toString() + ":" + seconds.toString() + return DateUtils.formatElapsedTime(time) + } + + +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_flag_circle_dest_36.xml b/app/src/main/res/drawable/ic_baseline_flag_circle_dest_36.xml new file mode 100644 index 0000000..3af23e4 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_flag_circle_dest_36.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_flag_circle_origin_36.xml b/app/src/main/res/drawable/ic_baseline_flag_circle_origin_36.xml new file mode 100644 index 0000000..aefbb1a --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_flag_circle_origin_36.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_trial_circle_36.xml b/app/src/main/res/drawable/ic_baseline_trial_circle_36.xml new file mode 100644 index 0000000..a34b66b --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_trial_circle_36.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_waypoints_circle_36.xml b/app/src/main/res/drawable/ic_baseline_waypoints_circle_36.xml new file mode 100644 index 0000000..7d2fef9 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_waypoints_circle_36.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/under_line.xml b/app/src/main/res/drawable/under_line.xml new file mode 100644 index 0000000..f088712 --- /dev/null +++ b/app/src/main/res/drawable/under_line.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index a488693..39d13e9 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,11 +1,9 @@ + android:layout_height="match_parent"> + tools:context=".ui.addtrial.start.AddTrialFragment"> - + + + + +