Tools screen

Added basic tools screen
Added epoch converter screen
Little refactor to avoid code duplication
This commit is contained in:
Sad Ellie 2023-01-29 12:13:07 +04:00
parent 541e13908b
commit 0bf3ac6155
27 changed files with 933 additions and 5 deletions

View File

@ -110,6 +110,8 @@ dependencies {
implementation(project(mapOf("path" to ":feature:converter"))) implementation(project(mapOf("path" to ":feature:converter")))
implementation(project(mapOf("path" to ":feature:settings"))) implementation(project(mapOf("path" to ":feature:settings")))
implementation(project(mapOf("path" to ":feature:unitslist"))) implementation(project(mapOf("path" to ":feature:unitslist")))
implementation(project(mapOf("path" to ":feature:tools")))
implementation(project(mapOf("path" to ":feature:epoch")))
implementation(project(mapOf("path" to ":data"))) implementation(project(mapOf("path" to ":data")))
implementation(project(mapOf("path" to ":core:base"))) implementation(project(mapOf("path" to ":core:base")))
implementation(project(mapOf("path" to ":core:ui"))) implementation(project(mapOf("path" to ":core:ui")))

View File

@ -24,10 +24,14 @@ import androidx.navigation.compose.NavHost
import com.sadellie.unitto.feature.converter.MainViewModel import com.sadellie.unitto.feature.converter.MainViewModel
import com.sadellie.unitto.feature.converter.navigation.converterRoute import com.sadellie.unitto.feature.converter.navigation.converterRoute
import com.sadellie.unitto.feature.converter.navigation.converterScreen import com.sadellie.unitto.feature.converter.navigation.converterScreen
import com.sadellie.unitto.feature.epoch.navigation.epochScreen
import com.sadellie.unitto.feature.epoch.navigation.navigateToEpoch
import com.sadellie.unitto.feature.settings.SettingsViewModel import com.sadellie.unitto.feature.settings.SettingsViewModel
import com.sadellie.unitto.feature.settings.navigation.navigateToSettings import com.sadellie.unitto.feature.settings.navigation.navigateToSettings
import com.sadellie.unitto.feature.settings.navigation.navigateToUnitGroups import com.sadellie.unitto.feature.settings.navigation.navigateToUnitGroups
import com.sadellie.unitto.feature.settings.navigation.settingGraph import com.sadellie.unitto.feature.settings.navigation.settingGraph
import com.sadellie.unitto.feature.tools.navigation.navigateToTools
import com.sadellie.unitto.feature.tools.navigation.toolsScreen
import com.sadellie.unitto.feature.unitslist.SecondViewModel import com.sadellie.unitto.feature.unitslist.SecondViewModel
import com.sadellie.unitto.feature.unitslist.navigation.leftScreen import com.sadellie.unitto.feature.unitslist.navigation.leftScreen
import com.sadellie.unitto.feature.unitslist.navigation.navigateToLeftSide import com.sadellie.unitto.feature.unitslist.navigation.navigateToLeftSide
@ -53,6 +57,7 @@ fun UnittoNavigation(
navController.navigateToRightSide(unitFrom, unitTo, input) navController.navigateToRightSide(unitFrom, unitTo, input)
}, },
navigateToSettings = { navController.navigateToSettings() }, navigateToSettings = { navController.navigateToSettings() },
navigateToTools = { navController.navigateToTools() },
viewModel = mainViewModel viewModel = mainViewModel
) )
@ -75,5 +80,14 @@ fun UnittoNavigation(
themmoController = themmoController, themmoController = themmoController,
navController = navController navController = navController
) )
toolsScreen(
navigateUpAction = navController::navigateUp,
navigateToEpoch = navController::navigateToEpoch
)
epochScreen(
navigateUpAction = navController::navigateUp
)
} }
} }

View File

