This repository has been archived by the owner on Dec 24, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4 from flex3r/kotlin_and_mvvm
Rewrote app in kotlin
Showing
15 changed files
with
446 additions
and
400 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
27 changes: 27 additions & 0 deletions
27
app/src/main/java/com/nuuls/axel/nuulsimageuploader/BindingAdapter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package com.nuuls.axel.nuulsimageuploader | ||
|
||
import android.net.Uri | ||
import android.widget.Button | ||
import android.widget.ImageView | ||
import androidx.core.content.ContextCompat | ||
import androidx.databinding.BindingAdapter | ||
|
||
@BindingAdapter("preview") | ||
fun ImageView.setPreview(uri: Uri?) { | ||
if (uri == null) { | ||
setImageResource(R.drawable.nuulslogo) | ||
} else { | ||
setImageURI(uri) | ||
} | ||
} | ||
|
||
@BindingAdapter("state") | ||
fun Button.setState(uploading: Boolean) { | ||
text = if (uploading) { | ||
setBackgroundColor(ContextCompat.getColor(context, R.color.upload_progress_background)) | ||
context.getString(R.string.uploading) | ||
} else { | ||
setBackgroundColor(ContextCompat.getColor(context, R.color.upload_background)) | ||
context.getString(R.string.upload) | ||
} | ||
} |
67 changes: 0 additions & 67 deletions
67
app/src/main/java/com/nuuls/axel/nuulsimageuploader/HttpPostTask.java
This file was deleted.
Oops, something went wrong.
265 changes: 0 additions & 265 deletions
265
app/src/main/java/com/nuuls/axel/nuulsimageuploader/MainActivity.java
This file was deleted.
Oops, something went wrong.
156 changes: 156 additions & 0 deletions
156
app/src/main/java/com/nuuls/axel/nuulsimageuploader/MainActivity.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
package com.nuuls.axel.nuulsimageuploader | ||
|
||
import android.Manifest | ||
import android.app.Activity | ||
import android.content.ClipData | ||
import android.content.ClipboardManager | ||
import android.content.Context | ||
import android.content.Intent | ||
import android.content.pm.PackageManager | ||
import android.net.Uri | ||
import android.os.Bundle | ||
import android.provider.MediaStore | ||
import android.util.Log | ||
import android.webkit.MimeTypeMap | ||
import android.widget.Toast | ||
import androidx.appcompat.app.AppCompatActivity | ||
import androidx.core.app.ActivityCompat | ||
import androidx.core.content.FileProvider | ||
import androidx.databinding.DataBindingUtil | ||
import androidx.lifecycle.ViewModelProvider | ||
import com.nuuls.axel.nuulsimageuploader.databinding.MainActivityBinding | ||
import java.io.File | ||
import java.io.IOException | ||
|
||
class MainActivity : AppCompatActivity() { | ||
private lateinit var binding: MainActivityBinding | ||
private lateinit var viewModel: MainViewModel | ||
|
||
private var currentImagePath: String = "" | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
viewModel = ViewModelProvider(this).get(MainViewModel::class.java) | ||
binding = DataBindingUtil.setContentView<MainActivityBinding>(this, R.layout.main_activity).apply { | ||
lifecycleOwner = this@MainActivity | ||
vm = viewModel | ||
buttonCamera.setOnClickListener { | ||
if (ActivityCompat.checkSelfPermission(this@MainActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { | ||
startCamera() | ||
} else { | ||
requestPermission(PERMISSION_CAPTURE_REQUEST_CODE) | ||
} | ||
} | ||
} | ||
|
||
viewModel.event.observe(this) { | ||
when (it) { | ||
is UploadEvent.Result -> { | ||
generateToast("Copied: ${it.url}") | ||
|
||
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager | ||
clipboard.setPrimaryClip(ClipData.newPlainText("nuuls url", it.url)) | ||
} | ||
is UploadEvent.Error -> generateToast(it.message) | ||
} | ||
} | ||
|
||
// Handling the intent coming from other app | ||
val type = intent.type | ||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { | ||
// if im receiving an image from other app | ||
if (Intent.ACTION_SEND == intent.action && type != null) { | ||
handleSendImage(intent) | ||
} | ||
} else { | ||
requestPermission(PERMISSION_SHARE_REQUEST_CODE) | ||
} | ||
} | ||
|
||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { | ||
if (grantResults.getOrNull(0) == PackageManager.PERMISSION_GRANTED) { | ||
when (requestCode) { | ||
PERMISSION_SHARE_REQUEST_CODE -> { | ||
handleSendImage(intent) | ||
} | ||
PERMISSION_CAPTURE_REQUEST_CODE -> { | ||
startCamera() | ||
} | ||
} | ||
} | ||
} | ||
|
||
private fun requestPermission(code: Int) { | ||
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), code) | ||
} | ||
|
||
private fun startCamera() { | ||
Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { captureIntent -> | ||
captureIntent.resolveActivity(packageManager)?.also { | ||
try { | ||
MediaUtils.createImageFile(this).apply { currentImagePath = absolutePath } | ||
} catch (e: IOException) { | ||
null | ||
}?.also { | ||
val uri = FileProvider.getUriForFile(this, "$packageName.fileprovider", it) | ||
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri) | ||
startActivityForResult(captureIntent, CAMERA_REQUEST_CODE) | ||
} | ||
} | ||
} | ||
} | ||
|
||
private fun handleSendImage(intent: Intent) { | ||
if (intent.type?.startsWith("image/") == false) { | ||
return | ||
} | ||
|
||
// copy the shared image to a new file so we don't remove exif data form the original | ||
val uri = intent.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri ?: return | ||
val mimeType = contentResolver?.getType(uri) | ||
val mimeTypeMap = MimeTypeMap.getSingleton() | ||
val extension = mimeTypeMap.getExtensionFromMimeType(mimeType) ?: return | ||
val copy = MediaUtils.createImageFile(this, extension) | ||
|
||
try { | ||
contentResolver.openInputStream(uri)?.run { copy.outputStream().use { copyTo(it) } } | ||
if (copy.extension == "jpg" || copy.extension == "jpeg") { | ||
MediaUtils.removeExifAttributes(copy.absolutePath) | ||
} | ||
val copyUri = FileProvider.getUriForFile(this, "$packageName.fileprovider", copy) | ||
viewModel.setPath(copy.absolutePath, copyUri) | ||
} catch (e: IOException) { | ||
Log.e(TAG, Log.getStackTraceString(e)) | ||
copy.delete() | ||
} | ||
|
||
} | ||
|
||
// after getting the pic taken from the camera activity | ||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { | ||
super.onActivityResult(requestCode, resultCode, data) | ||
if (resultCode == Activity.RESULT_OK && requestCode == CAMERA_REQUEST_CODE) { | ||
val imageFile = File(currentImagePath) | ||
val uri = FileProvider.getUriForFile(this, "$packageName.fileprovider", imageFile) | ||
try { | ||
MediaUtils.removeExifAttributes(currentImagePath) | ||
viewModel.setPath(currentImagePath, uri) | ||
} catch (e: IOException) { | ||
Log.e(TAG, Log.getStackTraceString(e)) | ||
imageFile.delete() | ||
} | ||
} | ||
} | ||
|
||
private fun generateToast(text: String) { | ||
Toast.makeText(this, text, Toast.LENGTH_LONG).show() | ||
} | ||
|
||
companion object { | ||
private val TAG = MainActivity::class.java.simpleName | ||
private const val PERMISSION_CAPTURE_REQUEST_CODE = 1 | ||
private const val PERMISSION_SHARE_REQUEST_CODE = 2 | ||
private const val CAMERA_REQUEST_CODE = 2 | ||
} | ||
|
||
} |
43 changes: 43 additions & 0 deletions
43
app/src/main/java/com/nuuls/axel/nuulsimageuploader/MainViewModel.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package com.nuuls.axel.nuulsimageuploader | ||
|
||
import android.net.Uri | ||
import androidx.lifecycle.MutableLiveData | ||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.viewModelScope | ||
import kotlinx.coroutines.launch | ||
import java.io.File | ||
|
||
class MainViewModel : ViewModel() { | ||
private var path = "" | ||
|
||
val event = SingleLiveEvent<UploadEvent>() | ||
val urlLiveData = MutableLiveData("") | ||
val uriLiveData = MutableLiveData<Uri>() | ||
val uploadLiveData = MutableLiveData(false) | ||
|
||
fun upload() { | ||
if (path.isEmpty() || uploadLiveData.value == true) { | ||
event.postValue(UploadEvent.Error("Take a picture first :D")) | ||
return | ||
} | ||
uploadLiveData.postValue(true) | ||
viewModelScope.launch { | ||
val result = NuulsUploader.upload(File(path)) | ||
if (result != null) { | ||
urlLiveData.postValue(result) | ||
event.postValue(UploadEvent.Result(result)) | ||
} else { | ||
event.postValue(UploadEvent.Error("ERROR")) | ||
} | ||
path = "" | ||
uriLiveData.postValue(null) | ||
uploadLiveData.postValue(false) | ||
} | ||
} | ||
|
||
fun setPath(path: String, uri: Uri) { | ||
this.path = path | ||
this.urlLiveData.value = "" | ||
this.uriLiveData.value = uri | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
app/src/main/java/com/nuuls/axel/nuulsimageuploader/MediaUtils.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package com.nuuls.axel.nuulsimageuploader | ||
|
||
import android.content.Context | ||
import android.os.Environment | ||
import androidx.exifinterface.media.ExifInterface | ||
import java.io.File | ||
import java.io.IOException | ||
import java.text.SimpleDateFormat | ||
import java.util.* | ||
|
||
object MediaUtils { | ||
private val GPS_ATTRIBUTES = listOf( | ||
ExifInterface.TAG_GPS_VERSION_ID, | ||
ExifInterface.TAG_GPS_AREA_INFORMATION, | ||
ExifInterface.TAG_GPS_LATITUDE, | ||
ExifInterface.TAG_GPS_LATITUDE_REF, | ||
ExifInterface.TAG_GPS_LONGITUDE, | ||
ExifInterface.TAG_GPS_LONGITUDE_REF, | ||
ExifInterface.TAG_GPS_ALTITUDE, | ||
ExifInterface.TAG_GPS_ALTITUDE_REF, | ||
ExifInterface.TAG_GPS_TIMESTAMP, | ||
ExifInterface.TAG_GPS_DATESTAMP | ||
) | ||
|
||
@Throws(IOException::class) | ||
fun createImageFile(context: Context, suffix: String = "jpg"): File { | ||
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date()) | ||
val storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) | ||
return File.createTempFile(timeStamp, ".$suffix", storageDir) | ||
} | ||
|
||
@Throws(IOException::class) | ||
fun removeExifAttributes(path: String) { | ||
ExifInterface(path).run { | ||
GPS_ATTRIBUTES.forEach { if (getAttribute(it) != null) setAttribute(it, null) } | ||
saveAttributes() | ||
} | ||
} | ||
} |
40 changes: 40 additions & 0 deletions
40
app/src/main/java/com/nuuls/axel/nuulsimageuploader/NuulsUploader.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package com.nuuls.axel.nuulsimageuploader | ||
|
||
import android.util.Log | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.withContext | ||
import okhttp3.MediaType.Companion.toMediaType | ||
import okhttp3.MultipartBody | ||
import okhttp3.OkHttpClient | ||
import okhttp3.Request | ||
import okhttp3.RequestBody.Companion.asRequestBody | ||
import java.io.File | ||
|
||
object NuulsUploader { | ||
private val client = OkHttpClient() | ||
private val MEDIA_TYPE = "image/png".toMediaType() | ||
private const val URL = "https://i.nuuls.com/upload" | ||
private val TAG = NuulsUploader::class.java.simpleName | ||
|
||
suspend fun upload(file: File): String? = withContext(Dispatchers.IO) { | ||
val requestBody = MultipartBody.Builder() | ||
.setType(MultipartBody.FORM) | ||
.addFormDataPart("xd", "justNameItXD.png", file.asRequestBody(MEDIA_TYPE)) | ||
.build() | ||
val request = Request.Builder() | ||
.url(URL) | ||
.post(requestBody) | ||
.build() | ||
|
||
try { | ||
val response = client.newCall(request).execute() | ||
if (response.isSuccessful) { | ||
return@withContext response.body?.string() | ||
} | ||
} catch (t: Throwable) { | ||
Log.e(TAG, Log.getStackTraceString(t)) | ||
} | ||
|
||
return@withContext null | ||
} | ||
} |
34 changes: 34 additions & 0 deletions
34
app/src/main/java/com/nuuls/axel/nuulsimageuploader/SingleLiveEvent.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package com.nuuls.axel.nuulsimageuploader | ||
|
||
import androidx.annotation.MainThread | ||
import androidx.lifecycle.LifecycleOwner | ||
import androidx.lifecycle.MutableLiveData | ||
import androidx.lifecycle.Observer | ||
import java.util.concurrent.atomic.AtomicBoolean | ||
|
||
class SingleLiveEvent<T> : MutableLiveData<T>() { | ||
private val pending = AtomicBoolean(false) | ||
|
||
@MainThread | ||
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) { | ||
observe(owner, observer::onChanged) | ||
} | ||
|
||
@MainThread | ||
fun observe(owner: LifecycleOwner, observer: (T) -> Unit) { | ||
super.observe(owner, PendingObserver(observer)) | ||
} | ||
|
||
override fun setValue(value: T) { | ||
pending.set(true) | ||
super.setValue(value) | ||
} | ||
|
||
private inner class PendingObserver<T>(private val observer: (T) -> Unit) : Observer<T> { | ||
override fun onChanged(t: T) { | ||
if (pending.compareAndSet(true, false)) { | ||
observer(t) | ||
} | ||
} | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
app/src/main/java/com/nuuls/axel/nuulsimageuploader/UploadEvent.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.nuuls.axel.nuulsimageuploader | ||
|
||
sealed class UploadEvent { | ||
data class Error(val message: String) : UploadEvent() | ||
data class Result(val url: String) : UploadEvent() | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<layout xmlns:android="http://schemas.android.com/apk/res/android" | ||
xmlns:app="http://schemas.android.com/apk/res-auto" | ||
xmlns:tools="http://schemas.android.com/tools"> | ||
|
||
<data> | ||
|
||
<variable | ||
name="vm" | ||
type="com.nuuls.axel.nuulsimageuploader.MainViewModel" /> | ||
</data> | ||
|
||
<androidx.constraintlayout.widget.ConstraintLayout | ||
android:layout_width="match_parent" | ||
android:layout_height="match_parent" | ||
android:background="@color/main_background" | ||
tools:context=".MainActivity"> | ||
|
||
<ImageView | ||
android:id="@+id/imageView" | ||
android:layout_width="match_parent" | ||
android:layout_height="0dp" | ||
android:layout_marginLeft="5dp" | ||
android:layout_marginRight="5dp" | ||
android:layout_marginBottom="5dp" | ||
android:contentDescription="@string/image_preview" | ||
android:scaleType="centerInside" | ||
app:layout_constraintBottom_toTopOf="@id/imageUrl" | ||
app:layout_constraintEnd_toEndOf="parent" | ||
app:layout_constraintStart_toStartOf="parent" | ||
app:layout_constraintTop_toTopOf="parent" | ||
app:preview="@{vm.uriLiveData}" /> | ||
|
||
<androidx.appcompat.widget.AppCompatTextView | ||
android:id="@+id/imageUrl" | ||
android:layout_width="match_parent" | ||
android:layout_height="wrap_content" | ||
android:gravity="center" | ||
android:text="@{vm.urlLiveData}" | ||
android:textColor="@android:color/white" | ||
app:layout_constraintBottom_toTopOf="@id/buttonCamera" | ||
app:layout_constraintEnd_toEndOf="parent" | ||
app:layout_constraintStart_toStartOf="parent" | ||
app:layout_constraintTop_toBottomOf="@id/imageView" /> | ||
|
||
<Button | ||
android:id="@+id/buttonCamera" | ||
android:layout_width="match_parent" | ||
android:layout_height="wrap_content" | ||
android:layout_marginLeft="10dp" | ||
android:layout_marginRight="10dp" | ||
android:layout_marginBottom="5dp" | ||
android:background="@color/camera_background" | ||
android:enabled="@{!vm.uploadLiveData}" | ||
android:text="@string/open_camera" | ||
android:textColor="@android:color/white" | ||
app:layout_constraintBottom_toTopOf="@id/buttonUpload" | ||
app:layout_constraintEnd_toEndOf="parent" | ||
app:layout_constraintStart_toStartOf="parent" | ||
app:layout_constraintTop_toBottomOf="@id/imageUrl" /> | ||
|
||
<Button | ||
android:id="@+id/buttonUpload" | ||
android:layout_width="match_parent" | ||
android:layout_height="wrap_content" | ||
android:layout_marginLeft="10dp" | ||
android:layout_marginRight="10dp" | ||
android:layout_marginBottom="5dp" | ||
android:enabled="@{!vm.uploadLiveData}" | ||
android:onClick="@{() -> vm.upload()}" | ||
android:textColor="@android:color/white" | ||
app:layout_constraintBottom_toBottomOf="parent" | ||
app:layout_constraintEnd_toEndOf="parent" | ||
app:layout_constraintStart_toStartOf="parent" | ||
app:layout_constraintTop_toBottomOf="@id/buttonCamera" | ||
app:state="@{vm.uploadLiveData}" /> | ||
|
||
|
||
</androidx.constraintlayout.widget.ConstraintLayout> | ||
</layout> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
<paths> | ||
<external-path name="Pictures" path="Pictures"/> | ||
<external-path | ||
name="Pictures" | ||
path="Android/data/com.nuuls.axel.nuulsimageuploader/files/Pictures" /> | ||
</paths> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters