diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..e8c5255 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,47 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "com.bing89.travebing" + compileSdk = 34 + + defaultConfig { + applicationId = "com.bing89.travebing" + minSdk = 29 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation("androidx.core:core-ktx:1.10.1") + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.9.0") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/bing89/travebing/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/bing89/travebing/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..b230aa7 --- /dev/null +++ b/app/src/androidTest/java/com/bing89/travebing/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.bing89.travebing + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.bing89.travebing", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..bee331d --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/bing89/travebing/MainActivity.kt b/app/src/main/java/com/bing89/travebing/MainActivity.kt new file mode 100644 index 0000000..88e376b --- /dev/null +++ b/app/src/main/java/com/bing89/travebing/MainActivity.kt @@ -0,0 +1,199 @@ +@file:OptIn(DelicateCoroutinesApi::class) + +package com.bing89.travebing + +import android.Manifest +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.location.Location +import android.location.LocationListener +import android.location.LocationManager +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.provider.MediaStore +import android.util.Base64 +import android.widget.Button +import android.widget.ImageView +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat +import androidx.core.content.FileProvider +import com.bing89.travebing.R.* +import kotlinx.coroutines.* +import java.io.ByteArrayOutputStream +import java.io.File +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +class MainActivity : AppCompatActivity() { + + private lateinit var startButton: Button + private lateinit var previewImageView: ImageView + private lateinit var gpsInfoTextView: TextView + + private var isMonitoring = false + private var photoFilePath: String? = null + private var locationManager: LocationManager? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(layout.activity_main) + + startButton = findViewById(id.startButton) + previewImageView = findViewById(id.previewImageView) + gpsInfoTextView = findViewById(id.gpsInfoTextView) + + startButton.setOnClickListener { + if (isMonitoring) { + stopMonitoring() + } else { + startMonitoring() + } + } + + locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager + } + + private fun startMonitoring() { + isMonitoring = true + startButton.text = "休息" + + val intervalMillis = 60000L // 间隔时间,这里设置为1分钟 + val handler = Handler(Looper.getMainLooper()) + + handler.post(object : Runnable { + override fun run() { + if (isMonitoring) { + capturePhotoAndUpload() + handler.postDelayed(this, intervalMillis) + } + } + }) + } + + private fun stopMonitoring() { + isMonitoring = false + startButton.text = "出发" + } + + @SuppressLint("QueryPermissionsNeeded") + private fun capturePhotoAndUpload() { + if (checkPermissions()) { + // 拍照 + val photoIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) + if (photoIntent.resolveActivity(packageManager) != null) { + val photoFile = createPhotoFile() + photoFilePath = photoFile?.absolutePath + val photoUri = FileProvider.getUriForFile( + this, + "com.bing89.travebing.fileprovider", + photoFile!! + ) + photoIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri) +// startActivityForResult(this, PHOTO_REQUEST_CODE) + } + } + } + + private fun checkPermissions(): Boolean { + val requiredPermissions = arrayOf( + Manifest.permission.CAMERA, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.ACCESS_FINE_LOCATION + ) + + for (permission in requiredPermissions) { + if (ActivityCompat.checkSelfPermission( + this, + permission + ) != PackageManager.PERMISSION_GRANTED + ) { + ActivityCompat.requestPermissions(this, requiredPermissions, PERMISSION_REQUEST_CODE) + return false + } + } + + return true + } + + private fun createPhotoFile(): File? { + val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) + val storageDir = getExternalFilesDir(null) + return File.createTempFile("JPEG_${timeStamp}_", ".jpg", storageDir) + } + + private fun getLocation() { + val locationListener = object : LocationListener { + override fun onLocationChanged(location: Location) { + updateGpsInfo(location) + } + + @Deprecated("Deprecated in Java") + override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) { + // Ignored + } + + override fun onProviderEnabled(provider: String) { + // Ignored + } + + override fun onProviderDisabled(provider: String) { + // Ignored + } + } + + if (ActivityCompat.checkSelfPermission( + this, + Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + ) { + locationManager?.requestLocationUpdates( + LocationManager.GPS_PROVIDER, + 0, + 0f, + locationListener + ) + } + } + + private fun updateGpsInfo(location: Location) { + val latitude = location.latitude + val longitude = location.longitude + val gpsInfo = "Latitude: $latitude, Longitude: $longitude" + gpsInfoTextView.text = gpsInfo + } + + private fun uploadPhotoAndGpsInfo(photoFilePath: String, gpsInfo: String) { + // 在此处实现将照片和GPS信息上传到api.abc.com/report接口的逻辑 + // 可以使用 Retrofit 或其他网络库进行网络请求 + // 将照片转换成 Base64 字符串,然后添加到请求中 + // 请求完成后更新界面上的预览窗口等信息 + GlobalScope.launch(Dispatchers.IO) { + try { + val photoFile = File(photoFilePath) + val photoBytes = ByteArrayOutputStream().use { + val inputStream = photoFile.inputStream() + inputStream.copyTo(it) + it.toByteArray() + } + + val base64Photo = Base64.encodeToString(photoBytes, Base64.DEFAULT) + + // 在此处添加上传逻辑 + // 使用 Retrofit 或其他网络库发送照片和GPS信息到api.abc.com/report接口 + // 请注意处理网络请求的结果和错误 + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + companion object { + private const val PERMISSION_REQUEST_CODE = 1001 + private const val PHOTO_REQUEST_CODE = 1002 + } +} diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ 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 new file mode 100644 index 0000000..944f30f --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,79 @@ + + + + + +