Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions app/src/main/kotlin/org/fossify/clock/activities/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@ import org.fossify.clock.BuildConfig
import org.fossify.clock.R
import org.fossify.clock.adapters.ViewPagerAdapter
import org.fossify.clock.databinding.ActivityMainBinding
import org.fossify.clock.extensions.config
import org.fossify.clock.extensions.getEnabledAlarms
import org.fossify.clock.extensions.rescheduleEnabledAlarms
import org.fossify.clock.extensions.updateWidgets
import org.fossify.clock.extensions.*
import org.fossify.clock.helpers.*
import org.fossify.commons.databinding.BottomTablayoutItemBinding
import org.fossify.commons.extensions.*
Expand Down Expand Up @@ -171,8 +168,10 @@ class MainActivity : SimpleActivity() {

override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
super.onActivityResult(requestCode, resultCode, resultData)
if (requestCode == PICK_AUDIO_FILE_INTENT_ID && resultCode == RESULT_OK && resultData != null) {
storeNewAlarmSound(resultData)
when {
requestCode == PICK_AUDIO_FILE_INTENT_ID && resultCode == RESULT_OK && resultData != null -> {
storeNewAlarmSound(resultData)
}
}
}

Expand Down
206 changes: 201 additions & 5 deletions app/src/main/kotlin/org/fossify/clock/activities/SettingsActivity.kt
Original file line number Diff line number Diff line change
@@ -1,24 +1,58 @@
package org.fossify.clock.activities

import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import org.fossify.clock.R
import org.fossify.clock.databinding.ActivitySettingsBinding
import org.fossify.clock.dialogs.ExportDataDialog
import org.fossify.clock.extensions.config
import org.fossify.clock.extensions.dbHelper
import org.fossify.clock.extensions.timerDb
import org.fossify.clock.helpers.DBHelper
import org.fossify.clock.helpers.DEFAULT_MAX_ALARM_REMINDER_SECS
import org.fossify.clock.helpers.DEFAULT_MAX_TIMER_REMINDER_SECS
import org.fossify.clock.helpers.DataExporter
import org.fossify.clock.helpers.DataImporter
import org.fossify.clock.helpers.TAB_ALARM
import org.fossify.clock.helpers.TAB_CLOCK
import org.fossify.clock.helpers.TAB_STOPWATCH
import org.fossify.clock.helpers.TAB_TIMER
import org.fossify.clock.helpers.TimerHelper
import org.fossify.commons.dialogs.FilePickerDialog
import org.fossify.commons.dialogs.RadioGroupDialog
import org.fossify.commons.extensions.*
import org.fossify.commons.extensions.beGoneIf
import org.fossify.commons.extensions.beVisibleIf
import org.fossify.commons.extensions.formatMinutesToTimeString
import org.fossify.commons.extensions.formatSecondsToTimeString
import org.fossify.commons.extensions.getCustomizeColorsString
import org.fossify.commons.extensions.getFileOutputStream
import org.fossify.commons.extensions.getProperPrimaryColor
import org.fossify.commons.extensions.getTempFile
import org.fossify.commons.extensions.isOrWasThankYouInstalled
import org.fossify.commons.extensions.launchPurchaseThankYouIntent
import org.fossify.commons.extensions.showErrorToast
import org.fossify.commons.extensions.showPickSecondsDialog
import org.fossify.commons.extensions.toFileDirItem
import org.fossify.commons.extensions.toast
import org.fossify.commons.extensions.updateTextColors
import org.fossify.commons.extensions.viewBinding
import org.fossify.commons.helpers.ExportResult
import org.fossify.commons.helpers.IS_CUSTOMIZING_COLORS
import org.fossify.commons.helpers.MINUTE_SECONDS
import org.fossify.commons.helpers.TAB_LAST_USED
import org.fossify.commons.helpers.NavigationIcon
import org.fossify.commons.helpers.PERMISSION_READ_STORAGE
import org.fossify.commons.helpers.PERMISSION_WRITE_STORAGE
import org.fossify.commons.helpers.TAB_LAST_USED
import org.fossify.commons.helpers.ensureBackgroundThread
import org.fossify.commons.helpers.isQPlus
import org.fossify.commons.helpers.isTiramisuPlus
import org.fossify.commons.models.RadioItem
import java.io.FileOutputStream
import java.io.OutputStream
import java.util.Locale
import kotlin.system.exitProcess

Expand All @@ -30,7 +64,12 @@ class SettingsActivity : SimpleActivity() {
super.onCreate(savedInstanceState)
setContentView(binding.root)

updateMaterialActivityViews(binding.settingsCoordinator, binding.settingsHolder, useTransparentNavigation = true, useTopSearchMenu = false)
updateMaterialActivityViews(
mainCoordinatorLayout = binding.settingsCoordinator,
nestedView = binding.settingsHolder,
useTransparentNavigation = true,
useTopSearchMenu = false
)
setupMaterialScrollListener(binding.settingsNestedScrollview, binding.settingsToolbar)
}

Expand All @@ -51,6 +90,8 @@ class SettingsActivity : SimpleActivity() {
setupTimerMaxReminder()
setupIncreaseVolumeGradually()
setupCustomizeWidgetColors()
setupExportData()
setupImportData()
updateTextColors(binding.settingsHolder)

arrayOf(
Expand Down Expand Up @@ -192,11 +233,13 @@ class SettingsActivity : SimpleActivity() {
}

private fun updateAlarmMaxReminderText() {
binding.settingsAlarmMaxReminder.text = formatSecondsToTimeString(config.alarmMaxReminderSecs)
binding.settingsAlarmMaxReminder.text =
formatSecondsToTimeString(config.alarmMaxReminderSecs)
}

private fun updateTimerMaxReminderText() {
binding.settingsTimerMaxReminder.text = formatSecondsToTimeString(config.timerMaxReminderSecs)
binding.settingsTimerMaxReminder.text =
formatSecondsToTimeString(config.timerMaxReminderSecs)
}

private fun setupCustomizeWidgetColors() {
Expand All @@ -207,4 +250,157 @@ class SettingsActivity : SimpleActivity() {
}
}
}

private fun setupExportData() {
binding.settingsExportDataHolder.setOnClickListener {
tryExportData()
}
}

private fun setupImportData() {
binding.settingsImportDataHolder.setOnClickListener {
tryImportData()
}
}

private val exportActivityResultLauncher =
registerForActivityResult(ActivityResultContracts.CreateDocument("application/json")) { uri ->
try {
val outputStream = uri?.let { contentResolver.openOutputStream(it) }
if (outputStream != null) {
exportDataTo(outputStream)
}
} catch (e: Exception) {
showErrorToast(e)
}
}

private val importActivityResultLauncher =
registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
try {
if (uri != null) {
tryImportDataFromFile(uri)
}
} catch (e: Exception) {
showErrorToast(e)
}
}

private fun exportDataTo(outputStream: OutputStream?) {
ensureBackgroundThread {
val alarms = dbHelper.getAlarms()
val timers = timerDb.getTimers()
if (alarms.isEmpty()) {
toast(org.fossify.commons.R.string.no_entries_for_exporting)
} else {
DataExporter.exportData(alarms, timers, outputStream) {
toast(
when (it) {
ExportResult.EXPORT_OK -> org.fossify.commons.R.string.exporting_successful
else -> org.fossify.commons.R.string.exporting_failed
}
)
}
}
}
}

private fun tryExportData() {
if (isQPlus()) {
ExportDataDialog(this, config.lastDataExportPath, true) { file ->
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
putExtra(Intent.EXTRA_TITLE, file.name)
addCategory(Intent.CATEGORY_OPENABLE)

try {
exportActivityResultLauncher.launch(file.name)
} catch (e: ActivityNotFoundException) {
toast(
org.fossify.commons.R.string.system_service_disabled,
Toast.LENGTH_LONG
)
} catch (e: Exception) {
showErrorToast(e)
}
}
}
} else {
handlePermission(PERMISSION_WRITE_STORAGE) { isAllowed ->
if (isAllowed) {
ExportDataDialog(this, config.lastDataExportPath, false) { file ->
getFileOutputStream(file.toFileDirItem(this), true) { out ->
exportDataTo(out)
}
}
}
}
}
}