@ -18,12 +18,17 @@
import org.gradle.api.Plugin import org.gradle.api.Plugin
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.kotlin.dsl.dependencies import org.gradle.kotlin.dsl.dependencies
import org.gradle.kotlin.dsl.getByType
class UnittoLibraryFeaturePlugin : Plugin<Project> { class UnittoLibraryFeaturePlugin : Plugin<Project> {
override fun apply(target: Project) { override fun apply(target: Project) {
with(target) { with(target) {
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
dependencies { dependencies {
"implementation"(libs.findLibrary("androidx.navigation").get())
"implementation"(project(mapOf("path" to ":data"))) "implementation"(project(mapOf("path" to ":data")))
"implementation"(project(mapOf("path" to ":core:base"))) "implementation"(project(mapOf("path" to ":core:base")))
"implementation"(project(mapOf("path" to ":core:ui"))) "implementation"(project(mapOf("path" to ":core:ui")))

View File

@ -992,6 +992,7 @@
<string name="units_screen_from">Convert from</string> <string name="units_screen_from">Convert from</string>
<string name="units_screen_to">Convert to</string> <string name="units_screen_to">Convert to</string>
<string name="settings_screen">Settings</string> <string name="settings_screen">Settings</string>
<string name="tools_screen">Tools</string>
<!--Settings items--> <!--Settings items-->
<string name="theme_setting">Themes</string> <string name="theme_setting">Themes</string>
@ -1014,6 +1015,10 @@
<string name="formatting_settings_group">Formatting</string> <string name="formatting_settings_group">Formatting</string>
<string name="additional_settings_group">Additional</string> <string name="additional_settings_group">Additional</string>
<!--Tools-->
<string name="epoch_converter">Epoch converter</string>
<string name="epoch_converter_support">Convert Unix timestamp</string>
<!--Precision--> <!--Precision-->
<string name="precision_setting_support">Number of decimal places</string> <string name="precision_setting_support">Number of decimal places</string>
<string name="precision_setting_info">Converted values may have a precision higher than the preferred one.</string> <string name="precision_setting_info">Converted values may have a precision higher than the preferred one.</string>
@ -1092,4 +1097,8 @@
<string name="about_unitto_support">Learn about the app</string> <string name="about_unitto_support">Learn about the app</string>
<string name="unit_groups_support">Disable and rearrange units</string> <string name="unit_groups_support">Disable and rearrange units</string>
<!--Epoch-->
<string name="year_short">y</string>
<string name="month_short">m</string>
</resources> </resources>

View File

@ -1,6 +1,6 @@
/* /*
* Unitto is a unit converter for Android * Unitto is a unit converter for Android
* Copyright (c) 2022-2022 Elshan Agaev * Copyright (c) 2022-2023 Elshan Agaev
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.sadellie.unitto.feature.converter.components package com.sadellie.unitto.core.ui.common
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.FastOutSlowInEasing
@ -36,7 +36,6 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.sadellie.unitto.core.ui.common.UnittoButton
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleTitleLarge import com.sadellie.unitto.core.ui.theme.NumbersTextStyleTitleLarge
/** /**
@ -51,7 +50,7 @@ import com.sadellie.unitto.core.ui.theme.NumbersTextStyleTitleLarge
* @param onClick Action to perform when clicking this button. * @param onClick Action to perform when clicking this button.
*/ */
@Composable @Composable
internal fun KeyboardButton( fun KeyboardButton(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
digit: String, digit: String,
allowVibration: Boolean, allowVibration: Boolean,

View File

@ -0,0 +1,63 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2022 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.core.ui.common
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
/**
* Commonly used TopAppBar with scroll behavior.
*
* @param title Text that is displayed in top bar.
* @param navigateUpAction Action when user click arrow button at the top.
* @param content Content that can be scrolled. Don't forget to use padding values.
*/
@Composable
fun UnittoTopAppBar(
title: String,
navigateUpAction: () -> Unit,
content: @Composable (PaddingValues) -> Unit
) {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
rememberTopAppBarState()
)
Scaffold(
modifier = Modifier
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
TopAppBar(
title = {
Text(text = title)
},
navigationIcon = {
NavigateUpButton { navigateUpAction() }
},
scrollBehavior = scrollBehavior
)
},
content = content
)
}

View File

@ -21,6 +21,7 @@ package com.sadellie.unitto.feature.converter
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.MoreVert import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material.icons.outlined.Science
import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@ -37,8 +38,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.sadellie.unitto.core.ui.common.AnimatedTopBarText
import com.sadellie.unitto.core.ui.R import com.sadellie.unitto.core.ui.R
import com.sadellie.unitto.core.ui.common.AnimatedTopBarText
import com.sadellie.unitto.feature.converter.components.Keyboard import com.sadellie.unitto.feature.converter.components.Keyboard
import com.sadellie.unitto.feature.converter.components.PortraitLandscape import com.sadellie.unitto.feature.converter.components.PortraitLandscape
import com.sadellie.unitto.feature.converter.components.TopScreenPart import com.sadellie.unitto.feature.converter.components.TopScreenPart
@ -49,6 +50,7 @@ internal fun MainScreen(
navigateToLeftScreen: (String) -> Unit, navigateToLeftScreen: (String) -> Unit,
navigateToRightScreen: (unitFrom: String, unitTo: String, input: String) -> Unit, navigateToRightScreen: (unitFrom: String, unitTo: String, input: String) -> Unit,
navigateToSettings: () -> Unit, navigateToSettings: () -> Unit,
navigateToTools: () -> Unit,
viewModel: MainViewModel = viewModel() viewModel: MainViewModel = viewModel()
) { ) {
var launched: Boolean by rememberSaveable { mutableStateOf(false) } var launched: Boolean by rememberSaveable { mutableStateOf(false) }
@ -61,6 +63,14 @@ internal fun MainScreen(
CenterAlignedTopAppBar( CenterAlignedTopAppBar(
modifier = Modifier, modifier = Modifier,
title = { AnimatedTopBarText(launched) }, title = { AnimatedTopBarText(launched) },
navigationIcon = {
IconButton(onClick = navigateToTools) {
Icon(
Icons.Outlined.Science,
contentDescription = stringResource(R.string.open_settings_description)
)
}
},
actions = { actions = {
IconButton(onClick = navigateToSettings) { IconButton(onClick = navigateToSettings) {
Icon( Icon(

View File

@ -56,6 +56,7 @@ import com.sadellie.unitto.core.base.KEY_PLUS
import com.sadellie.unitto.core.base.KEY_RIGHT_BRACKET import com.sadellie.unitto.core.base.KEY_RIGHT_BRACKET
import com.sadellie.unitto.core.base.KEY_SQRT import com.sadellie.unitto.core.base.KEY_SQRT
import com.sadellie.unitto.core.ui.Formatter import com.sadellie.unitto.core.ui.Formatter
import com.sadellie.unitto.core.ui.common.KeyboardButton
import com.sadellie.unitto.feature.converter.ConverterMode import com.sadellie.unitto.feature.converter.ConverterMode
/** /**

View File

@ -29,6 +29,7 @@ fun NavGraphBuilder.converterScreen(
navigateToLeftScreen: (String) -> Unit, navigateToLeftScreen: (String) -> Unit,
navigateToRightScreen: (unitFrom: String, unitTo: String, input: String) -> Unit, navigateToRightScreen: (unitFrom: String, unitTo: String, input: String) -> Unit,
navigateToSettings: () -> Unit, navigateToSettings: () -> Unit,
navigateToTools: () -> Unit,
viewModel: MainViewModel viewModel: MainViewModel
) { ) {
composable(converterRoute) { composable(converterRoute) {
@ -36,6 +37,7 @@ fun NavGraphBuilder.converterScreen(
navigateToLeftScreen = navigateToLeftScreen, navigateToLeftScreen = navigateToLeftScreen,
navigateToRightScreen = navigateToRightScreen, navigateToRightScreen = navigateToRightScreen,
navigateToSettings = navigateToSettings, navigateToSettings = navigateToSettings,
navigateToTools = navigateToTools,
viewModel = viewModel, viewModel = viewModel,
) )
} }

1
feature/epoch/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,28 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
plugins {
id("unitto.library")
id("unitto.library.compose")
id("unitto.library.feature")
id("unitto.android.hilt")
}
android {
namespace = "com.sadellie.unitto.feature.epoch"
}

View File

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -0,0 +1,145 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.epoch
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.SwapHoriz
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sadellie.unitto.core.ui.common.UnittoTopAppBar
import com.sadellie.unitto.feature.epoch.component.DateTextField
import com.sadellie.unitto.feature.epoch.component.EpochKeyboard
import com.sadellie.unitto.feature.epoch.component.TopPart
import com.sadellie.unitto.feature.epoch.component.UnixTextField
@Composable
internal fun EpochRoute(
navigateUpAction: () -> Unit,
viewModel: EpochViewModel = hiltViewModel()
) {
val uiState = viewModel.uiState.collectAsStateWithLifecycle()
EpochScreen(
navigateUpAction = navigateUpAction,
uiState = uiState.value,
addSymbol = viewModel::addSymbol,
deleteSymbol = viewModel::deleteSymbol,
swap = viewModel::swap,
clearSymbols = viewModel::clearSymbols
)
}
@Composable
private fun EpochScreen(
navigateUpAction: () -> Unit,
uiState: EpochUIState,
addSymbol: (String) -> Unit,
deleteSymbol: () -> Unit,
clearSymbols: () -> Unit,
swap: () -> Unit
) {
UnittoTopAppBar(
title = stringResource(R.string.epoch_converter),
navigateUpAction = navigateUpAction
) { padding ->
Column(
modifier = Modifier.padding(padding),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
TopPart(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 8.dp)
.weight(1f),
unixToDate = !uiState.dateToUnix,
dateField = {
Column(
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.animateItemPlacement()
) {
DateTextField(
modifier = Modifier.fillMaxWidth(),
date = uiState.dateField
)
Text(
text = "date",
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.End
)
}
},
unixField = {
Column(
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.animateItemPlacement()
) {
UnixTextField(
modifier = Modifier.fillMaxWidth(),
unix = uiState.unixField
)
Text(
text = "unix",
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.End
)
}
}
)
Button(
onClick = swap,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp),
) {
Icon(Icons.Default.SwapHoriz, null)
Spacer(modifier = Modifier.width(8.dp))
Text("SWAP")
}
EpochKeyboard(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 8.dp)
.weight(1f),
addSymbol = addSymbol,
clearSymbols = clearSymbols,
deleteSymbol = deleteSymbol
)
}
}
}

View File

@ -0,0 +1,132 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.epoch
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import java.math.BigDecimal
import java.util.*
import javax.inject.Inject
data class EpochUIState(
val dateField: String = "",
val unixField: String = "",
val dateToUnix: Boolean = true
)
@HiltViewModel
class EpochViewModel @Inject constructor() : ViewModel() {
private val _input: MutableStateFlow<String> = MutableStateFlow("")
private val _fromDateToUnix: MutableStateFlow<Boolean> = MutableStateFlow(true)
val uiState: StateFlow<EpochUIState> = combine(
_input, _fromDateToUnix
) { input, fromDateToUnix ->
if (fromDateToUnix) {
EpochUIState(
dateField = input,
unixField = convertDateToUnix(input),
dateToUnix = fromDateToUnix
)
} else {
EpochUIState(
unixField = input,
dateField = convertUnixToDate(input),
dateToUnix = fromDateToUnix
)
}
}.stateIn(
viewModelScope, SharingStarted.WhileSubscribed(5000L), EpochUIState()
)
fun addSymbol(symbol: String) {
val maxSymbols = if (_fromDateToUnix.value) 14 else 10
if (_input.value.length < maxSymbols) _input.update { it + symbol }
}
fun deleteSymbol() = _input.update { it.dropLast(1) }
fun clearSymbols() = _input.update { "" }
fun swap() = _fromDateToUnix.update {
clearSymbols()
!it
}
private fun convertDateToUnix(date: String): String {
// Here we add some zeros, so that input is 14 symbols long
val inputWithPadding = date.padEnd(14, '0')
// Now we break input that is 14 symbols into pieces
val day = inputWithPadding.substring(0, 2)
val month = inputWithPadding.substring(2, 4)
val year = inputWithPadding.substring(4, 8)
val hour = inputWithPadding.substring(8, 10)
val minute = inputWithPadding.substring(10, 12)
val second = inputWithPadding.substring(12, 14)
val cal = Calendar.getInstance()
cal.set(
year.toIntOrNull() ?: 1970,
(month.toIntOrNull() ?: 1) - 1,
day.toIntOrNull() ?: 0,
hour.toIntOrNull() ?: 0,
minute.toIntOrNull() ?: 0,
second.toIntOrNull() ?: 0,
)
return (cal.timeInMillis / 1000).toString()
}
private fun convertUnixToDate(unix: String): String {
var date = ""
val cal2 = Calendar.getInstance()
cal2.clear()
cal2.isLenient = true
// This lets us bypass calendars limits (it uses Int, we want BigDecimal)
try {
val unixBg = BigDecimal(unix)
val division = unixBg.divideAndRemainder(BigDecimal(Int.MAX_VALUE))
val intTimes = division.component1()
val rem = division.component2()
repeat(intTimes.intValueExact()) {
cal2.add(Calendar.SECOND, Int.MAX_VALUE)
}
cal2.add(Calendar.SECOND, rem.intValueExact())
} catch (e: NumberFormatException) {
return ""
}
date += cal2.get(Calendar.DAY_OF_MONTH).toString().padStart(2, '0')
date += (cal2.get(Calendar.MONTH) + 1).toString().padStart(2, '0')
// Year is 4 symbols long
date += cal2.get(Calendar.YEAR).toString().padStart(4, '0')
date += cal2.get(Calendar.HOUR_OF_DAY).toString().padStart(2, '0')
date += cal2.get(Calendar.MINUTE).toString().padStart(2, '0')
date += cal2.get(Calendar.SECOND).toString().padStart(2, '0')
return date
}
}

View File

@ -0,0 +1,162 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.epoch.component
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.with
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.Divider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayMedium
import com.sadellie.unitto.feature.epoch.R
@Composable
fun DateTextField(
modifier: Modifier,
date: String
) {
val inputWithPadding: String = date.padEnd(14, '0')
val day: String = inputWithPadding.substring(0, 2)
val month: String = inputWithPadding.substring(2, 4)
val year: String = inputWithPadding.substring(4, 8)
val hour: String = inputWithPadding.substring(8, 10)
val minute: String = inputWithPadding.substring(10, 12)
val second: String = inputWithPadding.substring(12, 14)
fun inFocus(range: Int): Boolean = date.length > range
Row(
modifier = modifier
.height(IntrinsicSize.Min)
.horizontalScroll(rememberScrollState()),
horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.End)
) {
Row(
horizontalArrangement = Arrangement.spacedBy(2.dp)
) {
Row {
DateTextAnimated(text = day, focus = inFocus(0))
DateText(text = stringResource(R.string.day_short), focus = inFocus(0))
}
Row {
DateTextAnimated(text = month, focus = inFocus(2))
DateText(text = stringResource(R.string.month_short), focus = inFocus(2))
}
Row {
DateTextAnimated(text = year, focus = inFocus(4))
DateText(text = stringResource(R.string.year_short), focus = inFocus(4))
}
}
Divider(
modifier = Modifier
.fillMaxHeight()
.width(1.dp)
)
Row(
horizontalArrangement = Arrangement.spacedBy(2.dp)
) {
DateTextAnimated(text = hour, focus = inFocus(8))
DateText(text = ":", focus = inFocus(8))
DateTextAnimated(text = minute, focus = inFocus(10))
DateText(text = ":", focus = inFocus(10))
DateTextAnimated(text = second, focus = inFocus(12))
}
}
}
@Composable
private fun BaseDateText(
focus: Boolean,
content: @Composable (Color) -> Unit
) {
val color = animateColorAsState(
if (focus) {
MaterialTheme.colorScheme.onBackground
} else {
MaterialTheme.colorScheme.outline.copy(alpha = 0.5f)
}
)
content(color.value)
}
@Composable
private fun DateText(
focus: Boolean = true,
text: String
) {
BaseDateText(focus = focus) { color ->
Text(
text = text,
style = NumbersTextStyleDisplayMedium,
color = color
)
}
}
@Composable
private fun DateTextAnimated(
focus: Boolean = true,
text: String
) {
BaseDateText(focus = focus) { color ->
AnimatedContent(
targetState = text,
transitionSpec = {
if (targetState.toInt() > initialState.toInt()) {
slideInVertically { height -> height } + fadeIn() with
slideOutVertically { height -> -height } + fadeOut()
} else {
slideInVertically { height -> -height } + fadeIn() with
slideOutVertically { height -> height } + fadeOut()
}.using(
SizeTransform(clip = false)
)
}
) {
Text(
text = it,
style = NumbersTextStyleDisplayMedium,
color = color
)
}
}
}

View File

@ -0,0 +1,87 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.epoch.component
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.sadellie.unitto.core.base.KEY_0
import com.sadellie.unitto.core.base.KEY_1
import com.sadellie.unitto.core.base.KEY_2
import com.sadellie.unitto.core.base.KEY_3
import com.sadellie.unitto.core.base.KEY_4
import com.sadellie.unitto.core.base.KEY_5
import com.sadellie.unitto.core.base.KEY_6
import com.sadellie.unitto.core.base.KEY_7
import com.sadellie.unitto.core.base.KEY_8
import com.sadellie.unitto.core.base.KEY_9
import com.sadellie.unitto.core.base.KEY_CLEAR
import com.sadellie.unitto.core.ui.common.KeyboardButton
@Composable
internal fun EpochKeyboard(
modifier: Modifier,
addSymbol: (String) -> Unit,
clearSymbols: () -> Unit,
deleteSymbol: () -> Unit
) {
Column(
modifier = modifier
) {
// Button modifier
val bModifier = Modifier
.fillMaxSize()
.weight(1f)
.padding(4.dp)
// Column modifier
val cModifier = Modifier.weight(1f)
val dModifier = Modifier
.fillMaxSize()
.weight(2f)
.padding(4.dp)
Row(cModifier) {
KeyboardButton(bModifier, KEY_7, allowVibration = false, onClick = addSymbol)
KeyboardButton(bModifier, KEY_8, allowVibration = false, onClick = addSymbol)
KeyboardButton(bModifier, KEY_9, allowVibration = false, onClick = addSymbol)
}
Row(cModifier) {
KeyboardButton(bModifier, KEY_4, allowVibration = false, onClick = addSymbol)
KeyboardButton(bModifier, KEY_5, allowVibration = false, onClick = addSymbol)
KeyboardButton(bModifier, KEY_6, allowVibration = false, onClick = addSymbol)
}
Row(cModifier) {
KeyboardButton(bModifier, KEY_1, allowVibration = false, onClick = addSymbol)
KeyboardButton(bModifier, KEY_2, allowVibration = false, onClick = addSymbol)
KeyboardButton(bModifier, KEY_3, allowVibration = false, onClick = addSymbol)
}
Row(cModifier) {
KeyboardButton(bModifier, KEY_0, allowVibration = false, onClick = addSymbol)
KeyboardButton(
dModifier,
KEY_CLEAR,
allowVibration = false,
onLongClick = clearSymbols
) { deleteSymbol() }
}
}
}

View File

@ -0,0 +1,46 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.epoch.component
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@Composable
fun TopPart(
modifier: Modifier,
unixToDate: Boolean,
dateField: @Composable() (LazyItemScope.() -> Unit),
unixField: @Composable() (LazyItemScope.() -> Unit),
) {
LazyColumn(
modifier = modifier,
verticalArrangement = Arrangement.Bottom
) {
if (unixToDate) {
item("unix") { unixField() }
item("date") { dateField() }
} else {
item("date") { dateField() }
item("unix") { unixField() }
}
}
}

View File

@ -0,0 +1,51 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.epoch.component
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayMedium
@Composable
fun UnixTextField(
modifier: Modifier,
unix: String
) {
Row(
modifier = modifier
.height(IntrinsicSize.Min)
.horizontalScroll(rememberScrollState()),
horizontalArrangement = Arrangement.End
) {
Text(
text = unix.ifEmpty { "0" },
modifier = modifier,
style = NumbersTextStyleDisplayMedium,
textAlign = TextAlign.End
)
}
}

View File

@ -0,0 +1,40 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.epoch.navigation
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import com.sadellie.unitto.feature.epoch.EpochRoute
private const val epochRoute = "epoch_route"
fun NavController.navigateToEpoch() {
navigate(epochRoute)
}
fun NavGraphBuilder.epochScreen(
navigateUpAction: () -> Unit
) {
composable(epochRoute) {
EpochRoute(
navigateUpAction = navigateUpAction
)
}
}

1
feature/tools/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,28 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
plugins {
id("unitto.library")
id("unitto.library.compose")
id("unitto.library.feature")
id("unitto.android.hilt")
}
android {
namespace = "com.sadellie.unitto.feature.tools"
}

View File

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>

View File

@ -0,0 +1,50 @@
package com.sadellie.unitto.feature.tools
import androidx.compose.foundation.clickable
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Schedule
import androidx.compose.material3.Icon
import androidx.compose.material3.ListItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.sadellie.unitto.core.ui.common.UnittoLargeTopAppBar
@Composable
internal fun ToolsScreen(
navigateUpAction: () -> Unit,
navigateToEpoch: () -> Unit
) {
UnittoLargeTopAppBar(
title = stringResource(R.string.tools_screen),
navigateUpAction = navigateUpAction
) { padding ->
LazyColumn(contentPadding = padding) {
item {
ListItem(
leadingContent = {
Icon(
Icons.Default.Schedule,
null // TODO describe
)
},
headlineText = { Text(stringResource(R.string.epoch_converter)) },
supportingText = { Text(stringResource(R.string.epoch_converter_support)) },
modifier = Modifier.clickable { navigateToEpoch() }
)
}
}
}
}
@Preview
@Composable
internal fun PreviewToolsScreen() {
ToolsScreen(
navigateUpAction = {},
navigateToEpoch = {}
)
}

View File

@ -0,0 +1,42 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.tools.navigation
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import com.sadellie.unitto.feature.tools.ToolsScreen
private const val toolsRoute = "tools_route"
fun NavController.navigateToTools() {
navigate(toolsRoute)
}
fun NavGraphBuilder.toolsScreen(
navigateUpAction: () -> Unit,
navigateToEpoch: () -> Unit
) {
composable(toolsRoute) {
ToolsScreen(
navigateUpAction = navigateUpAction,
navigateToEpoch = navigateToEpoch
)
}
}

View File

@ -23,3 +23,5 @@ include(":core:ui")
include(":feature:converter") include(":feature:converter")
include(":feature:unitslist") include(":feature:unitslist")
include(":feature:settings") include(":feature:settings")
include(":feature:tools")
include(":feature:epoch")