Skip to content

Commit

Permalink
feat(image processing): use fab with shared element transition
Browse files Browse the repository at this point in the history
  • Loading branch information
Tommy-Geenexus committed Aug 4, 2024
1 parent 73f1547 commit 4c62d30
Show file tree
Hide file tree
Showing 64 changed files with 953 additions and 1,086 deletions.
7 changes: 1 addition & 6 deletions app/config/baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
<ID>EmptyFunctionBlock:MainItemTouchHelperCallback.kt$MainItemTouchHelperCallback${ }</ID>
<ID>EmptyFunctionBlock:MainItemTouchUiUtil.kt$MainItemTouchUiUtil${ }</ID>
<ID>LargeClass:MainViewModelTest.kt$MainViewModelTest</ID>
<ID>LongMethod:ImageProcessingReportFragment.kt$ImageProcessingReportFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
<ID>LongMethod:ImageProcessingReportFragment.kt$ImageProcessingReportFragment$private fun handleReportSlide(slideOffset: Float, translationMax: Float)</ID>
<ID>LongMethod:ImageProcessingRepository.kt$ImageProcessingRepository$private suspend fun removeMetaData( proto: UserImageSelectionProto, treeUri: Uri, displayNameSuffix: String, isAutoDeleteEnabled: Boolean, isPreserveOrientationEnabled: Boolean, isRandomizeFileNamesEnabled: Boolean, progress: ImageProcessingProgress ): ImageProcessingStep.FinishedSingle</ID>
<ID>LongMethod:ImageProcessingViewModelTest.kt$ImageProcessingViewModelTest$@Test fun test_handleUserImagesSelection()</ID>
<ID>LongMethod:MainFragment.kt$MainFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?)</ID>
Expand All @@ -20,11 +18,8 @@
<ID>MagicNumber:FileUtilsKt.kt$FileUtilsKt$255</ID>
<ID>MagicNumber:FileUtilsKt.kt$FileUtilsKt$3</ID>
<ID>MagicNumber:HelpAdapter.kt$HelpAdapter$3</ID>
<ID>ReturnCount:AnimationUtils.kt$@ColorInt fun lerpArgb( @ColorInt startColor: Int, @ColorInt endColor: Int, @FloatRange( from = 0.0, fromInclusive = true, to = 1.0, toInclusive = true ) startFraction: Float, @FloatRange(from = 0.0, fromInclusive = true, to = 1.0, toInclusive = true) endFraction: Float, @FloatRange(from = 0.0, fromInclusive = true, to = 1.0, toInclusive = true) fraction: Float ): Int</ID>
<ID>ReturnCount:AnimationUtils.kt$fun lerp( startValue: Float, endValue: Float, @FloatRange( from = 0.0, fromInclusive = true, to = 1.0, toInclusive = true ) startFraction: Float, @FloatRange(from = 0.0, fromInclusive = true, to = 1.0, toInclusive = true) endFraction: Float, @FloatRange(from = 0.0, fromInclusive = true, to = 1.0, toInclusive = true) fraction: Float ): Float</ID>
<ID>TooGenericExceptionCaught:Extensions.kt$exception: Exception</ID>
<ID>TooGenericExceptionCaught:CoroutineExtensions.kt$exception: Exception</ID>
<ID>TooGenericExceptionCaught:HelpViewHolder.kt$HelpViewHolder$e: Exception</ID>
<ID>TooManyFunctions:ImageProcessingReportFragment.kt$ImageProcessingReportFragment : BaseFragmentListener</ID>
<ID>TooManyFunctions:MainFragment.kt$MainFragment : BaseFragmentListener</ID>
<ID>TooManyFunctions:MainViewModel.kt$MainViewModel : ContainerHostViewModel</ID>
<ID>TooManyFunctions:SettingsAdapter.kt$SettingsAdapter$Listener</ID>
Expand Down
24 changes: 4 additions & 20 deletions app/src/main/kotlin/com/none/tom/exiferaser/ExifEraserActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,10 @@ import android.os.Build
import android.os.Bundle
import android.util.TypedValue
import android.view.View
import android.widget.FrameLayout
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.navigation.fragment.NavHostFragment
import androidx.window.core.layout.WindowSizeClass
import androidx.window.core.layout.WindowWidthSizeClass
Expand All @@ -44,21 +41,14 @@ import com.none.tom.exiferaser.core.util.INTENT_ACTION_CHOOSE_IMAGES
import com.none.tom.exiferaser.core.util.INTENT_ACTION_CHOOSE_IMAGE_DIR
import com.none.tom.exiferaser.core.util.INTENT_ACTION_LAUNCH_CAM
import com.none.tom.exiferaser.core.util.INTENT_EXTRA_CONSUMED
import com.none.tom.exiferaser.core.util.NAV_ARG_IMAGE_SELECTION
import com.none.tom.exiferaser.core.util.NAV_ARG_SHORTCUT
import com.none.tom.exiferaser.databinding.ActivityExifEraserBinding
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class ExifEraserActivity : AppCompatActivity() {

companion object {

// See MainFragmentArgs
private const val KEY_IMAGES_SELECTION = "images_selection"

// See MainFragmentArgs
private const val KEY_SHORTCUT = "shortcut"
}

lateinit var windowSizeClass: WindowSizeClass

init {
Expand Down Expand Up @@ -91,11 +81,6 @@ class ExifEraserActivity : AppCompatActivity() {
}
}
}
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsetsCompat ->
val insets = windowInsetsCompat.getInsets(WindowInsetsCompat.Type.statusBars())
(view.layoutParams as FrameLayout.LayoutParams).topMargin = insets.top
windowInsetsCompat
}
binding.layout.addView(
object : View(this) {
override fun onConfigurationChanged(newConfig: Configuration?) {
Expand Down Expand Up @@ -139,17 +124,16 @@ class ExifEraserActivity : AppCompatActivity() {
.navController
.navigate(
R.id.global_to_main,
bundleOf(KEY_IMAGES_SELECTION to uris.toTypedArray())
bundleOf(NAV_ARG_IMAGE_SELECTION to uris.toTypedArray())
)
}
} else if (shortcutIntentActions.contains(intent.action)) {
val args = bundleOf(KEY_SHORTCUT to intent.action)
(supportFragmentManager.findFragmentById(R.id.nav_controller) as NavHostFragment)
.navController
.createDeepLink()
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.fragment_main)
.setArguments(args)
.setArguments(bundleOf(NAV_ARG_SHORTCUT to intent.action))
.createTaskStackBuilder()
.startActivities()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ import androidx.datastore.preferences.SharedPreferencesMigration
import androidx.datastore.preferences.preferencesDataStore
import com.none.tom.exiferaser.core.serializer.ImageSourcesSerializer
import com.none.tom.exiferaser.core.serializer.SelectionSerializer
import com.none.tom.exiferaser.settings.data.SettingsRepository
import com.none.tom.exiferaser.core.util.SETTINGS_KEY_DEFAULT_DISPLAY_NAME_SUFFIX
import com.none.tom.exiferaser.core.util.SETTINGS_KEY_DEFAULT_OPEN_PATH
import com.none.tom.exiferaser.core.util.SETTINGS_KEY_DEFAULT_SAVE_PATH
import com.none.tom.exiferaser.core.util.SETTINGS_KEY_PRESERVE_ORIENTATION
import com.none.tom.exiferaser.core.util.SETTINGS_KEY_SHARE_BY_DEFAULT
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
Expand Down Expand Up @@ -67,11 +71,11 @@ private val Context.settingsDataStore by preferencesDataStore(
context = context,
sharedPreferencesName = context.packageName + "_preferences",
keysToMigrate = setOf(
SettingsRepository.KEY_DEFAULT_OPEN_PATH,
SettingsRepository.KEY_DEFAULT_SAVE_PATH,
SettingsRepository.KEY_PRESERVE_ORIENTATION,
SettingsRepository.KEY_SHARE_BY_DEFAULT,
SettingsRepository.KEY_DEFAULT_DISPLAY_NAME_SUFFIX
SETTINGS_KEY_DEFAULT_OPEN_PATH,
SETTINGS_KEY_DEFAULT_SAVE_PATH,
SETTINGS_KEY_PRESERVE_ORIENTATION,
SETTINGS_KEY_SHARE_BY_DEFAULT,
SETTINGS_KEY_DEFAULT_DISPLAY_NAME_SUFFIX
)
)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2024, Tom Geiselmann (tomgapplicationsdevelopment@gmail.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY,WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package com.none.tom.exiferaser.core.extension

import android.app.Activity
import android.content.Context
import android.os.Build
import android.util.TypedValue
import androidx.annotation.AttrRes
import androidx.appcompat.app.AppCompatDelegate
import com.none.tom.exiferaser.R

fun Activity.resolveThemeAttribute(@AttrRes attrRes: Int): Int {
val tv = TypedValue()
theme.resolveAttribute(attrRes, tv, true)
return tv.data
}

fun Context.defaultNightModes() = mutableMapOf(
AppCompatDelegate.MODE_NIGHT_YES to getString(R.string.always),
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM to getString(R.string.automatically),
AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY to getString(R.string.low_batt_only),
AppCompatDelegate.MODE_NIGHT_NO to getString(R.string.never)
).apply {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
remove(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
}.toMap()

fun Context.defaultNightModeDisplayValue() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
getString(R.string.automatically)
} else {
getString(R.string.never)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@

package com.none.tom.exiferaser.core.extension

import kotlinx.coroutines.ensureActive
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.ensureActive

suspend fun <T> CoroutineContext.suspendRunCatching(block: suspend () -> T): Result<T> = try {
Result.success(block())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright (c) 2024, Tom Geiselmann (tomgapplicationsdevelopment@gmail.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or
* substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY,WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package com.none.tom.exiferaser.core.extension

import android.graphics.drawable.Animatable
import android.graphics.drawable.Drawable
import androidx.annotation.DrawableRes
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.findViewTreeLifecycleOwner
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.google.android.material.floatingactionbutton.FloatingActionButton

inline fun FloatingActionButton.addIconAnimation(
@DrawableRes animatedVectorDrawable: Int,
@DrawableRes animatedVectorDrawableInverse: Int,
crossinline showAnimatedVectorDrawableCondition: () -> Boolean
) {
setImageResource(animatedVectorDrawable)
val callback = object : Animatable2Compat.AnimationCallback() {

override fun onAnimationEnd(drawableEnd: Drawable?) {
AnimatedVectorDrawableCompat.unregisterAnimationCallback(drawableEnd, this)
setImageResource(
if (showAnimatedVectorDrawableCondition()) {
animatedVectorDrawable
} else {
animatedVectorDrawableInverse
}
)
AnimatedVectorDrawableCompat.registerAnimationCallback(drawable, this)
}
}
val lifecycle = findViewTreeLifecycleOwner()?.lifecycle
lifecycle?.addObserver(
object : LifecycleEventObserver {

override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
when (event) {
Lifecycle.Event.ON_START -> {
AnimatedVectorDrawableCompat.registerAnimationCallback(
drawable,
callback
)
}
Lifecycle.Event.ON_STOP -> {
if (drawable is Animatable) {
(drawable as Animatable).stop()
}
AnimatedVectorDrawableCompat.unregisterAnimationCallback(
drawable,
callback
)
}
Lifecycle.Event.ON_DESTROY -> {
lifecycle.removeObserver(this)
}
else -> {
}
}
}
}
)
}

fun ItemTouchHelper.attachTo(recyclerView: RecyclerView) {
val lifecycle = recyclerView.findViewTreeLifecycleOwner()?.lifecycle
lifecycle?.addObserver(
object : LifecycleEventObserver {

override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
when (event) {
Lifecycle.Event.ON_START -> {
attachToRecyclerView(recyclerView)
}
Lifecycle.Event.ON_STOP -> {
attachToRecyclerView(null)
}
Lifecycle.Event.ON_DESTROY -> {
lifecycle.removeObserver(this)
}
else -> {
}
}
}
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,14 @@ package com.none.tom.exiferaser.core.ui

import android.os.Build
import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.NavDirections
import androidx.navigation.fragment.findNavController
import androidx.viewbinding.ViewBinding
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.none.tom.exiferaser.R
import com.none.tom.exiferaser.core.extension.resolveThemeAttribute
import curtains.phoneWindow

abstract class BaseBottomSheetDialogFragment<B : ViewBinding> : BottomSheetDialogFragment() {
Expand All @@ -52,9 +50,8 @@ abstract class BaseBottomSheetDialogFragment<B : ViewBinding> : BottomSheetDialo
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
val tv = TypedValue()
requireActivity().theme.resolveAttribute(R.attr.colorSurfaceContainerLow, tv, true)
view.phoneWindow?.navigationBarColor = tv.data
view.phoneWindow?.navigationBarColor =
requireActivity().resolveThemeAttribute(R.attr.colorSurfaceContainerLow)
}
with(requireDialog()) {
setOnShowListener {
Expand All @@ -72,12 +69,4 @@ abstract class BaseBottomSheetDialogFragment<B : ViewBinding> : BottomSheetDialo
}

abstract fun inflateLayout(inflater: LayoutInflater, container: ViewGroup?): B

internal fun navigate(navDirections: NavDirections) {
val navController = findNavController()
val action = navController.currentDestination?.getAction(navDirections.actionId)
if (action != null && action.destinationId != 0) {
navController.navigate(navDirections)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,11 @@ package com.none.tom.exiferaser.core.ui

import android.os.Bundle
import android.view.View
import android.widget.FrameLayout
import androidx.annotation.LayoutRes
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.Fragment
import androidx.navigation.NavDirections
import androidx.navigation.fragment.findNavController
import androidx.viewbinding.ViewBinding
import com.none.tom.exiferaser.ExifEraserActivity
Expand All @@ -56,11 +52,9 @@ abstract class BaseFragment<B : ViewBinding>(

protected fun getWindowSizeClass() = (requireActivity() as ExifEraserActivity).windowSizeClass

protected fun setupToolbar(toolbar: Toolbar, @StringRes titleRes: Int = 0) {
if (titleRes != 0) {
toolbar.setTitle(titleRes)
}
protected fun setupToolbar(toolbar: Toolbar, @StringRes titleRes: Int) {
(requireActivity() as? AppCompatActivity)?.setSupportActionBar(toolbar)
toolbar.setTitle(titleRes)
requireParentFragment()
.childFragmentManager
.backStackEntryCount
Expand All @@ -70,20 +64,4 @@ abstract class BaseFragment<B : ViewBinding>(
toolbar.setNavigationOnClickListener { findNavController().navigateUp() }
}
}

protected fun setupLayoutMarginForNavigationWindowInsets() {
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { root, windowInsetsCompat ->
val insets = windowInsetsCompat.getInsets(WindowInsetsCompat.Type.navigationBars())
(root.layoutParams as FrameLayout.LayoutParams).bottomMargin = insets.bottom
WindowInsetsCompat.CONSUMED
}
}

protected fun navigate(navDirections: NavDirections) {
val navController = findNavController()
val action = navController.currentDestination?.getAction(navDirections.actionId)
if (action != null && action.destinationId != 0) {
navController.navigate(navDirections)
}
}
}
Loading

0 comments on commit 4c62d30

Please sign in to comment.