private fun tryImportData() {
if (isQPlus()) {
Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/json"

try {
importActivityResultLauncher.launch(type)
} catch (e: ActivityNotFoundException) {
toast(org.fossify.commons.R.string.system_service_disabled, Toast.LENGTH_LONG)
} catch (e: Exception) {
showErrorToast(e)
}
}
} else {
handlePermission(PERMISSION_READ_STORAGE) { isAllowed ->
if (isAllowed) {
pickFileToImportData()
}
}
}
}

private fun pickFileToImportData() {
FilePickerDialog(this) {
importData(it)
}
}

private fun tryImportDataFromFile(uri: Uri) {
when (uri.scheme) {
"file" -> importData(uri.path!!)
"content" -> {
val tempFile = getTempFile("fossify_clock_data", "fossify_clock_data.json")
if (tempFile == null) {
toast(org.fossify.commons.R.string.unknown_error_occurred)
return
}

try {
val inputStream = contentResolver.openInputStream(uri)
val out = FileOutputStream(tempFile)
inputStream!!.copyTo(out)
importData(tempFile.absolutePath)
} catch (e: Exception) {
showErrorToast(e)
}
}

else -> toast(org.fossify.commons.R.string.invalid_file_format)
}
}

private fun importData(path: String) {
ensureBackgroundThread {
val result =
DataImporter(this, DBHelper.dbInstance!!, TimerHelper(this)).importData(path)
toast(
when (result) {
DataImporter.ImportResult.IMPORT_OK -> org.fossify.commons.R.string.importing_successful
DataImporter.ImportResult.IMPORT_INCOMPLETE -> org.fossify.commons.R.string.no_new_entries_for_importing
DataImporter.ImportResult.IMPORT_FAIL -> org.fossify.commons.R.string.no_items_found
}
)
}
}
}
72 changes: 72 additions & 0 deletions app/src/main/kotlin/org/fossify/clock/dialogs/ExportDataDialog.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package org.fossify.clock.dialogs

