Image rotation saving feature & navigation buttons

This commit is contained in:
Hardcore Sushi 2020-09-30 16:19:16 +02:00
parent 0ca5bbcbb4
commit 646a194dd7
16 changed files with 146 additions and 119 deletions

View File

@ -4,7 +4,7 @@ apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 29
buildToolsVersion "30.0.0"
buildToolsVersion "30.0.2"
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
@ -14,8 +14,8 @@ android {
applicationId "sushi.hardcore.droidfs"
minSdkVersion 21
targetSdkVersion 29
versionCode 2
versionName "1.1.7"
versionCode 3
versionName "1.1.8"
ndk {
abiFilters 'x86_64', 'armeabi-v7a', 'arm64-v8a'

View File

@ -13,9 +13,9 @@ import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_change_password.*
import kotlinx.android.synthetic.main.activity_change_password.checkbox_remember_path
import kotlinx.android.synthetic.main.activity_change_password.checkbox_save_password
import kotlinx.android.synthetic.main.activity_change_password.edit_volume_path
import kotlinx.android.synthetic.main.activity_change_password.saved_path_listview
import kotlinx.android.synthetic.main.toolbar.*
import kotlinx.android.synthetic.main.volume_path_section.*
import sushi.hardcore.droidfs.adapters.SavedVolumesAdapter
import sushi.hardcore.droidfs.fingerprint_stuff.FingerprintPasswordHashSaver
import sushi.hardcore.droidfs.util.*

View File

@ -9,6 +9,7 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_create.*
import kotlinx.android.synthetic.main.toolbar.*
import kotlinx.android.synthetic.main.volume_path_section.*
import sushi.hardcore.droidfs.explorers.ExplorerActivity
import sushi.hardcore.droidfs.fingerprint_stuff.FingerprintPasswordHashSaver
import sushi.hardcore.droidfs.util.*
@ -16,7 +17,6 @@ import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
import java.io.File
import java.util.*
class CreateActivity : BaseActivity() {
companion object {
private const val PICK_DIRECTORY_REQUEST_CODE = 1

View File

@ -9,15 +9,9 @@ import android.view.View
import android.widget.AdapterView.OnItemClickListener
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_change_password.*
import kotlinx.android.synthetic.main.activity_create.*
import kotlinx.android.synthetic.main.activity_open.*
import kotlinx.android.synthetic.main.activity_open.checkbox_remember_path
import kotlinx.android.synthetic.main.activity_open.checkbox_save_password
import kotlinx.android.synthetic.main.activity_open.edit_password
import kotlinx.android.synthetic.main.activity_open.edit_volume_path
import kotlinx.android.synthetic.main.activity_open.saved_path_listview
import kotlinx.android.synthetic.main.toolbar.*
import kotlinx.android.synthetic.main.volume_path_section.*
import sushi.hardcore.droidfs.adapters.SavedVolumesAdapter
import sushi.hardcore.droidfs.explorers.ExplorerActivity
import sushi.hardcore.droidfs.explorers.ExplorerActivityDrop

View File

@ -287,7 +287,7 @@ open class BaseExplorerActivity : BaseActivity() {
dialogEditText.selectAll()
val dialog = ColoredAlertDialogBuilder(this)
.setView(dialogEditTextView)
.setTitle(getString(R.string.enter_new_name))
.setTitle(R.string.enter_new_name)
.setPositiveButton(R.string.ok) { _, _ ->
handler.sendMessage(Message().apply { obj = checkPathOverwrite(PathUtils.path_join(PathUtils.getParentPath(path), dialogEditText.text.toString()), isDirectory) })
}
@ -302,7 +302,7 @@ open class BaseExplorerActivity : BaseActivity() {
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
dialog.show()
}
.setPositiveButton(R.string.yes) {_, _ -> handler.sendMessage(Message().apply { obj = path }) }
.setPositiveButton(R.string.yes) { _, _ -> handler.sendMessage(Message().apply { obj = path }) }
.create()
dialog.setOnCancelListener { handler.sendMessage(Message().apply { obj = null }) }
dialog.show()

View File

@ -22,8 +22,8 @@ abstract class FileViewerActivity: BaseActivity() {
}
open fun hideSystemUi(){
window.decorView.systemUiVisibility =
View.SYSTEM_UI_FLAG_LOW_PROFILE or
View.SYSTEM_UI_FLAG_FULLSCREEN/* or
View.SYSTEM_UI_FLAG_LOW_PROFILE or
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or

View File

@ -21,6 +21,8 @@ import sushi.hardcore.droidfs.util.MiscUtils
import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
import sushi.hardcore.droidfs.widgets.ZoomableImageView
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.security.MessageDigest
import kotlin.math.abs
@ -40,11 +42,20 @@ class ImageViewer: FileViewerActivity() {
private var slideshowActive = false
private var currentMappedImageIndex = -1
private var rotationAngle: Float = 0F
private var rotatedBitmap: Bitmap? = null
private val handler = Handler()
private val hideUI = Runnable {
action_buttons.visibility = View.GONE
action_bar.visibility = View.GONE
}
private val slideshowLoop = object : Runnable {
override fun run() {
if (slideshowActive){
swipeImage(-1F)
handler.postDelayed(this, ConstValues.slideshow_delay)
}
}
}
override fun viewFile() {
setContentView(R.layout.activity_image_viewer)
image_viewer.setOnInteractionListener(object : ZoomableImageView.OnInteractionListener {
@ -69,7 +80,7 @@ class ImageViewer: FileViewerActivity() {
x2 = event.x
val deltaX = x2 - x1
if (abs(deltaX) > MIN_SWIPE_DISTANCE) {
swipeImage(deltaX)
askSaveRotation { swipeImage(deltaX) }
}
}
}
@ -149,13 +160,7 @@ class ImageViewer: FileViewerActivity() {
fun onClickSlideshow(view: View) {
if (!slideshowActive){
slideshowActive = true
Thread {
Thread.sleep(ConstValues.slideshow_delay)
while (slideshowActive){
runOnUiThread { swipeImage(-1F) }
Thread.sleep(ConstValues.slideshow_delay)
}
}.start()
handler.postDelayed(slideshowLoop, ConstValues.slideshow_delay)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
hideUI.run()
Toast.makeText(this, R.string.slideshow_started, Toast.LENGTH_SHORT).show()
@ -174,26 +179,27 @@ class ImageViewer: FileViewerActivity() {
if (slideshowActive){
stopSlideshow()
} else {
super.onBackPressed()
askSaveRotation { super.onBackPressed() }
}
}
class RotateTransformation(private val rotationAngle: Float): BitmapTransformation() {
class RotateTransformation(private val imageViewer: ImageViewer): BitmapTransformation() {
override fun transform(pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap {
override fun transform(pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap? {
val matrix = Matrix()
matrix.postRotate(rotationAngle)
return Bitmap.createBitmap(toTransform, 0, 0, toTransform.width, toTransform.height, matrix, true)
matrix.postRotate(imageViewer.rotationAngle)
imageViewer.rotatedBitmap = Bitmap.createBitmap(toTransform, 0, 0, toTransform.width, toTransform.height, matrix, true)
return imageViewer.rotatedBitmap
}
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
messageDigest.update("rotate$rotationAngle".toByteArray())
messageDigest.update("rotate${imageViewer.rotationAngle}".toByteArray())
}
}
private fun rotateImage(){
image_viewer.restoreZoomNormal()
glideImage.transform(RotateTransformation(rotationAngle)).into(image_viewer)
glideImage.transform(RotateTransformation(this)).into(image_viewer)
}
fun onCLickRotateRight(view: View){
rotationAngle += 90
@ -204,6 +210,55 @@ class ImageViewer: FileViewerActivity() {
rotateImage()
}
fun onClickPrevious(view: View){
askSaveRotation { swipeImage(1F) }
}
fun onClickNext(view: View){
askSaveRotation { swipeImage(-1F) }
}
private fun askSaveRotation(callback: () -> Unit){
if (rotationAngle%360 != 0f){
ColoredAlertDialogBuilder(this)
.keepFullScreen()
.setTitle(R.string.warning)
.setMessage(R.string.ask_save_img_rotated)
.setNegativeButton(R.string.no) { _, _ -> callback() }
.setPositiveButton(R.string.yes) { _, _ ->
val outputStream = ByteArrayOutputStream()
if (rotatedBitmap?.compress(
if (fileName.endsWith("png", true)){
Bitmap.CompressFormat.PNG
} else {
Bitmap.CompressFormat.JPEG
}, 100, outputStream) == true
){
if (gocryptfsVolume.importFile(ByteArrayInputStream(outputStream.toByteArray()), filePath)){
Toast.makeText(this, R.string.image_saved_successfully, Toast.LENGTH_SHORT).show()
callback()
} else {
ColoredAlertDialogBuilder(this)
.keepFullScreen()
.setTitle(R.string.error)
.setMessage(R.string.file_write_failed)
.setPositiveButton(R.string.ok, null)
.show()
}
} else {
ColoredAlertDialogBuilder(this)
.keepFullScreen()
.setTitle(R.string.error)
.setMessage(R.string.bitmap_compress_failed)
.setPositiveButton(R.string.ok, null)
.show()
}
}
.show()
} else {
callback()
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
image_viewer.restoreZoomNormal()

View File

@ -3,7 +3,9 @@ package sushi.hardcore.droidfs.util
import android.content.Context
import android.net.Uri
import sushi.hardcore.droidfs.explorers.ExplorerElement
import java.io.*
import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream
class GocryptfsVolume(var sessionID: Int) {
private external fun native_close(sessionID: Int)

View File

@ -2,6 +2,7 @@ package sushi.hardcore.droidfs.widgets
import androidx.appcompat.app.AlertDialog
import android.content.Context
import android.view.View
import android.view.WindowManager
class ColoredAlertDialogBuilder: AlertDialog.Builder {
@ -27,6 +28,10 @@ class ColoredAlertDialogBuilder: AlertDialog.Builder {
override fun show(): AlertDialog {
val dialog = create()
dialog.show()
if (keepFullScreen){
dialog.window?.decorView?.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN
dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
}
return dialog
}

View File

@ -16,34 +16,7 @@
android:orientation="vertical"
android:padding="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="@dimen/change_password_activity_label_width"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="@string/volume_path"
android:textSize="@dimen/edit_text_label_text_size" />
<sushi.hardcore.droidfs.widgets.ColoredEditText
android:id="@+id/edit_volume_path"
android:layout_width="0dp"
android:layout_weight="0.5"
android:layout_height="wrap_content"
android:inputType="text"
android:maxLines="1"/>
<sushi.hardcore.droidfs.widgets.ColoredImageButton
android:layout_width="40dp"
android:layout_height="40dp"
android:onClick="pickDirectory"
android:scaleType="fitCenter"
android:background="#00000000"
android:src="@drawable/icon_folder" />
</LinearLayout>
<include layout="@layout/volume_path_section"/>
<LinearLayout
android:layout_width="match_parent"

View File

@ -16,34 +16,7 @@
android:orientation="vertical"
android:padding="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="@dimen/create_activity_label_width"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="@string/volume_path"
android:textSize="@dimen/edit_text_label_text_size" />
<sushi.hardcore.droidfs.widgets.ColoredEditText
android:id="@+id/edit_volume_path"
android:layout_width="0dp"
android:layout_weight="0.5"
android:layout_height="wrap_content"
android:inputType="text"
android:maxLines="1"/>
<sushi.hardcore.droidfs.widgets.ColoredImageButton
android:layout_width="40dp"
android:layout_height="40dp"
android:onClick="pickDirectory"
android:scaleType="fitCenter"
android:background="#00000000"
android:src="@drawable/icon_folder" />
</LinearLayout>
<include layout="@layout/volume_path_section"/>
<LinearLayout
android:layout_width="match_parent"

View File

@ -65,8 +65,17 @@
android:gravity="center">
<ImageButton
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_width="@dimen/image_button_size"
android:layout_height="@dimen/image_button_size"
android:layout_margin="10dp"
android:scaleType="fitCenter"
android:background="#00000000"
android:src="@drawable/exo_icon_previous"
android:onClick="onClickPrevious"/>
<ImageButton
android:layout_width="@dimen/image_button_size"
android:layout_height="@dimen/image_button_size"
android:layout_margin="10dp"
android:scaleType="fitCenter"
android:background="#00000000"
@ -74,14 +83,23 @@
android:onClick="onClickRotateLeft"/>
<ImageButton
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_width="@dimen/image_button_size"
android:layout_height="@dimen/image_button_size"
android:layout_margin="10dp"
android:scaleType="fitCenter"
android:background="#00000000"
android:src="@drawable/icon_rotate_right"
android:onClick="onCLickRotateRight"/>
<ImageButton
android:layout_width="@dimen/image_button_size"
android:layout_height="@dimen/image_button_size"
android:layout_margin="10dp"
android:scaleType="fitCenter"
android:background="#00000000"
android:src="@drawable/exo_icon_next"
android:onClick="onClickNext"/>
</LinearLayout>
</RelativeLayout>

View File

@ -16,34 +16,7 @@
android:padding="10dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="@dimen/open_activity_label_width"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="@string/volume_path"
android:textSize="@dimen/edit_text_label_text_size" />
<sushi.hardcore.droidfs.widgets.ColoredEditText
android:id="@+id/edit_volume_path"
android:layout_width="0dp"
android:layout_weight="0.5"
android:layout_height="wrap_content"
android:inputType="text"
android:maxLines="1"/>
<sushi.hardcore.droidfs.widgets.ColoredImageButton
android:layout_width="40dp"
android:layout_height="40dp"
android:onClick="pickDirectory"
android:scaleType="fitCenter"
android:background="#00000000"
android:src="@drawable/icon_folder"/>
</LinearLayout>
<include layout="@layout/volume_path_section"/>
<LinearLayout
android:layout_width="match_parent"

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="@dimen/create_activity_label_width"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="@string/volume_path"
android:textSize="@dimen/edit_text_label_text_size" />
<sushi.hardcore.droidfs.widgets.ColoredEditText
android:id="@+id/edit_volume_path"
android:layout_width="0dp"
android:layout_weight="0.5"
android:layout_height="wrap_content"
android:inputType="text"
android:maxLines="1"/>
<sushi.hardcore.droidfs.widgets.ColoredImageButton
android:layout_width="@dimen/image_button_size"
android:layout_height="@dimen/image_button_size"
android:onClick="pickDirectory"
android:scaleType="fitCenter"
android:background="#00000000"
android:src="@drawable/icon_folder" />
</LinearLayout>

View File

@ -14,4 +14,5 @@
<dimen name="warning_msg_padding">20dp</dimen>
<dimen name="action_activity_button_margin_bottom">20dp</dimen>
<dimen name="adapter_text_size">18sp</dimen>
<dimen name="image_button_size">40dp</dimen>
</resources>

View File

@ -173,4 +173,8 @@
<string name="change_pwd_on_sdcard_error_msg">DroidFS can\'t write on removable SD cards, please move the volume to internal storage.</string>
<string name="slideshow_stopped">Slideshow stopped</string>
<string name="slideshow_started">Slideshow started</string>
<string name="ask_save_img_rotated">The image has been rotated. Do you want to save these changes and overwrite the original image ?</string>
<string name="image_saved_successfully">Image changes successfully saved.</string>
<string name="bitmap_compress_failed">Failed to compress the bitmap.</string>
<string name="file_write_failed">Failed to write the file.</string>
</resources>