A fork of Simple Time Tracker with WearOS support. (Now integrated upstream)
0

Configure Feed

Select the types of activity you want to include in your feed.

add statistics share

razeeman (Dec 29, 2022, 8:34 AM +0300) e1e08b69 b9d9a97e

+334 -36
+10
app/src/main/AndroidManifest.xml
··· 23 23 android:name="android.app.shortcuts" 24 24 android:resource="@xml/shortcuts" /> 25 25 </activity> 26 + 27 + <provider 28 + android:name="androidx.core.content.FileProvider" 29 + android:authorities="${applicationId}.provider" 30 + android:exported="false" 31 + android:grantUriPermissions="true"> 32 + <meta-data 33 + android:name="android.support.FILE_PROVIDER_PATHS" 34 + android:resource="@xml/paths" /> 35 + </provider> 26 36 </application> 27 37 28 38 <queries>
+3
app/src/main/res/xml/paths.xml
··· 1 + <paths> 2 + <cache-path name="cache" path="." /> 3 + </paths>
+9
core/src/main/res/drawable/share.xml
··· 1 + <vector xmlns:android="http://schemas.android.com/apk/res/android" 2 + android:width="24dp" 3 + android:height="24dp" 4 + android:viewportWidth="24" 5 + android:viewportHeight="24"> 6 + <path 7 + android:fillColor="#FF000000" 8 + android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/> 9 + </vector>
+21
features/feature_statistics/src/main/res/layout/item_statistics_chart_layout.xml
··· 25 25 26 26 </androidx.cardview.widget.CardView> 27 27 28 + <androidx.cardview.widget.CardView 29 + android:id="@+id/btnStatisticsChartShare" 30 + style="@style/SettingsCard" 31 + android:layout_width="44dp" 32 + android:layout_height="44dp" 33 + android:layout_marginEnd="2dp" 34 + android:foreground="?selectableItemBackground" 35 + app:cardBackgroundColor="?appCardBackgroundColor" 36 + app:layout_constraintDimensionRatio="h,1:1" 37 + app:layout_constraintEnd_toEndOf="parent" 38 + app:layout_constraintTop_toBottomOf="@id/btnStatisticsChartFilter"> 39 + 40 + <androidx.appcompat.widget.AppCompatImageView 41 + android:layout_width="match_parent" 42 + android:layout_height="match_parent" 43 + android:scaleType="center" 44 + android:src="@drawable/share" 45 + android:tint="?appInactiveColor" /> 46 + 47 + </androidx.cardview.widget.CardView> 48 + 28 49 <com.example.util.simpletimetracker.feature_views.pieChart.PieChartView 29 50 android:id="@+id/chartStatisticsItem" 30 51 android:layout_width="0dp"
+16
app/src/main/java/com/example/util/simpletimetracker/navigation/ActionResolverImpl.kt
··· 15 15 import com.example.util.simpletimetracker.navigation.params.action.OpenMarketParams 16 16 import com.example.util.simpletimetracker.navigation.params.action.OpenSystemSettings 17 17 import com.example.util.simpletimetracker.navigation.params.action.SendEmailParams 18 + import com.example.util.simpletimetracker.navigation.params.action.ShareImageParams 18 19 import javax.inject.Inject 19 20 20 21 class ActionResolverImpl @Inject constructor( ··· 36 37 is CreateFileParams -> createFile(activity, data) 37 38 is OpenFileParams -> openFile(activity, data) 38 39 is OpenSystemSettings -> openSystemSettings(activity, data) 40 + is ShareImageParams -> shareImage(activity, data) 39 41 } 40 42 } 41 43 ··· 105 107 activity?.startActivity(intent) 106 108 } 107 109 } 110 + } 111 + } 112 + 113 + private fun shareImage(activity: Activity?, data: ShareImageParams) { 114 + try { 115 + val uri = Uri.parse(data.uriString) 116 + val intent = Intent(Intent.ACTION_SEND).apply { 117 + setDataAndType(uri, activity?.contentResolver?.getType(uri)) 118 + putExtra(Intent.EXTRA_STREAM, uri) 119 + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) 120 + } 121 + activity?.startActivity(Intent.createChooser(intent, null)) 122 + } catch (e: ActivityNotFoundException) { 123 + data.notHandledCallback.invoke() 108 124 } 109 125 } 110 126
+40
core/src/main/java/com/example/util/simpletimetracker/core/base/SingleLiveEvent.kt
··· 1 + package com.example.util.simpletimetracker.core.base 2 + 3 + import androidx.annotation.MainThread 4 + import androidx.lifecycle.LifecycleOwner 5 + import androidx.lifecycle.MutableLiveData 6 + import androidx.lifecycle.Observer 7 + import timber.log.Timber 8 + import java.util.concurrent.atomic.AtomicBoolean 9 + 10 + class SingleLiveEvent<T> : MutableLiveData<T>() { 11 + 12 + private val mPending = AtomicBoolean(false) 13 + 14 + @MainThread 15 + override fun observe(owner: LifecycleOwner, observer: Observer<in T>) { 16 + if (hasActiveObservers()) { 17 + Timber.w("Multiple observers registered but only one will be notified of changes.") 18 + } 19 + // Observe the internal MutableLiveData 20 + super.observe(owner) { t -> 21 + if (mPending.compareAndSet(true, false)) { 22 + observer.onChanged(t) 23 + } 24 + } 25 + } 26 + 27 + @MainThread 28 + override fun setValue(t: T?) { 29 + mPending.set(true) 30 + super.setValue(t) 31 + } 32 + 33 + /** 34 + * Used for cases where T is Void, to make calls cleaner. 35 + */ 36 + @MainThread 37 + fun call() { 38 + value = null 39 + } 40 + }
+47
core/src/main/java/com/example/util/simpletimetracker/core/interactor/SharingInteractor.kt
··· 1 + package com.example.util.simpletimetracker.core.interactor 2 + 3 + import android.view.View 4 + import com.example.util.simpletimetracker.core.R 5 + import com.example.util.simpletimetracker.core.repo.ResourceRepo 6 + import com.example.util.simpletimetracker.domain.resolver.SharingRepo 7 + import com.example.util.simpletimetracker.feature_views.extension.getBitmapFromView 8 + import com.example.util.simpletimetracker.feature_views.extension.measureForSharing 9 + import com.example.util.simpletimetracker.navigation.Router 10 + import com.example.util.simpletimetracker.navigation.params.action.ShareImageParams 11 + import com.example.util.simpletimetracker.navigation.params.notification.ToastParams 12 + import javax.inject.Inject 13 + 14 + class SharingInteractor @Inject constructor( 15 + private val sharingRepo: SharingRepo, 16 + private val router: Router, 17 + private val resourceRepo: ResourceRepo, 18 + ) { 19 + 20 + @Suppress("MoveVariableDeclarationIntoWhen") 21 + suspend fun execute( 22 + view: Any, 23 + filename: String, 24 + ) { 25 + if (view !is View) return 26 + 27 + val bitmap = view.measureForSharing().getBitmapFromView() 28 + val result = sharingRepo.saveBitmap(bitmap, filename) 29 + 30 + when (result) { 31 + is SharingRepo.Result.Success -> { 32 + ShareImageParams( 33 + uriString = result.uriString, 34 + notHandledCallback = { R.string.message_app_not_found.let(::showMessage) } 35 + ).let(router::execute) 36 + } 37 + is SharingRepo.Result.Error -> { 38 + showMessage(R.string.message_export_error) 39 + } 40 + } 41 + } 42 + 43 + private fun showMessage(stringResId: Int) { 44 + val params = ToastParams(message = resourceRepo.getString(stringResId)) 45 + router.show(params) 46 + } 47 + }
+6
data_local/src/main/java/com/example/util/simpletimetracker/data_local/di/DataLocalModuleBinds.kt
··· 13 13 import com.example.util.simpletimetracker.data_local.resolver.BackupRepoImpl 14 14 import com.example.util.simpletimetracker.data_local.resolver.CsvRepoImpl 15 15 import com.example.util.simpletimetracker.data_local.resolver.IcsRepoImpl 16 + import com.example.util.simpletimetracker.data_local.resolver.SharingRepoImpl 16 17 import com.example.util.simpletimetracker.domain.repo.ActivityFilterRepo 17 18 import com.example.util.simpletimetracker.domain.repo.CategoryRepo 18 19 import com.example.util.simpletimetracker.domain.repo.PrefsRepo ··· 26 27 import com.example.util.simpletimetracker.domain.resolver.BackupRepo 27 28 import com.example.util.simpletimetracker.domain.resolver.CsvRepo 28 29 import com.example.util.simpletimetracker.domain.resolver.IcsRepo 30 + import com.example.util.simpletimetracker.domain.resolver.SharingRepo 29 31 import dagger.Binds 30 32 import dagger.Module 31 33 import dagger.hilt.InstallIn ··· 63 65 @Binds 64 66 @Singleton 65 67 fun IcsRepoImpl.bindIcsRepo(): IcsRepo 68 + 69 + @Binds 70 + @Singleton 71 + fun SharingRepoImpl.bindSharingRepo(): SharingRepo 66 72 67 73 @Binds 68 74 @Singleton
+51
data_local/src/main/java/com/example/util/simpletimetracker/data_local/resolver/SharingRepoImpl.kt
··· 1 + package com.example.util.simpletimetracker.data_local.resolver 2 + 3 + import android.content.Context 4 + import android.graphics.Bitmap 5 + import androidx.core.content.FileProvider 6 + import com.example.util.simpletimetracker.core.provider.ApplicationDataProvider 7 + import com.example.util.simpletimetracker.domain.resolver.SharingRepo 8 + import dagger.hilt.android.qualifiers.ApplicationContext 9 + import kotlinx.coroutines.Dispatchers 10 + import kotlinx.coroutines.withContext 11 + import timber.log.Timber 12 + import java.io.File 13 + import java.io.FileOutputStream 14 + import java.io.IOException 15 + import javax.inject.Inject 16 + 17 + class SharingRepoImpl @Inject constructor( 18 + @ApplicationContext private val context: Context, 19 + private val applicationDataProvider: ApplicationDataProvider, 20 + ) : SharingRepo { 21 + 22 + override suspend fun saveBitmap( 23 + bitmap: Any, 24 + filename: String, 25 + ): SharingRepo.Result = withContext(Dispatchers.IO) { 26 + if (bitmap !is Bitmap) return@withContext SharingRepo.Result.Error 27 + 28 + var outputStream: FileOutputStream? = null 29 + try { 30 + val file = File(context.cacheDir, "$filename.png") 31 + outputStream = FileOutputStream(file) 32 + bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) 33 + val uri = FileProvider.getUriForFile( 34 + context, 35 + applicationDataProvider.getPackageName() + ".provider", 36 + file 37 + ) 38 + SharingRepo.Result.Success(uri.toString()) 39 + } catch (e: Exception) { 40 + Timber.e(e) 41 + SharingRepo.Result.Error 42 + } finally { 43 + try { 44 + outputStream?.flush() 45 + outputStream?.close() 46 + } catch (e: IOException) { 47 + // Do nothing 48 + } 49 + } 50 + } 51 + }
+14
domain/src/main/java/com/example/util/simpletimetracker/domain/resolver/SharingRepo.kt
··· 1 + package com.example.util.simpletimetracker.domain.resolver 2 + 3 + interface SharingRepo { 4 + 5 + suspend fun saveBitmap( 6 + bitmap: Any, 7 + filename: String, 8 + ): Result 9 + 10 + sealed interface Result { 11 + data class Success(val uriString: String) : Result 12 + object Error : Result 13 + } 14 + }
+9 -1
features/feature_statistics/src/main/java/com/example/util/simpletimetracker/feature_statistics/adapter/StatisticsChartAdapterDelegate.kt
··· 4 4 import com.example.util.simpletimetracker.feature_views.extension.setOnClick 5 5 import com.example.util.simpletimetracker.feature_statistics.databinding.ItemStatisticsChartLayoutBinding as Binding 6 6 import com.example.util.simpletimetracker.feature_statistics.viewData.StatisticsChartViewData as ViewData 7 + import androidx.core.view.isVisible 7 8 8 9 fun createStatisticsChartAdapterDelegate( 9 - onFilterClick: (() -> Unit) 10 + onFilterClick: (() -> Unit), 11 + onShareClick: (() -> Unit), 10 12 ) = createRecyclerBindingAdapterDelegate<ViewData, Binding>( 11 13 Binding::inflate 12 14 ) { binding, item, _ -> ··· 14 16 with(binding) { 15 17 item as ViewData 16 18 19 + chartStatisticsItem.setAnimated(item.animated) 17 20 chartStatisticsItem.setSegments(item.data) 21 + 22 + btnStatisticsChartFilter.isVisible = item.buttonsVisible 23 + btnStatisticsChartShare.isVisible = item.buttonsVisible 24 + 18 25 btnStatisticsChartFilter.setOnClick(onFilterClick) 26 + btnStatisticsChartShare.setOnClick(onShareClick) 19 27 } 20 28 }
+13 -3
features/feature_statistics/src/main/java/com/example/util/simpletimetracker/feature_statistics/interactor/StatisticsViewDataInteractor.kt
··· 20 20 private val statisticsViewDataMapper: StatisticsViewDataMapper, 21 21 ) { 22 22 23 - suspend fun getViewData(rangeLength: RangeLength, shift: Int): List<ViewHolderType> { 23 + suspend fun getViewData( 24 + rangeLength: RangeLength, 25 + shift: Int, 26 + forSharing: Boolean, 27 + ): List<ViewHolderType> { 24 28 val filterType = prefsInteractor.getChartFilterType() 25 29 val isDarkTheme = prefsInteractor.getDarkMode() 26 30 val useProportionalMinutes = prefsInteractor.getUseProportionalMinutes() ··· 51 55 dataHolders = dataHolders, 52 56 types = types, 53 57 isDarkTheme = isDarkTheme 54 - ).let(::StatisticsChartViewData) 58 + ).let { 59 + StatisticsChartViewData( 60 + data = it, 61 + animated = !forSharing, 62 + buttonsVisible = !forSharing, 63 + ) 64 + } 55 65 val list = statisticsViewDataMapper.mapItemsList( 56 66 statistics = statistics, 57 67 data = dataHolders, ··· 78 88 list.let(result::addAll) 79 89 totalTracked.let(result::add) 80 90 // If has any activity or tag other than untracked 81 - if (list.any { it.id != UNTRACKED_ITEM_ID }) { 91 + if (list.any { it.id != UNTRACKED_ITEM_ID } && !forSharing) { 82 92 statisticsViewDataMapper.mapToHint().let(result::add) 83 93 } 84 94 }
+46 -18
features/feature_statistics/src/main/java/com/example/util/simpletimetracker/feature_statistics/view/StatisticsFragment.kt
··· 1 1 package com.example.util.simpletimetracker.feature_statistics.view 2 2 3 + import com.example.util.simpletimetracker.feature_statistics.databinding.StatisticsFragmentBinding as Binding 3 4 import android.os.Bundle 4 5 import android.view.LayoutInflater 5 6 import android.view.ViewGroup 6 7 import androidx.appcompat.app.AppCompatActivity 7 8 import androidx.fragment.app.viewModels 8 9 import androidx.recyclerview.widget.LinearLayoutManager 9 - import com.example.util.simpletimetracker.feature_base_adapter.BaseRecyclerAdapter 10 - import com.example.util.simpletimetracker.feature_base_adapter.hint.createHintAdapterDelegate 11 - import com.example.util.simpletimetracker.feature_base_adapter.loader.createLoaderAdapterDelegate 12 - import com.example.util.simpletimetracker.feature_base_adapter.statistics.createStatisticsAdapterDelegate 10 + import androidx.recyclerview.widget.RecyclerView 13 11 import com.example.util.simpletimetracker.core.base.BaseFragment 14 12 import com.example.util.simpletimetracker.core.di.BaseViewModelFactory 15 13 import com.example.util.simpletimetracker.core.dialog.ChartFilterDialogListener 14 + import com.example.util.simpletimetracker.core.extension.getThemedAttr 15 + import com.example.util.simpletimetracker.core.repo.DeviceRepo 16 16 import com.example.util.simpletimetracker.domain.extension.orZero 17 + import com.example.util.simpletimetracker.feature_base_adapter.BaseRecyclerAdapter 18 + import com.example.util.simpletimetracker.feature_base_adapter.ViewHolderType 19 + import com.example.util.simpletimetracker.feature_base_adapter.hint.createHintAdapterDelegate 20 + import com.example.util.simpletimetracker.feature_base_adapter.loader.createLoaderAdapterDelegate 21 + import com.example.util.simpletimetracker.feature_base_adapter.statistics.createStatisticsAdapterDelegate 22 + import com.example.util.simpletimetracker.feature_statistics.R 17 23 import com.example.util.simpletimetracker.feature_statistics.adapter.createStatisticsChartAdapterDelegate 18 24 import com.example.util.simpletimetracker.feature_statistics.adapter.createStatisticsEmptyAdapterDelegate 19 25 import com.example.util.simpletimetracker.feature_statistics.adapter.createStatisticsInfoAdapterDelegate ··· 23 29 import com.example.util.simpletimetracker.navigation.params.screen.StatisticsParams 24 30 import dagger.hilt.android.AndroidEntryPoint 25 31 import javax.inject.Inject 26 - import com.example.util.simpletimetracker.feature_statistics.databinding.StatisticsFragmentBinding as Binding 27 32 28 33 @AndroidEntryPoint 29 34 class StatisticsFragment : ··· 39 44 @Inject 40 45 lateinit var viewModelFactory: BaseViewModelFactory<StatisticsViewModel> 41 46 47 + @Inject 48 + lateinit var deviceRepo: DeviceRepo 49 + 42 50 private val settingsViewModel: StatisticsSettingsViewModel by viewModels( 43 51 ownerProducer = { activity as AppCompatActivity }, 44 52 factoryProducer = { settingsViewModelFactory } ··· 47 55 factoryProducer = { viewModelFactory } 48 56 ) 49 57 50 - private val statisticsAdapter: BaseRecyclerAdapter by lazy { 51 - BaseRecyclerAdapter( 52 - createStatisticsChartAdapterDelegate(viewModel::onFilterClick), 53 - createStatisticsInfoAdapterDelegate(), 54 - createHintAdapterDelegate(), 55 - createStatisticsAdapterDelegate( 56 - addTransitionNames = true, 57 - onItemClick = viewModel::onItemClick 58 - ), 59 - createStatisticsEmptyAdapterDelegate(viewModel::onFilterClick), 60 - createLoaderAdapterDelegate() 61 - ) 62 - } 58 + private val statisticsAdapter: BaseRecyclerAdapter by lazy { buildAdapter() } 63 59 64 60 override fun initUi(): Unit = with(binding) { 65 61 parentFragment?.postponeEnterTransition() ··· 79 75 with(viewModel) { 80 76 extra = StatisticsExtra(shift = arguments?.getInt(ARGS_POSITION).orZero()) 81 77 statistics.observe(statisticsAdapter::replace) 78 + sharingData.observe(::onNewSharingData) 82 79 } 83 80 with(settingsViewModel) { 84 81 rangeUpdated.observe { viewModel.onRangeUpdated() } ··· 97 94 98 95 override fun onChartFilterDialogDismissed() { 99 96 viewModel.onFilterApplied() 97 + } 98 + 99 + private fun onNewSharingData(data: List<ViewHolderType>) { 100 + val context = binding.root.context 101 + val adapter = buildAdapter() 102 + adapter.replace(data) 103 + val view = RecyclerView(context).apply { 104 + layoutManager = LinearLayoutManager(context) 105 + this.adapter = adapter 106 + context.getThemedAttr(R.attr.appBackgroundColor).let(::setBackgroundColor) 107 + } 108 + viewModel.onShareView(view) 109 + } 110 + 111 + private fun buildAdapter(): BaseRecyclerAdapter { 112 + return BaseRecyclerAdapter( 113 + createStatisticsChartAdapterDelegate( 114 + onFilterClick = viewModel::onFilterClick, 115 + onShareClick = viewModel::onShareClick, 116 + ), 117 + createStatisticsInfoAdapterDelegate(), 118 + createHintAdapterDelegate(), 119 + createStatisticsAdapterDelegate( 120 + addTransitionNames = true, 121 + onItemClick = viewModel::onItemClick 122 + ), 123 + createStatisticsEmptyAdapterDelegate( 124 + onFilterClick = viewModel::onFilterClick, 125 + ), 126 + createLoaderAdapterDelegate() 127 + ) 100 128 } 101 129 102 130 companion object {
+3 -1
features/feature_statistics/src/main/java/com/example/util/simpletimetracker/feature_statistics/viewData/StatisticsChartViewData.kt
··· 4 4 import com.example.util.simpletimetracker.feature_views.pieChart.PiePortion 5 5 6 6 data class StatisticsChartViewData( 7 - val data: List<PiePortion> 7 + val data: List<PiePortion>, 8 + val animated: Boolean, 9 + val buttonsVisible: Boolean, 8 10 ) : ViewHolderType { 9 11 10 12 override fun getUniqueId(): Long = 1L
+16 -1
features/feature_statistics/src/main/java/com/example/util/simpletimetracker/feature_statistics/viewModel/StatisticsViewModel.kt
··· 4 4 import androidx.lifecycle.MutableLiveData 5 5 import androidx.lifecycle.ViewModel 6 6 import androidx.lifecycle.viewModelScope 7 + import com.example.util.simpletimetracker.core.base.SingleLiveEvent 7 8 import com.example.util.simpletimetracker.core.extension.set 8 9 import com.example.util.simpletimetracker.core.extension.toParams 10 + import com.example.util.simpletimetracker.core.interactor.SharingInteractor 9 11 import com.example.util.simpletimetracker.core.utils.UNTRACKED_ITEM_ID 10 12 import com.example.util.simpletimetracker.domain.extension.orZero 11 13 import com.example.util.simpletimetracker.domain.interactor.PrefsInteractor ··· 30 32 class StatisticsViewModel @Inject constructor( 31 33 private val router: Router, 32 34 private val statisticsViewDataInteractor: StatisticsViewDataInteractor, 35 + private val sharingInteractor: SharingInteractor, 33 36 private val prefsInteractor: PrefsInteractor, 34 37 ) : ViewModel() { 35 38 ··· 38 41 val statistics: LiveData<List<ViewHolderType>> by lazy { 39 42 MutableLiveData(listOf(LoaderViewData() as ViewHolderType)) 40 43 } 44 + val sharingData: SingleLiveEvent<List<ViewHolderType>> = SingleLiveEvent() 41 45 42 46 private var isVisible: Boolean = false 43 47 private var timerJob: Job? = null ··· 63 67 64 68 fun onFilterClick() { 65 69 router.navigate(ChartFilterDialogParams) 70 + } 71 + 72 + fun onShareClick() = viewModelScope.launch { 73 + val data = loadStatisticsViewData(forSharing = true) 74 + sharingData.set(data) 66 75 } 67 76 68 77 fun onItemClick( ··· 113 122 updateStatistics() 114 123 } 115 124 125 + fun onShareView(view: Any) = viewModelScope.launch { 126 + sharingInteractor.execute(view, SHARING_NAME) 127 + } 128 + 116 129 private fun updateStatistics() = viewModelScope.launch { 117 130 val data = loadStatisticsViewData() 118 131 statistics.set(data) 119 132 } 120 133 121 - private suspend fun loadStatisticsViewData(): List<ViewHolderType> { 134 + private suspend fun loadStatisticsViewData(forSharing: Boolean = false): List<ViewHolderType> { 122 135 return statisticsViewDataInteractor.getViewData( 123 136 rangeLength = prefsInteractor.getStatisticsRange(), 124 137 shift = shift, 138 + forSharing = forSharing, 125 139 ) 126 140 } 127 141 ··· 143 157 144 158 companion object { 145 159 private const val TIMER_UPDATE = 1000L 160 + private const val SHARING_NAME = "stt_statistics" 146 161 } 147 162 }
+12 -6
features/feature_views/src/main/java/com/example/util/simpletimetracker/feature_views/extension/ViewExtensions.kt
··· 4 4 import android.graphics.Bitmap 5 5 import android.graphics.Canvas 6 6 import android.view.View 7 + import android.view.View.MeasureSpec 7 8 import android.view.ViewGroup 8 9 import android.view.animation.LinearInterpolator 9 10 import android.widget.AdapterView ··· 20 21 get() { 21 22 return visibility == View.VISIBLE 22 23 } 23 - 24 - fun View.flipVisibility() { 25 - visibility = if (visibility == View.VISIBLE) View.GONE else View.VISIBLE 26 - } 27 24 28 25 fun View.rotate(from: Float, to: Float, duration: Long = 300) { 29 26 ObjectAnimator.ofFloat(this, "rotation", from, to).apply { ··· 143 140 } 144 141 145 142 fun View.measureExactly(width: Int, height: Int = width) { 146 - val specWidth = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY) 147 - val specHeight = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY) 143 + val specWidth = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY) 144 + val specHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY) 148 145 measure(specWidth, specHeight) 149 146 layout(0, 0, measuredWidth, measuredHeight) 147 + } 148 + 149 + fun View.measureForSharing(): View { 150 + // Measure with width matched to screen width and height as wrap content. 151 + val specWidth = MeasureSpec.makeMeasureSpec(context.resources.displayMetrics.widthPixels, MeasureSpec.EXACTLY) 152 + val specHeight = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) 153 + measure(specWidth, specHeight) 154 + layout(0, 0, measuredWidth, measuredHeight) 155 + return this 150 156 } 151 157 152 158 fun GridLayoutManager.setSpanSizeLookup(getSpanSize: (position: Int) -> Int) {
+7 -1
features/feature_views/src/main/java/com/example/util/simpletimetracker/feature_views/pieChart/PieChartView.kt
··· 82 82 drawIcons(canvas, w, h, r) 83 83 } 84 84 85 + // Set before setting data. 86 + fun setAnimated(animated: Boolean) { 87 + this.animated = animated 88 + } 89 + 85 90 fun setSegments(data: List<PiePortion>) { 86 91 val res = mutableListOf<Arc>() 87 92 val valuesSum = data.map(PiePortion::value).sum() ··· 136 141 getInt(R.styleable.PieChartView_segmentCount, 0) 137 142 drawIcons = 138 143 getBoolean(R.styleable.PieChartView_drawIcons, false) 139 - animated = 144 + 145 + if (hasValue(R.styleable.PieChartView_pieChartAnimated)) animated = 140 146 getBoolean(R.styleable.PieChartView_pieChartAnimated, true) 141 147 recycle() 142 148 }
+6
navigation/src/main/java/com/example/util/simpletimetracker/navigation/params/action/ShareImageParams.kt
··· 1 + package com.example.util.simpletimetracker.navigation.params.action 2 + 3 + data class ShareImageParams( 4 + val uriString: String, 5 + val notHandledCallback: (() -> Unit), 6 + ) : ActionParams
+5 -5
navigation/src/main/java/com/example/util/simpletimetracker/navigation/params/notification/SnackBarParams.kt
··· 6 6 val duration: Duration = Duration.Normal, 7 7 val dismissedListener: ((TAG?) -> Unit)? = null, 8 8 val actionText: String = "", 9 - val actionListener: ((TAG?) -> Unit)? = null 9 + val actionListener: ((TAG?) -> Unit)? = null, 10 10 ) : NotificationParams { 11 11 12 12 enum class TAG { ··· 15 15 } 16 16 17 17 sealed interface Duration { 18 - object Short: Duration 19 - object Normal: Duration 20 - object Long: Duration 21 - object Indefinite: Duration 18 + object Short : Duration 19 + object Normal : Duration 20 + object Long : Duration 21 + object Indefinite : Duration 22 22 } 23 23 }