import androidx.appcompat.app.AlertDialog
import org.fossify.commons.activities.BaseSimpleActivity
import org.fossify.commons.dialogs.FilePickerDialog
import org.fossify.commons.extensions.*
import org.fossify.commons.helpers.ensureBackgroundThread
import org.fossify.clock.R
import org.fossify.clock.databinding.DialogExportDataBinding
import org.fossify.clock.extensions.config
import org.fossify.clock.helpers.DATA_EXPORT_EXTENSION
import java.io.File

class ExportDataDialog(
val activity: BaseSimpleActivity,
val path: String,
val hidePath: Boolean,
callback: (file: File) -> Unit,
) {
private var realPath = path.ifEmpty { activity.internalStoragePath }
private val config = activity.config

init {
val view = DialogExportDataBinding.inflate(activity.layoutInflater, null, false).apply {
exportDataFolder.text = activity.humanizePath(realPath)
exportDataFilename.setText("${activity.getString(R.string.settings_export_data)}_${activity.getCurrentFormattedDateTime()}")

if (hidePath) {
exportDataFolderLabel.beGone()
exportDataFolder.beGone()
} else {
exportDataFolder.setOnClickListener {
FilePickerDialog(activity, realPath, false, showFAB = true) {
exportDataFolder.text = activity.humanizePath(it)
realPath = it
}
}
}
}

activity.getAlertDialogBuilder()
.setPositiveButton(org.fossify.commons.R.string.ok, null)
.setNegativeButton(org.fossify.commons.R.string.cancel, null)
.apply {
activity.setupDialogStuff(view.root, this, R.string.settings_export_data) { alertDialog ->
alertDialog.showKeyboard(view.exportDataFilename)
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
val filename = view.exportDataFilename.value
when {
filename.isEmpty() -> activity.toast(org.fossify.commons.R.string.empty_name)
filename.isAValidFilename() -> {
val file = File(realPath, "$filename$DATA_EXPORT_EXTENSION")
if (!hidePath && file.exists()) {
activity.toast(org.fossify.commons.R.string.name_taken)
return@setOnClickListener
}

ensureBackgroundThread {
config.lastDataExportPath = file.absolutePath.getParentPath()
callback(file)
alertDialog.dismiss()
}
}

else -> activity.toast(org.fossify.commons.R.string.invalid_name)
}
}
}
}
}
}

4 changes: 4 additions & 0 deletions app/src/main/kotlin/org/fossify/clock/helpers/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,8 @@ class Config(context: Context) : BaseConfig(context) {
var wasInitialWidgetSetUp: Boolean
get() = prefs.getBoolean(WAS_INITIAL_WIDGET_SET_UP, false)
set(wasInitialWidgetSetUp) = prefs.edit().putBoolean(WAS_INITIAL_WIDGET_SET_UP, wasInitialWidgetSetUp).apply()

var lastDataExportPath: String
get() = prefs.getString(LAST_DATA_EXPORT_PATH, "")!!
set(lastDataExportPath) = prefs.edit().putString(LAST_DATA_EXPORT_PATH, lastDataExportPath).apply()
}
2 changes: 2 additions & 0 deletions app/src/main/kotlin/org/fossify/clock/helpers/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const val INCREASE_VOLUME_GRADUALLY = "increase_volume_gradually"
const val ALARMS_SORT_BY = "alarms_sort_by"
const val STOPWATCH_LAPS_SORT_BY = "stopwatch_laps_sort_by"
const val WAS_INITIAL_WIDGET_SET_UP = "was_initial_widget_set_up"
const val DATA_EXPORT_EXTENSION = ".json"
const val LAST_DATA_EXPORT_PATH = "last_alarms_export_path"

const val TABS_COUNT = 4
const val EDITED_TIME_ZONE_SEPARATOR = ":"
Expand Down
Loading
Loading