Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c85247dd1 | ||
|
|
8f02da5c35 | ||
|
|
3b2e433757 | ||
|
|
ce18b47cbd | ||
|
|
9eb2a55350 | ||
|
|
56d8f37ab7 | ||
|
|
2106ad1ddc |
@@ -0,0 +1,209 @@
|
||||
# Index
|
||||
|
||||
- [What is Markdown?](#what-is-markdown)
|
||||
- [What is CommonMark?](#what-is-commonmark)
|
||||
- [What is GitHub Flavored Markdown?](#what-is-github-flavored-markdown)
|
||||
- [What is LaTeX?](#what-is-latex)
|
||||
- [How to use LaTeX in Markdown?](#how-to-use-latex-in-markdown)
|
||||
- [What is Flux?](#what-is-flux-approach)
|
||||
- [What has Flux offered?](#what-has-flux-offered)
|
||||
- [Some Basic Markdown Syntax](#some-basic-markdown-syntax)
|
||||
- [Some Extended Markdown Syntax](#some-extended-markdown-syntax)
|
||||
- [LaTeX Math Syntax](#latex-math-syntax)
|
||||
- [Greek Letters](#greek-letters)
|
||||
- [References](#references)
|
||||
|
||||
# What is Markdown?
|
||||
|
||||
Markdown is a plain text format for writing structured documents, based on conventions for
|
||||
indicating formatting in email and usenet posts.
|
||||
It was developed by John Gruber (with help from Aaron Swartz) and released in 2004 in the form of a
|
||||
syntax description and a Perl script (Markdown.pl) for converting Markdown to HTML.
|
||||
|
||||
In the next decade, dozens of implementations were developed in many languages.
|
||||
Some extended the original Markdown syntax with conventions for footnotes, tables, and other
|
||||
document elements.
|
||||
Some allowed Markdown documents to be rendered in formats other than HTML.
|
||||
|
||||
Websites like Reddit, StackOverflow, and GitHub had millions of people using Markdown.
|
||||
And Markdown started to be used beyond the web, to author books, articles, slide shows, letters, and
|
||||
lecture notes.
|
||||
|
||||
# What is CommonMark?
|
||||
|
||||
CommonMark was developed to address inconsistencies and ambiguities in Markdown.
|
||||
|
||||
The CommonMark specification defines rules for elements such as headings, lists, links, emphasis,
|
||||
and code blocks, among others.
|
||||
|
||||
By adhering to the CommonMark standard, developers can ensure consistent rendering of Markdown
|
||||
content across different applications and platforms.
|
||||
|
||||
# What is GitHub Flavored Markdown?
|
||||
|
||||
GitHub Flavored Markdown, often shortened as GFM, is the dialect of Markdown that is currently
|
||||
supported for user content on GitHub.com and GitHub Enterprise.
|
||||
|
||||
This formal specification, based on the CommonMark Spec, defines the syntax and semantics of this
|
||||
dialect.
|
||||
|
||||
GFM is a strict superset of CommonMark. All the features which are supported in GitHub user content
|
||||
and that are not specified on the original CommonMark Spec are hence known as extensions, and
|
||||
highlighted as such.
|
||||
|
||||
While GFM supports a wide range of inputs, it’s worth noting that GitHub.com and GitHub Enterprise
|
||||
perform additional post-processing and sanitization after GFM is converted to HTML to ensure
|
||||
security and consistency of the website.
|
||||
|
||||
# What is LaTeX?
|
||||
|
||||
LaTeX is a typesetting system commonly used for producing scientific and mathematical documents.
|
||||
|
||||
LaTeX provides a powerful way to represent mathematical notation in documents, allowing users to
|
||||
create complex equations and formulas with ease.
|
||||
|
||||
# How to use LaTeX in Markdown?
|
||||
|
||||
Mathematical expressions are key to information sharing amongst engineers, scientists, data
|
||||
scientists, and mathematicians.
|
||||
|
||||
You can use the $ and $$ delimiters in GFM to insert math expressions in TeX and LaTeX style syntax.
|
||||
|
||||
# What is Flux Approach
|
||||
|
||||
Flux is built around the idea that information should be organized based on context, intent, and workflow rather than being confined to a single linear note system.
|
||||
|
||||
At the core of this approach is the concept of **Workspaces**.
|
||||
|
||||
A workspace acts as an independent environment that users can create based on specific needs or domains of their life or work. For example, a user might create separate workspaces for Personal Life, Work Projects, Research, Study, or Fitness Tracking.
|
||||
|
||||
Each workspace functions as a self-contained organizational unit, allowing users to keep related data structured and isolated without interference from other contexts.
|
||||
|
||||
Within every workspace, Flux provides multiple **Templates** designed for different types of information and productivity workflows. These templates include, but are not limited to:
|
||||
|
||||
* **Notes** — Rich Markdown documents with media and LaTeX support
|
||||
* **Todo** — Task tracking with completion states and prioritization
|
||||
* **Events** — Scheduled items with time and reminder support
|
||||
* **Calendar** — Temporal visualization of events and plans
|
||||
* **Journal** — Chronological personal or professional entries
|
||||
* **Analytics** — Insights and productivity metrics derived from workspace data
|
||||
|
||||
This modular structure allows users to mix and match templates depending on their requirements. A research workspace might emphasize notes and analytics, while a personal workspace may focus on journals, todos, and events.
|
||||
|
||||
By separating data into workspaces and structuring content through templates, Flux enables scalable organization, reduces cognitive clutter, and supports diverse productivity systems within a single unified platform.
|
||||
|
||||
# What has Flux offered?
|
||||
|
||||
Flux supports CommonMark and GitHub Flavored Markdown (GFM) syntax, as well as LaTeX math
|
||||
syntax.
|
||||
|
||||
This allows users to create richly formatted notes with support for headings, lists, links,
|
||||
emphasis, code blocks, tables, and mathematical expressions, and allows notes to be exported in
|
||||
various formats including TXT, MD (Markdown), and HTML.
|
||||
|
||||
# Some Basic Markdown Syntax
|
||||
|
||||
| Element | Syntax |
|
||||
|:--------------:|:----------------------------------:|
|
||||
| Heading | `# H1` <br/> `## H2`<br/> `### H3` |
|
||||
| Italic | `_italic_ or *italic*` |
|
||||
| Bold | `**bold** or __bold__` |
|
||||
| Blockquote | `> Blockquote` |
|
||||
| Link | `[title](https://www.example.com)` |
|
||||
| Code | `` `code` `` |
|
||||
| Ordered List | `1. List item 1 or 2) List item 2` |
|
||||
| Unordered List | `- Apple or + Banana or * Orange` |
|
||||
| Image | `` |
|
||||
|
||||
# Some Extended Markdown Syntax
|
||||
|
||||
| Element | Syntax |
|
||||
|:-------------:|:----------------------------------:|
|
||||
| Strikethrough | `~~Strikethrough~~` |
|
||||
| Underline | `++Underline++` |
|
||||
| Highlight | `==Marking==` |
|
||||
| Task List | `- [x] Task 1`<br/> `- [ ] Task 2` |
|
||||
| Heading ID | `# Heading {#custom-id}` |
|
||||
|
||||
# LaTeX Math Syntax
|
||||
|
||||
| Element | Syntax |
|
||||
|:-----------:|:------------------:|
|
||||
| Inline | `$x^2$` |
|
||||
| Display | `$$x^2$$` |
|
||||
| newline | `\\ or \\\\` |
|
||||
| Whitespace | `\quad or \` |
|
||||
| Superscript | `x^2` |
|
||||
| Subscript | `y_1` |
|
||||
| Expression | `{x}` |
|
||||
| Overline | `\overline{x}` |
|
||||
| Underline | `\underline{x}` |
|
||||
| Fraction | `\frac{x}{y}` |
|
||||
| Left Paren | `\left(` |
|
||||
| Right Paren | `\right)` |
|
||||
| Parentheses | `\left(x+y\right)` |
|
||||
| Brackets | `\left[x+y\right]` |
|
||||
| Braces | `\left{x+y\right}` |
|
||||
| Root | `\sqrt[n]{x}` |
|
||||
| × | `\times` |
|
||||
| ÷ | `\div` |
|
||||
| ± | `\pm` |
|
||||
| ≠ | `\neq` |
|
||||
| ≈ | `\approx` |
|
||||
| ≤ | `\leq` |
|
||||
| ≥ | `\geq` |
|
||||
| ∞ | `\infty` |
|
||||
| ∑ | `\sum` |
|
||||
| ∏ | `\prod` |
|
||||
| ∫ | `\int` |
|
||||
| ∑ | `\sum` |
|
||||
| lim | `\lim` |
|
||||
| ∀ | `\forall` |
|
||||
| ∃ | `\exists` |
|
||||
| ∴ | `\therefore` |
|
||||
| ∵ | `\because` |
|
||||
| ⊂ | `\subset` |
|
||||
| ⊃ | `\supset` |
|
||||
| ⊆ | `\subseteq` |
|
||||
| ⊇ | `\supseteq` |
|
||||
| ∈ | `\in` |
|
||||
| ∉ | `\notin` |
|
||||
|
||||
# Greek Letters
|
||||
|
||||
| Uppercase | Syntax | Lowercase | Syntax |
|
||||
|:---------:|:----------:|:---------:|:----------:|
|
||||
| A | `A` | α | `\alpha` |
|
||||
| Β | `B` | β | `\beta` |
|
||||
| Γ | `\Gamma` | γ | `\gamma` |
|
||||
| Δ | `\Delta` | δ | `\delta` |
|
||||
| Ε | `E` | ε | `\epsilon` |
|
||||
| Ζ | `Z` | ζ | `\zeta` |
|
||||
| Η | `H` | η | `\eta` |
|
||||
| Θ | `\Theta` | θ | `\theta` |
|
||||
| Ι | `I` | ι | `\iota` |
|
||||
| Κ | `K` | κ | `\kappa` |
|
||||
| Λ | `\Lambda` | λ | `\lambda` |
|
||||
| Μ | `M` | μ | `\mu` |
|
||||
| Ν | `N` | ν | `\nu` |
|
||||
| Ξ | `\Xi` | ξ | `\xi` |
|
||||
| Ο | `O` | ο | `\omicron` |
|
||||
| Π | `\Pi` | π | `\pi` |
|
||||
| Ρ | `P` | ρ | `\rho` |
|
||||
| Σ | `\Sigma` | σ | `\sigma` |
|
||||
| Τ | `T` | τ | `\tau` |
|
||||
| Υ | `\Upsilon` | υ | `\upsilon` |
|
||||
| Φ | `\Phi` | φ | `\phi` |
|
||||
| Χ | `X` | χ | `\chi` |
|
||||
| Ψ | `\Psi` | ψ | `\psi` |
|
||||
| Ω | `\Omega` | ω | `\omega` |
|
||||
|
||||
# References
|
||||
|
||||
You can find more information about Markdown, CommonMark, GitHub Flavored Markdown, LaTeX Math, and
|
||||
Mermaid at the following links:
|
||||
|
||||
- [CommonMark](https://commonmark.org/)
|
||||
- [GitHub Flavored Markdown](https://github.github.com/gfm/)
|
||||
- [LaTeX](https://www.latex-project.org/)
|
||||
- [Mermaid](https://mermaid.js.org/)
|
||||
@@ -0,0 +1,41 @@
|
||||
## Privacy Policy of Open Note
|
||||
|
||||
Flux is an open-source Android app developed by Ronit Chinda.
|
||||
The source code is available on GitHub under the GPL-3.0 license.
|
||||
|
||||
### Data Collection
|
||||
|
||||
Flux does not collect any personal or confidential information such as addresses, names, or email addresses.
|
||||
|
||||
### Permissions Requested
|
||||
|
||||
The app requires the following permissions, as listed in the `AndroidManifest.xml` file:
|
||||
|
||||
https://github.com/chindaronit/Flux/blob/master/app/src/main/AndroidManifest.xml
|
||||
|
||||
| Permission | Purpose |
|
||||
|---------------------------------|---------------------------------------------------------------------------------------------------------------|
|
||||
| `android.permission.USE_BIOMETRIC` | Enables biometric authentication to provide secure access to protected notes |
|
||||
| `android.permission.INTERNET` | Allows network connectivity for loading web content (images, videos, and other media) when rendering markdown |
|
||||
| `android.permission.RECEIVE_BOOT_COMPLETED` | Allows the app to receive the system broadcast after device boot, enabling it to restart scheduled tasks such as reminders, alarms, or background sync services |
|
||||
| `android.permission.POST_NOTIFICATIONS` | Grants permission to display notifications to the user (required on Android 13+), used for reminders, updates, or alerting about scheduled events |
|
||||
| `android.permission.SCHEDULE_EXACT_ALARM` | Allows the app to schedule precise alarms that trigger at exact times (e.g., note reminders, task deadlines), bypassing battery optimizations that delay inexact alarms |
|
||||
|
||||
### Dependencies
|
||||
|
||||
The app uses the following dependencies:
|
||||
|
||||
- **Room**: For local database management.
|
||||
- **Hilt**: For dependency injection.
|
||||
- **Compose**: For building the UI.
|
||||
- **CommonMark**: For markdown rendering and parsing.
|
||||
|
||||
### Data Sharing
|
||||
Flux does not share any personal or sensitive user data with third parties.
|
||||
|
||||
### Data Deletion
|
||||
Usually all data is stored locally and can be cleared by the user at any time.
|
||||
|
||||
---
|
||||
|
||||
If you have any questions about this policy or personal information protection, please send your inquiries, opinions, or suggestions to: chindaronit04@gmail.com
|
||||
@@ -75,7 +75,7 @@ To build and run this application, you need to install the latest version of And
|
||||
you can clone this repository from GitHub and open it in Android Studio.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/YangDai2003/OpenNote.git
|
||||
git clone git@github.com:chindaronit/Flux.git
|
||||
```
|
||||
|
||||
In Android Studio, select `Run > Run 'app'` to start the application.
|
||||
|
||||
@@ -15,8 +15,8 @@ android {
|
||||
applicationId = "com.flux"
|
||||
minSdk = 29
|
||||
targetSdk = 36
|
||||
versionCode = 5
|
||||
versionName = "3.0"
|
||||
versionCode = 7
|
||||
versionName = "3.1.1"
|
||||
}
|
||||
|
||||
dependenciesInfo {
|
||||
@@ -92,6 +92,8 @@ dependencies {
|
||||
implementation(libs.androidx.biometric)
|
||||
implementation(libs.androidx.browser)
|
||||
implementation(libs.androidx.documentfile)
|
||||
implementation(libs.androidx.work.runtime.ktx)
|
||||
implementation(libs.androidx.compose.foundation.layout)
|
||||
|
||||
// Hilt
|
||||
ksp(libs.hilt.android.compiler)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||
|
||||
<application
|
||||
android:name=".di.Flux"
|
||||
|
||||
@@ -10,7 +10,9 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SnackbarDuration
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.SnackbarResult
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -22,6 +24,7 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.flux.navigation.AppNavHost
|
||||
import com.flux.navigation.Loader
|
||||
import com.flux.other.Constants.Other
|
||||
import com.flux.other.createNotificationChannel
|
||||
import com.flux.ui.effects.ScreenEffect
|
||||
import com.flux.ui.theme.FluxTheme
|
||||
@@ -34,6 +37,9 @@ import com.flux.ui.viewModel.SettingsViewModel
|
||||
import com.flux.ui.viewModel.TodoViewModel
|
||||
import com.flux.ui.viewModel.WorkspaceViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : AppCompatActivity() {
|
||||
@@ -73,18 +79,11 @@ class MainActivity : AppCompatActivity() {
|
||||
// Stop splash screen when settings are loaded
|
||||
LaunchedEffect(settings.isLoading) { keepSplashScreen.value = settings.isLoading }
|
||||
|
||||
// Snackbar effect
|
||||
LaunchedEffect(Unit) {
|
||||
workspaceViewModel.effect.collect { effect ->
|
||||
if (effect is ScreenEffect.ShowSnackBarMessage) {
|
||||
snackBarHostState.currentSnackbarData?.dismiss()
|
||||
snackBarHostState.showSnackbar(
|
||||
message = effect.message,
|
||||
duration = SnackbarDuration.Short
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
HandleSideEffects(
|
||||
snackBarHostState,
|
||||
workspaceViewModel.effect,
|
||||
settingsViewModel.effect
|
||||
)
|
||||
|
||||
if (!settings.isLoading) {
|
||||
FluxTheme(settings) {
|
||||
@@ -117,4 +116,30 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HandleSideEffects(
|
||||
snackbarHostState: SnackbarHostState,
|
||||
vararg effectFlows: Flow<ScreenEffect>
|
||||
) {
|
||||
LaunchedEffect(Other.SIDE_EFFECT_KEY) {
|
||||
effectFlows.forEach { effectFlow ->
|
||||
effectFlow
|
||||
.onEach { effect ->
|
||||
if (effect is ScreenEffect.ShowSnackBarMessage) {
|
||||
val result = snackbarHostState.showSnackbar(
|
||||
message = effect.message,
|
||||
actionLabel = "Dismiss",
|
||||
duration = SnackbarDuration.Short
|
||||
)
|
||||
|
||||
if (result == SnackbarResult.ActionPerformed) {
|
||||
snackbarHostState.currentSnackbarData?.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
.launchIn(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ import java.util.UUID
|
||||
|
||||
@Database(
|
||||
entities = [EventModel::class, LabelModel::class, EventInstanceModel::class, SettingsModel::class, NotesModel::class, HabitModel::class, HabitInstanceModel::class, WorkspaceModel::class, TodoModel::class, JournalModel::class],
|
||||
version = 4,
|
||||
version = 5,
|
||||
exportSchema = false
|
||||
)
|
||||
@TypeConverters(Converter::class)
|
||||
@@ -322,3 +322,9 @@ val MIGRATION_3_4 = object : Migration(3, 4) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val Migration_4_5 = object : Migration(4, 5) {
|
||||
override fun migrate(db: SupportSQLiteDatabase) {
|
||||
db.execSQL("ALTER TABLE SettingsModel ADD COLUMN backupFrequency INTEGER NOT NULL DEFAULT 0")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,5 +26,6 @@ data class SettingsModel(
|
||||
val isLintValid: Boolean = false,
|
||||
val isLineNumbersVisible: Boolean = false,
|
||||
val startWithReadView: Boolean = false,
|
||||
val storageRootUri: String? = null
|
||||
val storageRootUri: String? = null,
|
||||
val backupFrequency: Int = 0
|
||||
)
|
||||
|
||||
@@ -16,6 +16,7 @@ import com.flux.data.database.FluxDatabase
|
||||
import com.flux.data.database.MIGRATION_1_2
|
||||
import com.flux.data.database.MIGRATION_2_3
|
||||
import com.flux.data.database.MIGRATION_3_4
|
||||
import com.flux.data.database.Migration_4_5
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
@@ -35,7 +36,7 @@ object DataModule {
|
||||
FluxDatabase::class.java,
|
||||
"FluxDatabase"
|
||||
)
|
||||
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
|
||||
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, Migration_4_5)
|
||||
.build()
|
||||
|
||||
@Singleton
|
||||
|
||||
@@ -19,9 +19,9 @@ import com.flux.ui.screens.journal.EditJournal
|
||||
import com.flux.ui.screens.notes.EditLabels
|
||||
import com.flux.ui.screens.notes.NoteDetails
|
||||
import com.flux.ui.screens.settings.About
|
||||
import com.flux.ui.screens.settings.Backup
|
||||
import com.flux.ui.screens.settings.Contact
|
||||
import com.flux.ui.screens.settings.Customize
|
||||
import com.flux.ui.screens.settings.Data
|
||||
import com.flux.ui.screens.settings.Editor
|
||||
import com.flux.ui.screens.settings.Languages
|
||||
import com.flux.ui.screens.settings.Privacy
|
||||
@@ -171,8 +171,8 @@ val SettingsScreens =
|
||||
NavRoutes.Contact.route to { navController, _, states, _ ->
|
||||
Contact(navController, states.settings.data.cornerRadius)
|
||||
},
|
||||
NavRoutes.Backup.route to { navController, _, states, viewModels ->
|
||||
Backup(navController, states.settings.data.cornerRadius, viewModels.backupViewModel)
|
||||
NavRoutes.Backup.route to { navController, snackbarHostState, states, viewModels ->
|
||||
Data(navController, states.settings.data.cornerRadius, states.settings, snackbarHostState, viewModels.backupViewModel, viewModels.settingsViewModel::onEvent)
|
||||
},
|
||||
NavRoutes.Editor.route to { navController, _, states, viewModels ->
|
||||
Editor(navController, states.settings, viewModels.settingsViewModel::onEvent)
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.flux.other
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.media.MediaRecorder
|
||||
import android.os.Build
|
||||
import androidx.core.content.ContextCompat
|
||||
import java.io.File
|
||||
|
||||
class AudioRecorder(private val context: Context) {
|
||||
private var recorder: MediaRecorder? = null
|
||||
private var tempFile: File? = null
|
||||
private var isRecording = false
|
||||
|
||||
fun hasMicPermission(context: Context): Boolean {
|
||||
return ContextCompat.checkSelfPermission(
|
||||
context,
|
||||
Manifest.permission.RECORD_AUDIO
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
// Start recording into a temp cache file
|
||||
fun startRecording() {
|
||||
if (isRecording) return
|
||||
tempFile = File(
|
||||
context.cacheDir,
|
||||
"temp_audio_${System.currentTimeMillis()}.m4a"
|
||||
)
|
||||
|
||||
val mediaRecorder =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
MediaRecorder(context)
|
||||
} else {
|
||||
MediaRecorder()
|
||||
}
|
||||
|
||||
recorder = mediaRecorder.apply {
|
||||
|
||||
setAudioSource(MediaRecorder.AudioSource.MIC)
|
||||
|
||||
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
|
||||
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
|
||||
|
||||
setAudioEncodingBitRate(128_000)
|
||||
setAudioSamplingRate(44_100)
|
||||
|
||||
setOutputFile(tempFile!!.absolutePath)
|
||||
|
||||
prepare()
|
||||
start()
|
||||
}
|
||||
|
||||
isRecording = true
|
||||
}
|
||||
|
||||
// Stop recording and return temp file
|
||||
fun stopRecording(): File? {
|
||||
if (!isRecording) return null
|
||||
|
||||
return try {
|
||||
|
||||
recorder?.apply {
|
||||
stop()
|
||||
release()
|
||||
}
|
||||
|
||||
val file = tempFile
|
||||
|
||||
recorder = null
|
||||
tempFile = null
|
||||
isRecording = false
|
||||
|
||||
file
|
||||
|
||||
} catch (_: Exception) {
|
||||
|
||||
// Cleanup corrupted file
|
||||
tempFile?.delete()
|
||||
|
||||
recorder = null
|
||||
tempFile = null
|
||||
isRecording = false
|
||||
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
// Delete temp recording without saving
|
||||
fun deleteRecording() {
|
||||
|
||||
try {
|
||||
recorder?.apply {
|
||||
stop()
|
||||
release()
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
// Ignore stop failure if not started
|
||||
}
|
||||
|
||||
tempFile?.delete()
|
||||
|
||||
recorder = null
|
||||
tempFile = null
|
||||
isRecording = false
|
||||
}
|
||||
|
||||
// Release resources (lifecycle safety)
|
||||
fun release() {
|
||||
try {
|
||||
recorder?.release()
|
||||
} catch (_: Exception) {}
|
||||
|
||||
recorder = null
|
||||
tempFile = null
|
||||
isRecording = false
|
||||
}
|
||||
|
||||
// For waveform UI
|
||||
fun getAmplitude(): Int {
|
||||
return recorder?.maxAmplitude ?: 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.flux.other
|
||||
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class BackupManager(
|
||||
private val workManager: WorkManager
|
||||
) {
|
||||
fun scheduleBackup(frequency: BackupFrequency) {
|
||||
if (frequency == BackupFrequency.NEVER) {
|
||||
workManager.cancelUniqueWork(BACKUP_WORK_NAME)
|
||||
return
|
||||
}
|
||||
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiresBatteryNotLow(true)
|
||||
.setRequiresStorageNotLow(true)
|
||||
.build()
|
||||
|
||||
val backupRequest = PeriodicWorkRequestBuilder<BackupWorker>(
|
||||
frequency.days.toLong(), TimeUnit.DAYS
|
||||
)
|
||||
.setConstraints(constraints)
|
||||
.build()
|
||||
|
||||
workManager.enqueueUniquePeriodicWork(
|
||||
BACKUP_WORK_NAME,
|
||||
ExistingPeriodicWorkPolicy.UPDATE,
|
||||
backupRequest
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val BACKUP_WORK_NAME = "database_backup_work"
|
||||
const val BACKUP_FREQUENCY_KEY = "backup_frequency"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.flux.other
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.net.toUri
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.flux.R
|
||||
import com.flux.data.database.FluxBackup
|
||||
import com.flux.data.model.SettingsModel
|
||||
import com.flux.di.DataModule
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.OutputStreamWriter
|
||||
|
||||
enum class BackupFrequency(val days: Int, val textRes: Int) {
|
||||
NEVER(0, R.string.never),
|
||||
DAILY(1, R.string.daily),
|
||||
WEEKLY(7, R.string.weekly),
|
||||
MONTHLY(30, R.string.monthly);
|
||||
}
|
||||
|
||||
class BackupWorker(
|
||||
appContext: Context,
|
||||
workerParams: WorkerParameters
|
||||
) : CoroutineWorker(appContext, workerParams) {
|
||||
|
||||
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val context = applicationContext
|
||||
val fluxDatabase = DataModule.provideFluxDatabase(context)
|
||||
val settingsDao = DataModule.provideSettingsDao(fluxDatabase)
|
||||
val notesDao = DataModule.provideNotesDao(fluxDatabase)
|
||||
val workspaceDao = DataModule.provideWorkspaceDao(fluxDatabase)
|
||||
val todoDao = DataModule.provideTodoDao(fluxDatabase)
|
||||
val habitDao = DataModule.provideHabitDao(fluxDatabase)
|
||||
val habitInstanceDao = DataModule.provideHabitInstanceDao(fluxDatabase)
|
||||
val journalDao = DataModule.provideJournalDao(fluxDatabase)
|
||||
val labelDao = DataModule.provideLabelDao(fluxDatabase)
|
||||
val eventDao = DataModule.provideEventDao(fluxDatabase)
|
||||
val eventInstanceDao = DataModule.provideEventInstanceDao(fluxDatabase)
|
||||
val settings = settingsDao.loadSetting()
|
||||
val rootUri = settings?.storageRootUri?.toUri()?: "".toUri()
|
||||
|
||||
val openNoteDir = getOrCreateDirectory(context, rootUri, Constants.File.FLUX)
|
||||
val backupDir = openNoteDir?.let { dir ->
|
||||
getOrCreateDirectory(context, dir.uri, Constants.File.FLUX_BACKUP)
|
||||
}
|
||||
backupDir?.let { dir ->
|
||||
val backup = FluxBackup(
|
||||
workspaces = workspaceDao.getAll(),
|
||||
notes = notesDao.loadAllNotes(),
|
||||
todos = todoDao.loadAllLists(),
|
||||
habits = habitDao.loadAllHabits(),
|
||||
habitInstances = habitInstanceDao.loadAllInstances(),
|
||||
journals = journalDao.loadAllEntries(),
|
||||
labels = labelDao.getAll(),
|
||||
events = eventDao.loadAllEvents(),
|
||||
eventInstances = eventInstanceDao.getAll(),
|
||||
settings = settingsDao.loadSetting()?: SettingsModel()
|
||||
)
|
||||
val json = Json.encodeToString(FluxBackup.serializer(), backup)
|
||||
|
||||
val fileName = "${System.currentTimeMillis()}.json"
|
||||
val file = dir.createFile("application/json", fileName)
|
||||
|
||||
file?.let { docFile ->
|
||||
context.contentResolver.openOutputStream(docFile.uri)
|
||||
?.use { outputStream ->
|
||||
OutputStreamWriter(outputStream).use { writer ->
|
||||
writer.write(json)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result.success()
|
||||
} catch (_: Exception) {
|
||||
Result.failure()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
class BootReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action == Intent.ACTION_BOOT_COMPLETED ||
|
||||
@@ -49,7 +48,6 @@ class BootReceiver : BroadcastReceiver() {
|
||||
val events = eventRepository.loadAllEvents()
|
||||
|
||||
return habits.filter { it.isLive() } + events
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,11 @@ object Constants {
|
||||
const val FLUX_IMAGES = "Images"
|
||||
const val FLUX_AUDIO = "Audio"
|
||||
const val FLUX_VIDEOS = "Videos"
|
||||
const val FLUX_BACKUP = "Backup"
|
||||
}
|
||||
|
||||
object Other {
|
||||
const val SIDE_EFFECT_KEY = "side_effect"
|
||||
}
|
||||
|
||||
object Editor {
|
||||
|
||||
@@ -58,10 +58,8 @@ import org.commonmark.node.StrongEmphasis
|
||||
import org.commonmark.node.Text
|
||||
import org.commonmark.parser.IncludeSourceSpans
|
||||
import org.commonmark.parser.Parser
|
||||
import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter
|
||||
import androidx.core.net.toUri
|
||||
|
||||
|
||||
fun Int.toHexColor(): String {
|
||||
return String.format("#%06X", 0xFFFFFF and this)
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ import com.flux.data.model.getSpacesList
|
||||
import com.flux.other.HeaderNode
|
||||
import com.flux.other.icons
|
||||
import com.flux.other.workspaceIconList
|
||||
import com.flux.ui.screens.events.formatCustom
|
||||
import com.flux.ui.screens.events.formatMonthly
|
||||
import com.flux.ui.screens.events.formatOnce
|
||||
import com.flux.ui.screens.events.formatYearly
|
||||
@@ -479,7 +480,7 @@ fun RecurrenceBottomSheet(
|
||||
value = rule.everyXDays.toString(),
|
||||
onValueChange = { new -> new.toIntOrNull()?.let { tempRule = rule.copy(everyXDays = it) } },
|
||||
modifier = Modifier.padding(start = 8.dp),
|
||||
label = { Text(stringResource(R.string.recurrence_every_x_days)) },
|
||||
label = { Text(formatCustom(rule)) },
|
||||
singleLine = true
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.flux.ui.components
|
||||
|
||||
import androidx.compose.foundation.shape.CornerBasedShape
|
||||
import androidx.compose.foundation.shape.CornerSize
|
||||
import androidx.compose.foundation.shape.ZeroCornerSize
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Outline
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
/**
|
||||
* A custom shape with curly edges, resembling a circle with sinusoidal indentations.
|
||||
*
|
||||
* This shape creates an outline that appears as a circle with "curly" or "wavy"
|
||||
* edges, achieved by applying a sine wave function to the circle's radius. The
|
||||
* amplitude and the number of curls (waves) can be adjusted.
|
||||
*
|
||||
* @property curlAmplitude The amplitude of the sine wave, controlling the depth of the curls.
|
||||
* A higher value results in deeper curls. Defaults to 16.0.
|
||||
* @property curlCount The number of curls (waves) around the circle's circumference.
|
||||
* A higher count results in more frequent curls. Defaults to 12.
|
||||
*/
|
||||
class CurlyCornerShape(
|
||||
private val curlAmplitude: Double = 16.0,
|
||||
private val curlCount: Int = 12,
|
||||
) : CornerBasedShape(
|
||||
topStart = ZeroCornerSize,
|
||||
topEnd = ZeroCornerSize,
|
||||
bottomEnd = ZeroCornerSize,
|
||||
bottomStart = ZeroCornerSize
|
||||
) {
|
||||
|
||||
/**
|
||||
* Calculates the x and y coordinates of a point on the curly circle at a given angle.
|
||||
*
|
||||
* @param centerX The x-coordinate of the center of the circle.
|
||||
* @param centerY The y-coordinate of the center of the circle.
|
||||
* @param baseRadius The base radius of the circle.
|
||||
* @param amplitude The amplitude of the sine wave.
|
||||
* @param angle The angle in radians.
|
||||
* @return A Pair containing the x and y coordinates of the point.
|
||||
*/
|
||||
private fun calculateCurlyCirclePoint(
|
||||
centerX: Double,
|
||||
centerY: Double,
|
||||
baseRadius: Double,
|
||||
amplitude: Double,
|
||||
angle: Double,
|
||||
): Pair<Double, Double> {
|
||||
// Calculate the radius with the sine wave applied.
|
||||
val radius = baseRadius + amplitude * sin(curlCount * angle)
|
||||
// Calculate x and y coordinates using polar coordinates.
|
||||
val x = centerX + radius * cos(angle)
|
||||
val y = centerY + radius * sin(angle)
|
||||
return Pair(x, y)
|
||||
}
|
||||
|
||||
override fun createOutline(
|
||||
size: Size,
|
||||
topStart: Float,
|
||||
topEnd: Float,
|
||||
bottomEnd: Float,
|
||||
bottomStart: Float,
|
||||
layoutDirection: LayoutDirection
|
||||
): Outline {
|
||||
val centerX = size.width / 2.0
|
||||
val centerY = size.height / 2.0
|
||||
val baseRadius = centerX - curlAmplitude
|
||||
val path = Path()
|
||||
|
||||
// Start at the rightmost point
|
||||
val startPoint = calculateCurlyCirclePoint(centerX, centerY, baseRadius, curlAmplitude, 0.0)
|
||||
path.moveTo(startPoint.first.toFloat(), startPoint.second.toFloat())
|
||||
|
||||
// Iterate through 360 degrees to draw the curly circle
|
||||
for (angleDegrees in 1..360) {
|
||||
// Convert the angle to radians.
|
||||
val angleRadians = Math.toRadians(angleDegrees.toDouble())
|
||||
|
||||
// calculate the current point
|
||||
val currentPoint =
|
||||
calculateCurlyCirclePoint(centerX, centerY, baseRadius, curlAmplitude, angleRadians)
|
||||
|
||||
path.lineTo(currentPoint.first.toFloat(), currentPoint.second.toFloat())
|
||||
}
|
||||
|
||||
path.close()
|
||||
return Outline.Generic(path)
|
||||
}
|
||||
|
||||
override fun copy(
|
||||
topStart: CornerSize,
|
||||
topEnd: CornerSize,
|
||||
bottomEnd: CornerSize,
|
||||
bottomStart: CornerSize,
|
||||
): CurlyCornerShape = CurlyCornerShape(
|
||||
curlAmplitude = this.curlAmplitude,
|
||||
curlCount = this.curlCount
|
||||
)
|
||||
}
|
||||
@@ -1,13 +1,23 @@
|
||||
package com.flux.ui.components
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
@@ -18,6 +28,7 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.selection.selectable
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
@@ -28,6 +39,7 @@ import androidx.compose.material.icons.automirrored.outlined.FormatListBulleted
|
||||
import androidx.compose.material.icons.automirrored.outlined.LabelImportant
|
||||
import androidx.compose.material.icons.filled.AccessTime
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.AudioFile
|
||||
import androidx.compose.material.icons.filled.Code
|
||||
import androidx.compose.material.icons.filled.DeleteOutline
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
@@ -35,10 +47,14 @@ import androidx.compose.material.icons.filled.EditCalendar
|
||||
import androidx.compose.material.icons.filled.FilePresent
|
||||
import androidx.compose.material.icons.filled.FontDownload
|
||||
import androidx.compose.material.icons.filled.Image
|
||||
import androidx.compose.material.icons.filled.Mic
|
||||
import androidx.compose.material.icons.filled.NewLabel
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material.icons.filled.Remove
|
||||
import androidx.compose.material.icons.filled.StopCircle
|
||||
import androidx.compose.material.icons.filled.SubdirectoryArrowRight
|
||||
import androidx.compose.material.icons.filled.TextFields
|
||||
import androidx.compose.material.icons.filled.Verified
|
||||
import androidx.compose.material.icons.outlined.FormatListNumbered
|
||||
import androidx.compose.material.icons.outlined.NotificationsActive
|
||||
import androidx.compose.material3.AlertDialog
|
||||
@@ -70,15 +86,22 @@ import androidx.compose.material3.rememberDatePickerState
|
||||
import androidx.compose.material3.rememberTimePickerState
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.toMutableStateList
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.nativeCanvas
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
@@ -86,13 +109,20 @@ import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.core.net.toUri
|
||||
import com.flux.R
|
||||
import com.flux.data.model.LabelModel
|
||||
import com.flux.other.AudioRecorder
|
||||
import com.flux.other.ExportType
|
||||
import com.flux.ui.theme.FONTS
|
||||
import com.flux.ui.theme.completed
|
||||
import com.flux.ui.theme.failed
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
@@ -1124,4 +1154,396 @@ fun TaskDialog(
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RecordAudioDialog(
|
||||
context: Context,
|
||||
recorder: AudioRecorder,
|
||||
onSave: (Uri?) -> Unit,
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
var isRecording by remember { mutableStateOf(false) }
|
||||
var recordedFile by remember { mutableStateOf<File?>(null) }
|
||||
val hasRecorded = recordedFile != null
|
||||
var recordedTime by remember { mutableLongStateOf(0L) }
|
||||
val permissionRequired = stringResource(R.string.permission_required)
|
||||
|
||||
// Permission launcher
|
||||
val permissionLauncher =
|
||||
rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
) { granted ->
|
||||
if (granted) {
|
||||
recorder.startRecording()
|
||||
isRecording = true
|
||||
} else {
|
||||
Toast.makeText(context, permissionRequired, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = {
|
||||
if (isRecording) { recorder.deleteRecording() }
|
||||
onDismissRequest()
|
||||
}
|
||||
) {
|
||||
Card(modifier = Modifier.size(320.dp)) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp).fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
if (!isRecording&& !hasRecorded) {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(stringResource(R.string.record_audio), style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.Bold))
|
||||
|
||||
Spacer(Modifier.height(32.dp))
|
||||
// RECORD/ STOP
|
||||
IconButton({
|
||||
if (recorder.hasMicPermission(context)) {
|
||||
recorder.startRecording()
|
||||
isRecording = true
|
||||
} else {
|
||||
permissionLauncher.launch(Manifest.permission.RECORD_AUDIO)
|
||||
}
|
||||
}, colors = IconButtonDefaults.iconButtonColors(
|
||||
containerColor = failed,
|
||||
contentColor = Color.White
|
||||
), modifier = Modifier.size(120.dp),
|
||||
shape = CurlyCornerShape(14.0, 6)
|
||||
) {
|
||||
Icon(Icons.Default.Mic, null, modifier = Modifier.size(75.dp))
|
||||
}
|
||||
Spacer(Modifier.height(32.dp))
|
||||
Text(stringResource(R.string.tap_to_start), style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Light))
|
||||
}
|
||||
|
||||
if(isRecording && !hasRecorded) {
|
||||
StudioRecorderUI(recorder) {
|
||||
recordedTime=it
|
||||
recordedFile = recorder.stopRecording()
|
||||
isRecording = false
|
||||
}
|
||||
}
|
||||
|
||||
if(hasRecorded){
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(formatDuration(recordedTime), style = MaterialTheme.typography.headlineMedium.copy(fontWeight = FontWeight.Bold))
|
||||
Spacer(Modifier.height(16.dp))
|
||||
|
||||
// RECORD/ STOP
|
||||
IconButton({}, colors = IconButtonDefaults.iconButtonColors(
|
||||
containerColor = failed,
|
||||
contentColor = Color.White
|
||||
), modifier = Modifier.size(120.dp),
|
||||
shape = CurlyCornerShape(curlCount = 8, curlAmplitude = 12.0)) {
|
||||
Icon(Icons.Default.AudioFile, null, modifier = Modifier.size(80.dp))
|
||||
}
|
||||
Spacer(Modifier.height(12.dp))
|
||||
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
// Retry
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = {
|
||||
recorder.deleteRecording()
|
||||
recordedFile=null
|
||||
recorder.startRecording()
|
||||
isRecording = true
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = failed,
|
||||
contentColor = Color.White
|
||||
)
|
||||
) {
|
||||
Icon(Icons.Default.Refresh, null)
|
||||
Text(stringResource(R.string.retry), modifier = Modifier.padding(start = 8.dp))
|
||||
}
|
||||
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = {
|
||||
onSave(recordedFile?.toUri())
|
||||
onDismissRequest()
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = completed,
|
||||
contentColor = Color.White
|
||||
)
|
||||
) {
|
||||
Icon(Icons.Default.Verified, null)
|
||||
Text(stringResource(R.string.save), modifier = Modifier.padding(start = 8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StudioRecorderUI(
|
||||
recorder: AudioRecorder,
|
||||
onStop: (Long) -> Unit
|
||||
) {
|
||||
val amplitudes = remember { MutableList(120) { 0.02f }.toMutableStateList() }
|
||||
var elapsedMs by remember { mutableLongStateOf(0L) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
val start = System.currentTimeMillis()
|
||||
var smoothedAmp = 0f
|
||||
|
||||
while (true) {
|
||||
|
||||
val raw = recorder.getAmplitude() / 32767f
|
||||
|
||||
// Low-pass smoothing filter
|
||||
smoothedAmp = (raw * 0.2f) + (smoothedAmp * 0.8f)
|
||||
|
||||
// Silence floor so waveform never vanishes
|
||||
val visualAmp = maxOf(smoothedAmp, 0.02f)
|
||||
|
||||
amplitudes.add(visualAmp)
|
||||
|
||||
if (amplitudes.size > 200) {
|
||||
amplitudes.removeAt(0)
|
||||
}
|
||||
|
||||
elapsedMs = System.currentTimeMillis() - start
|
||||
|
||||
delay(50)
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().fillMaxHeight().padding(8.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// TIMELINE + WAVE
|
||||
Box(modifier = Modifier.fillMaxWidth().height(160.dp)) {
|
||||
TimelineWaveform(amplitudes, elapsedMs)
|
||||
|
||||
// CENTER RED CURSOR
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(2.dp)
|
||||
.fillMaxHeight()
|
||||
.align(Alignment.Center)
|
||||
.background(Color.Red)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(6.dp))
|
||||
|
||||
// TIMER
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Box(Modifier.size(12.dp).background(Color.Red, CircleShape))
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(text = formatDuration(elapsedMs), fontSize = 22.sp)
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(8.dp))
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
IconButton(
|
||||
onClick = {onStop(elapsedMs)},
|
||||
modifier = Modifier.size(80.dp)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.StopCircle,
|
||||
contentDescription = null,
|
||||
tint = failed,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TimelineWaveform(
|
||||
amplitudes: List<Float>,
|
||||
elapsedMs: Long
|
||||
) {
|
||||
val onSurface = MaterialTheme.colorScheme.onSurface
|
||||
|
||||
Canvas(modifier = Modifier.fillMaxWidth().height(160.dp)) {
|
||||
val width = size.width
|
||||
val height = size.height
|
||||
val centerX = width / 2f
|
||||
val centerY = height / 2f
|
||||
|
||||
val pxPerSecond = 80f
|
||||
val samplesPerSecond = 20f
|
||||
val pxPerSample = pxPerSecond / samplesPerSecond
|
||||
|
||||
val secondsElapsed = elapsedMs / 1000f
|
||||
|
||||
// ==============================
|
||||
// WAVEFORM (PAST → LEFT)
|
||||
// ==============================
|
||||
|
||||
amplitudes.forEachIndexed { index, rawAmp ->
|
||||
|
||||
val x = centerX - (amplitudes.size - index) * (pxPerSample * 1.6f)
|
||||
|
||||
if (x >= 0f) {
|
||||
|
||||
// Boost amplitude visually
|
||||
val amp = rawAmp.coerceIn(0f, 1f)
|
||||
|
||||
val visualHeight =
|
||||
(amp * height * 1.2f).coerceAtMost(height * 0.95f)
|
||||
|
||||
val halfBar = visualHeight / 2f
|
||||
|
||||
// Dynamic alpha for clarity
|
||||
val alpha =
|
||||
0.3f + (amp * 0.7f)
|
||||
|
||||
drawLine(
|
||||
color = onSurface.copy(alpha = alpha),
|
||||
start = Offset(x, centerY - halfBar),
|
||||
end = Offset(x, centerY + halfBar),
|
||||
strokeWidth = 3f,
|
||||
cap = StrokeCap.Round
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ==============================
|
||||
// FUTURE PLACEHOLDER (RIGHT DOTS)
|
||||
// ==============================
|
||||
|
||||
var futureX = centerX + pxPerSample
|
||||
|
||||
while (futureX < width) {
|
||||
|
||||
drawLine(
|
||||
color = Color.Gray.copy(alpha = 0.4f),
|
||||
start = Offset(futureX, centerY - 4f),
|
||||
end = Offset(futureX, centerY + 4f),
|
||||
strokeWidth = 3f,
|
||||
cap = StrokeCap.Round
|
||||
)
|
||||
|
||||
futureX += pxPerSample * 2
|
||||
}
|
||||
|
||||
// ==============================
|
||||
// TIMELINE TICKS + LABELS
|
||||
// ==============================
|
||||
|
||||
val offsetPx = (secondsElapsed * pxPerSecond) % pxPerSecond
|
||||
|
||||
val paint = android.graphics.Paint().apply {
|
||||
color = android.graphics.Color.BLACK
|
||||
textSize = 28f
|
||||
textAlign = android.graphics.Paint.Align.CENTER
|
||||
}
|
||||
|
||||
// Prevent duplicate labels
|
||||
val drawnSeconds = mutableSetOf<Int>()
|
||||
|
||||
// -------- PAST (LEFT) --------
|
||||
|
||||
var pastTickIndex = 0
|
||||
var tickX = centerX - offsetPx
|
||||
|
||||
while (tickX > 0f) {
|
||||
val timeAtTick = secondsElapsed - pastTickIndex
|
||||
val secondInt = timeAtTick.toInt()
|
||||
|
||||
// Tick line
|
||||
drawLine(
|
||||
color = Color.Gray,
|
||||
start = Offset(tickX, 0f),
|
||||
end = Offset(tickX, 24f),
|
||||
strokeWidth = 2f
|
||||
)
|
||||
|
||||
// Draw label only if:
|
||||
// 1. Time exists (>= 0)
|
||||
// 2. Even second
|
||||
// 3. Not already drawn
|
||||
if (secondInt >= 0 &&
|
||||
secondInt % 2 == 0 &&
|
||||
drawnSeconds.add(secondInt)
|
||||
) {
|
||||
drawContext.canvas.nativeCanvas.drawText(
|
||||
formatTimelineLabel(secondInt),
|
||||
tickX,
|
||||
48f,
|
||||
paint
|
||||
)
|
||||
}
|
||||
|
||||
tickX -= pxPerSecond
|
||||
pastTickIndex++
|
||||
}
|
||||
|
||||
// -------- FUTURE (RIGHT) --------
|
||||
|
||||
var futureTickIndex = 1
|
||||
var futureTickX =
|
||||
centerX + (pxPerSecond - offsetPx)
|
||||
|
||||
while (futureTickX < width) {
|
||||
|
||||
val futureSecond =
|
||||
(secondsElapsed + futureTickIndex).toInt()
|
||||
|
||||
drawLine(
|
||||
color = onSurface,
|
||||
start = Offset(futureTickX, 0f),
|
||||
end = Offset(futureTickX, 24f),
|
||||
strokeWidth = 2f
|
||||
)
|
||||
|
||||
if (futureSecond % 2 == 0 &&
|
||||
drawnSeconds.add(futureSecond)
|
||||
) {
|
||||
drawContext.canvas.nativeCanvas.drawText(
|
||||
formatTimelineLabel(futureSecond),
|
||||
futureTickX,
|
||||
48f,
|
||||
paint
|
||||
)
|
||||
}
|
||||
|
||||
futureTickX += pxPerSecond
|
||||
futureTickIndex++
|
||||
}
|
||||
|
||||
// ==============================
|
||||
// CURSOR
|
||||
// ==============================
|
||||
|
||||
drawLine(
|
||||
color = failed,
|
||||
start = Offset(centerX, 0f),
|
||||
end = Offset(centerX, height),
|
||||
strokeWidth = 3f
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun formatTimelineLabel(seconds: Int): String {
|
||||
|
||||
val min = seconds / 60
|
||||
val sec = seconds % 60
|
||||
|
||||
return "%02d:%02d".format(min, sec)
|
||||
}
|
||||
|
||||
fun formatDuration(ms: Long): String {
|
||||
|
||||
val totalSec = ms / 1000
|
||||
val min = totalSec / 60
|
||||
val sec = totalSec % 60
|
||||
|
||||
return "%02d:%02d".format(min, sec)
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import androidx.compose.material.icons.automirrored.outlined.FormatIndentIncreas
|
||||
import androidx.compose.material.icons.automirrored.outlined.Label
|
||||
import androidx.compose.material.icons.automirrored.outlined.List
|
||||
import androidx.compose.material.icons.outlined.AddChart
|
||||
import androidx.compose.material.icons.outlined.AudioFile
|
||||
import androidx.compose.material.icons.outlined.CheckBox
|
||||
import androidx.compose.material.icons.outlined.Code
|
||||
import androidx.compose.material.icons.outlined.DataArray
|
||||
@@ -99,6 +100,7 @@ fun MarkdownEditorRow(
|
||||
onLinkButtonClick: () -> Unit,
|
||||
onImageButtonClick: () -> Unit,
|
||||
onAudioButtonClick: () -> Unit,
|
||||
onRecordAudioClick: () -> Unit,
|
||||
onVideoButtonClick: () -> Unit
|
||||
) {
|
||||
|
||||
@@ -366,6 +368,12 @@ fun MarkdownEditorRow(
|
||||
CustomIconButton(
|
||||
imageVector = Icons.Outlined.Mic,
|
||||
contentDescription = "Audio",
|
||||
onClick = onRecordAudioClick
|
||||
)
|
||||
|
||||
CustomIconButton(
|
||||
imageVector = Icons.Outlined.AudioFile,
|
||||
contentDescription = "Audio",
|
||||
onClick = onAudioButtonClick
|
||||
)
|
||||
|
||||
|
||||
@@ -3,5 +3,6 @@ package com.flux.ui.events
|
||||
import com.flux.data.model.SettingsModel
|
||||
|
||||
sealed class SettingEvents {
|
||||
data class UpdateSettings(val data: SettingsModel) : SettingEvents()
|
||||
data class UpdateSettings(val data: SettingsModel): SettingEvents()
|
||||
data object ResetDatabase: SettingEvents()
|
||||
}
|
||||
@@ -43,6 +43,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastJoinToString
|
||||
import androidx.navigation.NavController
|
||||
import com.flux.data.model.JournalModel
|
||||
import com.flux.other.AudioRecorder
|
||||
import com.flux.other.Constants
|
||||
import com.flux.other.HeaderNode
|
||||
import com.flux.other.ensureStorageRoot
|
||||
@@ -55,6 +56,7 @@ import com.flux.ui.components.ListDialog
|
||||
import com.flux.ui.components.MarkdownEditorRow
|
||||
import com.flux.ui.components.NotesInfoBottomSheet
|
||||
import com.flux.ui.components.OutlineBottomSheet
|
||||
import com.flux.ui.components.RecordAudioDialog
|
||||
import com.flux.ui.components.ShareDialog
|
||||
import com.flux.ui.components.TableDialog
|
||||
import com.flux.ui.components.TaskDialog
|
||||
@@ -116,7 +118,9 @@ fun EditJournal(
|
||||
var showTableDialog by rememberSaveable { mutableStateOf(false) }
|
||||
var showListDialog by rememberSaveable { mutableStateOf(false) }
|
||||
val isReadView by remember { derivedStateOf { pagerState.currentPage == 1 } }
|
||||
var showAudioRecorder by rememberSaveable { mutableStateOf(false) }
|
||||
var readWebView by remember { mutableStateOf<WebView?>(null) }
|
||||
val recorder = AudioRecorder(context)
|
||||
|
||||
val rootPicker =
|
||||
rememberLauncherForActivityResult(
|
||||
@@ -223,6 +227,15 @@ fun EditJournal(
|
||||
onListButtonClick = { showListDialog = true },
|
||||
onTaskButtonClick = { showTaskDialog = true },
|
||||
onLinkButtonClick = { showLinkDialog = true },
|
||||
onRecordAudioClick = {
|
||||
ensureStorageRoot(
|
||||
scope = scope,
|
||||
settingsViewModel = settingsViewModel,
|
||||
rootPicker = rootPicker
|
||||
) {
|
||||
showAudioRecorder=true
|
||||
}
|
||||
},
|
||||
onImageButtonClick = {
|
||||
ensureStorageRoot(
|
||||
scope = scope,
|
||||
@@ -368,6 +381,12 @@ fun EditJournal(
|
||||
})
|
||||
}
|
||||
|
||||
if(showAudioRecorder){
|
||||
RecordAudioDialog(context, recorder, {onJournalEvents(JournalEvents.ImportAudio(context, it!!, contentState))}) {
|
||||
showAudioRecorder=false
|
||||
}
|
||||
}
|
||||
|
||||
NotesInfoBottomSheet(
|
||||
isVisible = showAboutNotes,
|
||||
words = aboutJournal.wordCountWithPunctuation,
|
||||
|
||||
@@ -96,6 +96,8 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
import com.flux.other.AudioRecorder
|
||||
import com.flux.ui.components.RecordAudioDialog
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
@@ -121,6 +123,7 @@ fun NoteDetails(
|
||||
val hasContent = remember(note.notesId) {
|
||||
note.title.isNotBlank() || note.description.isNotBlank()
|
||||
}
|
||||
val recorder = AudioRecorder(context)
|
||||
|
||||
val pagerState = rememberPagerState(
|
||||
initialPage = if (startWithReadView && hasContent) 1 else 0,
|
||||
@@ -141,6 +144,7 @@ fun NoteDetails(
|
||||
var showTaskDialog by rememberSaveable { mutableStateOf(false) }
|
||||
var showTableDialog by rememberSaveable { mutableStateOf(false) }
|
||||
var showListDialog by rememberSaveable { mutableStateOf(false) }
|
||||
var showAudioRecorder by rememberSaveable { mutableStateOf(false) }
|
||||
var showSelectLabels by rememberSaveable { mutableStateOf(false) }
|
||||
var isPinned by rememberSaveable(note.notesId) { mutableStateOf(note.isPinned) }
|
||||
val noteLabelIds = rememberSaveable {
|
||||
@@ -275,6 +279,15 @@ fun NoteDetails(
|
||||
onListButtonClick = { showListDialog = true },
|
||||
onTaskButtonClick = { showTaskDialog = true },
|
||||
onLinkButtonClick = { showLinkDialog = true },
|
||||
onRecordAudioClick = {
|
||||
ensureStorageRoot(
|
||||
scope = scope,
|
||||
settingsViewModel = settingsViewModel,
|
||||
rootPicker = rootPicker
|
||||
) {
|
||||
showAudioRecorder=true
|
||||
}
|
||||
},
|
||||
onImageButtonClick = {
|
||||
ensureStorageRoot(
|
||||
scope = scope,
|
||||
@@ -484,6 +497,12 @@ fun NoteDetails(
|
||||
}
|
||||
}
|
||||
|
||||
if(showAudioRecorder){
|
||||
RecordAudioDialog(context, recorder, {onNotesEvents(NotesEvents.ImportAudio(context, it!!, contentState))}) {
|
||||
showAudioRecorder=false
|
||||
}
|
||||
}
|
||||
|
||||
NotesInfoBottomSheet(
|
||||
isVisible = showAboutNotes,
|
||||
words = aboutNotes.wordCountWithPunctuation,
|
||||
|
||||
@@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
|
||||
@@ -65,13 +66,12 @@ fun LazyListScope.notesHomeItems(
|
||||
item {
|
||||
val columns = if (isGridView) 2 else 1
|
||||
val rowCount = ceil(pinnedNotes.size / columns.toFloat()).toInt()
|
||||
val gridHeight =
|
||||
rowCount * 300.dp + if (pinnedNotes.isNotEmpty()) 200.dp else 0.dp
|
||||
val gridHeight = rowCount * 300.dp + if (pinnedNotes.isNotEmpty() && unPinnedNotes.isEmpty()) 200.dp else 25.dp
|
||||
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(gridHeight)
|
||||
.heightIn(max=gridHeight)
|
||||
) {
|
||||
LazyVerticalStaggeredGrid(
|
||||
columns = StaggeredGridCells.Fixed(columns),
|
||||
|
||||
@@ -8,9 +8,12 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.Article
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.outlined.Info
|
||||
import androidx.compose.material.icons.rounded.Code
|
||||
import androidx.compose.material.icons.rounded.DeveloperMode
|
||||
import androidx.compose.material.icons.rounded.Info
|
||||
import androidx.compose.material.icons.rounded.IosShare
|
||||
import androidx.compose.material.icons.rounded.PrivacyTip
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -110,6 +113,63 @@ fun About(navController: NavController, radius: Int) {
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(Modifier.height(24.dp))
|
||||
SettingOption(
|
||||
title = stringResource(R.string.user_guide_title),
|
||||
description = stringResource(R.string.user_guide_description),
|
||||
icon = Icons.Outlined.Info,
|
||||
radius = shapeManager(radius = radius, isFirst = true),
|
||||
actionType = ActionType.LINK,
|
||||
linkClicked = {
|
||||
val intent = Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
"https://github.com/chindaronit/Flux/blob/master/Guide.md".toUri()
|
||||
)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SettingOption(
|
||||
title = stringResource(R.string.privacy_policy_title),
|
||||
description = stringResource(R.string.privacy_policy_description),
|
||||
icon = Icons.Rounded.PrivacyTip,
|
||||
radius = shapeManager(radius = radius, isLast = true),
|
||||
actionType = ActionType.LINK,
|
||||
linkClicked = {
|
||||
val intent = Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
"https://github.com/chindaronit/Flux/blob/master/PRIVACY_POLICY.md".toUri()
|
||||
)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(Modifier.height(24.dp))
|
||||
SettingOption(
|
||||
title = stringResource(R.string.share),
|
||||
description = stringResource(R.string.share_description),
|
||||
icon = Icons.Rounded.IosShare,
|
||||
radius = shapeManager(radius = radius, isBoth = true),
|
||||
actionType = ActionType.LINK,
|
||||
linkClicked = {
|
||||
val sendIntent = Intent(Intent.ACTION_SEND)
|
||||
sendIntent.setType("text/plain")
|
||||
sendIntent.putExtra(
|
||||
Intent.EXTRA_TITLE, context.getString(R.string.app_name)
|
||||
)
|
||||
sendIntent.putExtra(
|
||||
Intent.EXTRA_TEXT, context.getString(R.string.shareContent)
|
||||
)
|
||||
context.startActivity(Intent.createChooser(sendIntent, null))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
package com.flux.ui.screens.settings
|
||||
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Backup
|
||||
import androidx.compose.material.icons.rounded.Restore
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import com.flux.R
|
||||
import com.flux.other.canScheduleReminder
|
||||
import com.flux.other.isNotificationPermissionGranted
|
||||
import com.flux.other.openAppNotificationSettings
|
||||
import com.flux.other.requestExactAlarmPermission
|
||||
import com.flux.ui.components.ActionType
|
||||
import com.flux.ui.components.BasicScaffold
|
||||
import com.flux.ui.components.SettingOption
|
||||
import com.flux.ui.components.shapeManager
|
||||
import com.flux.ui.viewModel.BackupViewModel
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun Backup(
|
||||
navController: NavController,
|
||||
radius: Int,
|
||||
backupViewModel: BackupViewModel
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
// EXPORT launcher
|
||||
val exportLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.CreateDocument("application/json")
|
||||
) { uri ->
|
||||
uri?.let { backupViewModel.exportBackup(context, it) }
|
||||
}
|
||||
|
||||
// IMPORT launcher
|
||||
val importLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.OpenDocument()
|
||||
) { uri ->
|
||||
uri?.let { backupViewModel.importBackup(context, it) }
|
||||
}
|
||||
|
||||
// Observe result - Collect SharedFlow properly
|
||||
LaunchedEffect(Unit) {
|
||||
backupViewModel.backupResult.collect { result ->
|
||||
if (result.isSuccess) {
|
||||
Toast.makeText(context, "Operation successful!", Toast.LENGTH_LONG).show()
|
||||
} else {
|
||||
Toast.makeText(context, "Operation failed.", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ... rest of your UI
|
||||
BasicScaffold(
|
||||
title = stringResource(R.string.Backup),
|
||||
onBackClicked = {
|
||||
navController.navigateUp()
|
||||
}
|
||||
) { innerPadding ->
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
) {
|
||||
item {
|
||||
SettingOption(
|
||||
title = stringResource(R.string.Backup),
|
||||
description = stringResource(R.string.Backup_Description),
|
||||
icon = Icons.Rounded.Backup,
|
||||
radius = shapeManager(radius = radius, isFirst = true),
|
||||
actionType = ActionType.CUSTOM,
|
||||
onCustomClick = { exportLauncher.launch("flux-backup.json") }
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SettingOption(
|
||||
title = stringResource(R.string.Restore),
|
||||
description = stringResource(R.string.Restore_Description),
|
||||
icon = Icons.Rounded.Restore,
|
||||
radius = shapeManager(radius = radius, isLast = true),
|
||||
actionType = ActionType.CUSTOM,
|
||||
onCustomClick = {
|
||||
if (!canScheduleReminder(context)) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getText(R.string.Reminder_Permission),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
requestExactAlarmPermission(context)
|
||||
}
|
||||
if (!isNotificationPermissionGranted(context)) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getText(R.string.Notification_Permission),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
openAppNotificationSettings(context)
|
||||
}
|
||||
if (canScheduleReminder(context) && isNotificationPermissionGranted(context)) {
|
||||
importLauncher.launch(arrayOf("application/json"))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,8 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AccessTime
|
||||
import androidx.compose.material.icons.filled.Colorize
|
||||
import androidx.compose.material.icons.filled.DarkMode
|
||||
import androidx.compose.material.icons.filled.FormatListNumbered
|
||||
import androidx.compose.material.icons.filled.LightMode
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.filled.Spellcheck
|
||||
import androidx.compose.material.icons.rounded.FontDownload
|
||||
import androidx.compose.material.icons.rounded.RoundedCorner
|
||||
import androidx.compose.material.icons.rounded.ViewCompact
|
||||
@@ -316,46 +314,6 @@ fun Customize(
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SettingOption(
|
||||
title = stringResource(R.string.show_line_numbers),
|
||||
description = stringResource(R.string.show_line_numbers_desc),
|
||||
icon = Icons.Filled.FormatListNumbered,
|
||||
radius = shapeManager(radius = settings.data.cornerRadius),
|
||||
actionType = ActionType.SWITCH,
|
||||
variable = settings.data.isLineNumbersVisible,
|
||||
switchEnabled = {
|
||||
onSettingsEvents(
|
||||
SettingEvents.UpdateSettings(
|
||||
settings.data.copy(
|
||||
isLineNumbersVisible = it
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SettingOption(
|
||||
title = stringResource(R.string.markdown_lint),
|
||||
description = stringResource(R.string.markdown_lint_desc),
|
||||
icon = Icons.Filled.Spellcheck,
|
||||
radius = shapeManager(radius = settings.data.cornerRadius),
|
||||
actionType = ActionType.SWITCH,
|
||||
variable = settings.data.isLintValid,
|
||||
switchEnabled = {
|
||||
onSettingsEvents(
|
||||
SettingEvents.UpdateSettings(
|
||||
settings.data.copy(
|
||||
isLintValid = it
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SettingOption(
|
||||
title = stringResource(R.string.Hour_Format_24),
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
package com.flux.ui.screens.settings
|
||||
|
||||
import android.os.Build
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.outlined.CleaningServices
|
||||
import androidx.compose.material.icons.outlined.EditCalendar
|
||||
import androidx.compose.material.icons.rounded.Backup
|
||||
import androidx.compose.material.icons.rounded.Restore
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.ListItemDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import androidx.work.WorkManager
|
||||
import com.flux.R
|
||||
import com.flux.other.BackupFrequency
|
||||
import com.flux.other.BackupManager
|
||||
import com.flux.other.canScheduleReminder
|
||||
import com.flux.other.isNotificationPermissionGranted
|
||||
import com.flux.other.openAppNotificationSettings
|
||||
import com.flux.other.requestExactAlarmPermission
|
||||
import com.flux.ui.components.ActionType
|
||||
import com.flux.ui.components.DeleteAlert
|
||||
import com.flux.ui.components.SettingOption
|
||||
import com.flux.ui.components.shapeManager
|
||||
import com.flux.ui.events.SettingEvents
|
||||
import com.flux.ui.state.Settings
|
||||
import com.flux.ui.viewModel.BackupViewModel
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun Data(
|
||||
navController: NavController,
|
||||
radius: Int,
|
||||
settings: Settings,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
backupViewModel: BackupViewModel,
|
||||
onSettingsEvents: (SettingEvents) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
var showWarningDialog by remember { mutableStateOf(false) }
|
||||
|
||||
// EXPORT launcher
|
||||
val exportLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.CreateDocument("application/json")
|
||||
) { uri ->
|
||||
uri?.let { backupViewModel.exportBackup(context, it) }
|
||||
}
|
||||
|
||||
// IMPORT launcher
|
||||
val importLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.OpenDocument()
|
||||
) { uri ->
|
||||
uri?.let { backupViewModel.importBackup(context, it) }
|
||||
}
|
||||
|
||||
// Observe result - Collect SharedFlow properly
|
||||
LaunchedEffect(Unit) {
|
||||
backupViewModel.backupResult.collect { result ->
|
||||
if (result.isSuccess) {
|
||||
Toast.makeText(context, "Operation successful!", Toast.LENGTH_LONG).show()
|
||||
} else {
|
||||
Toast.makeText(context, "Operation failed.", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ... rest of your UI
|
||||
Scaffold(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainerLow,
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
colors = TopAppBarDefaults.topAppBarColors(containerColor = MaterialTheme.colorScheme.surfaceContainerLow),
|
||||
title = { Text(stringResource(R.string.data_title)) },
|
||||
navigationIcon = {
|
||||
IconButton({navController.navigateUp()}) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = "Back"
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) }
|
||||
){ innerPadding ->
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
) {
|
||||
item {
|
||||
SettingOption(
|
||||
title = stringResource(R.string.Backup),
|
||||
description = stringResource(R.string.Backup_Description),
|
||||
icon = Icons.Rounded.Backup,
|
||||
radius = shapeManager(radius = radius, isFirst = true),
|
||||
actionType = ActionType.CUSTOM,
|
||||
onCustomClick = { exportLauncher.launch("flux-backup.json") }
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SettingOption(
|
||||
title = stringResource(R.string.Restore),
|
||||
description = stringResource(R.string.Restore_Description),
|
||||
icon = Icons.Rounded.Restore,
|
||||
radius = shapeManager(radius = radius, isLast = true),
|
||||
actionType = ActionType.CUSTOM,
|
||||
onCustomClick = {
|
||||
if (!canScheduleReminder(context)) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getText(R.string.Reminder_Permission),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
requestExactAlarmPermission(context)
|
||||
}
|
||||
if (!isNotificationPermissionGranted(context)) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getText(R.string.Notification_Permission),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
openAppNotificationSettings(context)
|
||||
}
|
||||
if (canScheduleReminder(context) && isNotificationPermissionGranted(context)) {
|
||||
importLauncher.launch(arrayOf("application/json"))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
val workManager = remember { WorkManager.getInstance(context.applicationContext) }
|
||||
val backupManager = remember(workManager) { BackupManager(workManager) }
|
||||
|
||||
fun mapDaysToSliderPosition(days: Int): Float = when (days) {
|
||||
0 -> 0f
|
||||
1 -> 1f
|
||||
7 -> 2f
|
||||
30 -> 3f
|
||||
else -> 0f
|
||||
}
|
||||
|
||||
fun mapSliderPositionToFrequency(position: Float): BackupFrequency = when (position) {
|
||||
0f -> BackupFrequency.NEVER
|
||||
1f -> BackupFrequency.DAILY
|
||||
2f -> BackupFrequency.WEEKLY
|
||||
3f -> BackupFrequency.MONTHLY
|
||||
else -> BackupFrequency.NEVER
|
||||
}
|
||||
|
||||
val sliderPosition = remember(settings.data.backupFrequency) {
|
||||
mapDaysToSliderPosition(settings.data.backupFrequency)
|
||||
}
|
||||
|
||||
val selectedFrequency = remember(sliderPosition) {
|
||||
mapSliderPositionToFrequency(sliderPosition)
|
||||
}
|
||||
|
||||
ListItem(
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
leadingContent = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.EditCalendar,
|
||||
contentDescription = "Auto backup"
|
||||
)
|
||||
},
|
||||
colors = ListItemDefaults.colors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainerLow
|
||||
),
|
||||
headlineContent = { Text(stringResource(R.string.backup_frequency)) },
|
||||
supportingContent = { Text(text = stringResource(selectedFrequency.textRes)) }
|
||||
)
|
||||
|
||||
Slider(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
value = sliderPosition,
|
||||
onValueChange = { newPosition ->
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.SegmentFrequentTick)
|
||||
val newFrequency = mapSliderPositionToFrequency(newPosition)
|
||||
onSettingsEvents(SettingEvents.UpdateSettings(settings.data.copy(backupFrequency = newFrequency.days)))
|
||||
},
|
||||
onValueChangeFinished = { backupManager.scheduleBackup(selectedFrequency) },
|
||||
valueRange = 0f..3f,
|
||||
steps = 2
|
||||
)
|
||||
}
|
||||
item {
|
||||
ListItem(
|
||||
colors = ListItemDefaults.colors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainerLow
|
||||
),
|
||||
leadingContent = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.CleaningServices,
|
||||
contentDescription = "Reset"
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(stringResource(R.string.reset_database)) },
|
||||
trailingContent = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.ContextClick)
|
||||
showWarningDialog = true
|
||||
},
|
||||
colors = ButtonDefaults.textButtonColors().copy(
|
||||
containerColor = MaterialTheme.colorScheme.errorContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onErrorContainer
|
||||
)
|
||||
) {
|
||||
Text(stringResource(R.string.reset))
|
||||
}
|
||||
},
|
||||
supportingContent = {
|
||||
Text(stringResource(R.string.clear_app_data))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
if(showWarningDialog){
|
||||
DeleteAlert(
|
||||
onConfirmation = {
|
||||
showWarningDialog=false
|
||||
onSettingsEvents(SettingEvents.ResetDatabase) },
|
||||
onDismissRequest = { showWarningDialog = false },
|
||||
dialogTitle = stringResource(R.string.deleteDialogTitle),
|
||||
dialogText = stringResource(R.string.deleteDialogText),
|
||||
icon = Icons.Default.Delete
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,8 +119,8 @@ fun Settings(
|
||||
|
||||
item {
|
||||
SettingCategory(
|
||||
title = stringResource(R.string.Backup),
|
||||
subTitle = stringResource(R.string.Backup_Description),
|
||||
title = stringResource(R.string.data_title),
|
||||
subTitle = stringResource(R.string.data_subtitle),
|
||||
icon = Icons.Rounded.Backup,
|
||||
shape = shapeManager(radius = settings.data.cornerRadius),
|
||||
action = {
|
||||
|
||||
@@ -5,8 +5,8 @@ import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val completed = Color(0xFF009B44)
|
||||
val pending = Color(0xFFBE0000)
|
||||
val failed = Color(0xFFFFD600)
|
||||
val failed = Color(0xFFBE0000)
|
||||
val pending = Color(0xFFFFD600)
|
||||
|
||||
private val primaryLight = Color(0xFF006A65)
|
||||
private val onPrimaryLight = Color(0xFFFFFFFF)
|
||||
|
||||
@@ -3,28 +3,39 @@ package com.flux.ui.viewModel
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.flux.data.database.FluxDatabase
|
||||
import com.flux.data.model.SettingsModel
|
||||
import com.flux.data.repository.SettingsRepository
|
||||
import com.flux.ui.effects.ScreenEffect
|
||||
import com.flux.ui.events.SettingEvents
|
||||
import com.flux.ui.state.Settings
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class SettingsViewModel @Inject constructor(
|
||||
val repository: SettingsRepository
|
||||
val repository: SettingsRepository,
|
||||
val database: FluxDatabase
|
||||
) : ViewModel() {
|
||||
|
||||
private val _state: MutableStateFlow<Settings> = MutableStateFlow(Settings())
|
||||
val state: StateFlow<Settings> = _state.asStateFlow()
|
||||
private val _effect: Channel<ScreenEffect> = Channel()
|
||||
val effect = _effect.receiveAsFlow()
|
||||
|
||||
init {
|
||||
loadSettings()
|
||||
init { loadSettings() }
|
||||
|
||||
private fun setEffect(builder: () -> ScreenEffect) {
|
||||
val effectValue = builder()
|
||||
viewModelScope.launch { _effect.send(effectValue) }
|
||||
}
|
||||
|
||||
fun onEvent(event: SettingEvents) {
|
||||
@@ -37,9 +48,8 @@ class SettingsViewModel @Inject constructor(
|
||||
|
||||
private fun reduce(event: SettingEvents) {
|
||||
when (event) {
|
||||
is SettingEvents.UpdateSettings -> {
|
||||
updateSettings(event.data)
|
||||
}
|
||||
is SettingEvents.UpdateSettings -> { updateSettings(event.data) }
|
||||
is SettingEvents.ResetDatabase -> { resetDatabase() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,10 +57,7 @@ class SettingsViewModel @Inject constructor(
|
||||
updateState { it.copy(isLoading = true) }
|
||||
viewModelScope.launch {
|
||||
repository.loadSettings().collect { data ->
|
||||
if (data != null){
|
||||
println(data)
|
||||
updateState { it.copy(isLoading = false, data = data) }
|
||||
}
|
||||
if (data != null){ updateState { it.copy(isLoading = false, data = data) } }
|
||||
else updateState { it.copy(isLoading = false) }
|
||||
}
|
||||
}
|
||||
@@ -69,4 +76,24 @@ class SettingsViewModel @Inject constructor(
|
||||
repository.saveStorageRoot(uri)
|
||||
}
|
||||
}
|
||||
|
||||
fun resetDatabase() {
|
||||
viewModelScope.launch {
|
||||
runCatching {
|
||||
withContext(Dispatchers.IO) {
|
||||
database.clearAllTables()
|
||||
}
|
||||
}.onSuccess {
|
||||
setEffect {
|
||||
ScreenEffect.ShowSnackBarMessage("Reset completed")
|
||||
}
|
||||
}.onFailure { throwable ->
|
||||
setEffect {
|
||||
ScreenEffect.ShowSnackBarMessage(
|
||||
"Failed to reset database: ${throwable.localizedMessage ?: "error"}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,14 @@
|
||||
<string name="Backup_Description">Daten in einer Datei sichern</string>
|
||||
<string name="Restore">Wiederherstellen</string>
|
||||
<string name="Restore_Description">Daten aus einer Backup-Datei wiederherstellen</string>
|
||||
<string name="reset">Zurücksetzen</string>
|
||||
<string name="backup_frequency">Häufigkeit der automatischen Sicherung</string>
|
||||
<string name="reset_database">Datenbank zurücksetzen</string>
|
||||
<string name="clear_app_data">Alle App-Daten löschen</string>
|
||||
<string name="never">Nie</string>
|
||||
<string name="daily">Täglich</string>
|
||||
<string name="weekly">Wöchentlich</string>
|
||||
<string name="monthly">Monatlich</string>
|
||||
|
||||
<!-- Settings - About -->
|
||||
<string name="About">Über</string>
|
||||
@@ -132,6 +140,14 @@
|
||||
<string name="Github_Repository">GitHub-Repository</string>
|
||||
<string name="license">Lizenz</string>
|
||||
<string name="license_gpl3_only">GPL-3.0 Only</string>
|
||||
<string name="user_guide_title">Benutzerhandbuch</string>
|
||||
<string name="user_guide_description">Lesen Sie das Handbuch, um die Anwendung vollständig zu nutzen.</string>
|
||||
<string name="privacy_policy_title">Datenschutzrichtlinie</string>
|
||||
<string name="privacy_policy_description">Datenschutzrichtlinie der Anwendung.</string>
|
||||
<string name="share_title">Teilen</string>
|
||||
<string name="share_description">Teilen Sie die Anwendung mit Freunden und Familie.</string>
|
||||
<string name="data_title">Daten</string>
|
||||
<string name="data_subtitle">Sicherung • Wiederherstellung</string>
|
||||
|
||||
<!-- Settings - Contact & Support -->
|
||||
<string name="Contact">Kontakt</string>
|
||||
@@ -190,6 +206,10 @@
|
||||
<string name="import_failed">Fehler beim Import der Notiz</string>
|
||||
<string name="find">Suchen</string>
|
||||
<string name="replace">Ersetzen</string>
|
||||
<string name="record_audio">Audio aufnehmen</string>
|
||||
<string name="tap_to_start">Tippen zum Starten</string>
|
||||
<string name="retry">Erneut versuchen</string>
|
||||
<string name="permission_required">Bitte erteilen Sie die Aufnahmeberechtigung.</string>
|
||||
|
||||
<!-- To-Do -->
|
||||
<string name="To_Do">Aufgaben</string>
|
||||
@@ -309,20 +329,20 @@
|
||||
|
||||
<string name="editor_title">Editor</string>
|
||||
<string name="editor_subtitle">Standardansicht • Standardbearbeitungsmodus</string>
|
||||
|
||||
<string name="show_line_numbers_title">Zeilennummern anzeigen</string>
|
||||
<string name="show_line_numbers_description">
|
||||
Zeilennummern im Editor anzeigen
|
||||
</string>
|
||||
|
||||
<string name="show_line_numbers_description">Zeilennummern im Editor anzeigen</string>
|
||||
<string name="markdown_lint_title">Markdown-Lint</string>
|
||||
<string name="markdown_lint_description">
|
||||
Markdown-Format und Syntax werden überprüft
|
||||
</string>
|
||||
|
||||
<string name="markdown_lint_description">Markdown-Format und Syntax werden überprüft</string>
|
||||
<string name="default_editor_view">Standard-Editoransicht</string>
|
||||
|
||||
<string name="reading_view">Leseansicht</string>
|
||||
<string name="editing_view">Bearbeitungsansicht</string>
|
||||
|
||||
<string name="shareContent">
|
||||
Flux ist eine moderne Android-Produktivitäts-App, die vollständig mit Compose entwickelt wurde.\n
|
||||
Sie ermöglicht das Erstellen, Bearbeiten und Löschen von Workspaces mit Vorlagen wie Notizen, Gewohnheiten, Journal, To-Do, Ereignissen usw. sowie erweiterten Funktionen wie Suchen und Ersetzen, Notizvorlagen, lokalem Import von Bildern/Videos/Audio, Markdown, LaTeX-Formeln und Mermaid-Diagrammen.\n
|
||||
Jetzt herunterladen und ausprobieren:\n
|
||||
- GitHub: https://github.com/chindaronit/Flux/releases\n
|
||||
- F-Droid: https://f-droid.org/packages/com.flux/
|
||||
</string>
|
||||
|
||||
</resources>
|
||||
@@ -120,6 +120,14 @@
|
||||
<string name="Backup_Description">Respaldar tus datos a un archivo</string>
|
||||
<string name="Restore">Restaurar</string>
|
||||
<string name="Restore_Description">Restaurar tus datos desde un archivo de respaldo</string>
|
||||
<string name="reset">Restablecer</string>
|
||||
<string name="backup_frequency">Frecuencia de copia de seguridad automática</string>
|
||||
<string name="reset_database">Restablecer base de datos</string>
|
||||
<string name="clear_app_data">Borrar todos los datos de la app</string>
|
||||
<string name="never">Nunca</string>
|
||||
<string name="daily">Diario</string>
|
||||
<string name="weekly">Semanal</string>
|
||||
<string name="monthly">Mensual</string>
|
||||
|
||||
<!-- Settings - About -->
|
||||
<string name="About">Acerca de</string>
|
||||
@@ -132,6 +140,14 @@
|
||||
<string name="Github_Repository">Repositorio de Github</string>
|
||||
<string name="license">Licencia</string>
|
||||
<string name="license_gpl3_only">GPL-3.0 Only</string>
|
||||
<string name="user_guide_title">Guía de usuario</string>
|
||||
<string name="user_guide_description">Lee la guía para aprovechar completamente la aplicación.</string>
|
||||
<string name="privacy_policy_title">Política de privacidad</string>
|
||||
<string name="privacy_policy_description">Política de privacidad de la aplicación.</string>
|
||||
<string name="share_title">Compartir</string>
|
||||
<string name="share_description">Comparte la aplicación con amigos y familiares.</string>
|
||||
<string name="data_title">Datos</string>
|
||||
<string name="data_subtitle">Copia de seguridad • Recuperación</string>
|
||||
|
||||
<!-- Settings - Contact & Support -->
|
||||
<string name="Contact">Contacto</string>
|
||||
@@ -190,6 +206,10 @@
|
||||
<string name="import_failed">Error al importar la nota</string>
|
||||
<string name="find">Buscar</string>
|
||||
<string name="replace">Reemplazar</string>
|
||||
<string name="record_audio">Grabar audio</string>
|
||||
<string name="tap_to_start">Toca para comenzar</string>
|
||||
<string name="retry">Reintentar</string>
|
||||
<string name="permission_required">Por favor concede permiso de grabación.</string>
|
||||
|
||||
<!-- To-Do -->
|
||||
<string name="To_Do">Tareas</string>
|
||||
@@ -321,4 +341,12 @@
|
||||
<string name="reading_view">Vista de lectura</string>
|
||||
<string name="editing_view">Vista de edición</string>
|
||||
|
||||
<string name="shareContent">
|
||||
Flux es una aplicación moderna de productividad para Android creada completamente con Compose.\n
|
||||
Permite crear, editar y eliminar espacios de trabajo con plantillas como Notas, Hábitos, Diario, Tareas, Eventos, etc., junto con funciones avanzadas como buscar y reemplazar, plantillas de notas, importación local de imagen/video/audio, Markdown, fórmulas matemáticas LaTeX y diagramas Mermaid.\n
|
||||
Descárgala y pruébala ahora:\n
|
||||
- GitHub: https://github.com/chindaronit/Flux/releases\n
|
||||
- F-Droid: https://f-droid.org/packages/com.flux/
|
||||
</string>
|
||||
|
||||
</resources>
|
||||
@@ -120,6 +120,14 @@
|
||||
<string name="Backup_Description">Sauvegarder vos données dans un fichier</string>
|
||||
<string name="Restore">Restaurer</string>
|
||||
<string name="Restore_Description">Restaurer vos données depuis un fichier de sauvegarde</string>
|
||||
<string name="reset">Réinitialiser</string>
|
||||
<string name="backup_frequency">Fréquence de sauvegarde automatique</string>
|
||||
<string name="reset_database">Réinitialiser la base de données</string>
|
||||
<string name="clear_app_data">Effacer toutes les données de l’application</string>
|
||||
<string name="never">Jamais</string>
|
||||
<string name="daily">Quotidien</string>
|
||||
<string name="weekly">Hebdomadaire</string>
|
||||
<string name="monthly">Mensuel</string>
|
||||
|
||||
<!-- Settings - About -->
|
||||
<string name="About">À propos</string>
|
||||
@@ -132,6 +140,14 @@
|
||||
<string name="Github_Repository">Dépôt Github</string>
|
||||
<string name="license">Licence</string>
|
||||
<string name="license_gpl3_only">GPL-3.0 Only</string>
|
||||
<string name="user_guide_title">Guide d’utilisation</string>
|
||||
<string name="user_guide_description">Lisez le guide pour utiliser pleinement l’application.</string>
|
||||
<string name="privacy_policy_title">Politique de confidentialité</string>
|
||||
<string name="privacy_policy_description">Politique de confidentialité de l’application.</string>
|
||||
<string name="share_title">Partager</string>
|
||||
<string name="share_description">Partagez l’application avec vos amis et votre famille.</string>
|
||||
<string name="data_title">Données</string>
|
||||
<string name="data_subtitle">Sauvegarde • Récupération</string>
|
||||
|
||||
<!-- Settings - Contact & Support -->
|
||||
<string name="Contact">Contact</string>
|
||||
@@ -190,6 +206,10 @@
|
||||
<string name="import_failed">Échec de l\'importation de la note</string>
|
||||
<string name="find">Rechercher</string>
|
||||
<string name="replace">Remplacer</string>
|
||||
<string name="record_audio">Enregistrer l’audio</string>
|
||||
<string name="tap_to_start">Appuyez pour démarrer</string>
|
||||
<string name="retry">Réessayer</string>
|
||||
<string name="permission_required">Veuillez accorder l’autorisation d’enregistrement.</string>
|
||||
|
||||
<!-- To-Do -->
|
||||
<string name="To_Do">À faire</string>
|
||||
@@ -324,4 +344,12 @@
|
||||
<string name="reading_view">Vue de lecture</string>
|
||||
<string name="editing_view">Vue d’édition</string>
|
||||
|
||||
<string name="shareContent">
|
||||
Flux est une application moderne de productivité Android entièrement conçue avec Compose.\n
|
||||
Elle permet de créer, modifier et supprimer des espaces de travail avec des modèles comme Notes, Habitudes, Journal, Tâches, Événements, etc., ainsi que des fonctionnalités avancées telles que rechercher/remplacer, modèles de notes, import local d’images/vidéos/audio, Markdown, formules LaTeX et diagrammes Mermaid.\n
|
||||
Téléchargez-la dès maintenant :\n
|
||||
- GitHub : https://github.com/chindaronit/Flux/releases\n
|
||||
- F-Droid : https://f-droid.org/packages/com.flux/
|
||||
</string>
|
||||
|
||||
</resources>
|
||||
@@ -120,6 +120,14 @@
|
||||
<string name="Backup_Description">अपने डेटा का बैकअप फ़ाइल में लें</string>
|
||||
<string name="Restore">पुनर्स्थापित करें</string>
|
||||
<string name="Restore_Description">बैकअप फ़ाइल से अपना डेटा पुनर्स्थापित करें</string>
|
||||
<string name="reset">रीसेट करें</string>
|
||||
<string name="backup_frequency">स्वचालित बैकअप की आवृत्ति</string>
|
||||
<string name="reset_database">डेटाबेस रीसेट करें</string>
|
||||
<string name="clear_app_data">सभी ऐप डेटा साफ़ करें</string>
|
||||
<string name="never">कभी नहीं</string>
|
||||
<string name="daily">दैनिक</string>
|
||||
<string name="weekly">साप्ताहिक</string>
|
||||
<string name="monthly">मासिक</string>
|
||||
|
||||
<!-- Settings - About -->
|
||||
<string name="About">अबाउट</string>
|
||||
@@ -132,6 +140,14 @@
|
||||
<string name="Github_Repository">गिटहब रिपोजिटरी</string>
|
||||
<string name="license">लाइसेंस</string>
|
||||
<string name="license_gpl3_only">GPL-3.0 Only</string>
|
||||
<string name="user_guide_title">उपयोगकर्ता मार्गदर्शिका</string>
|
||||
<string name="user_guide_description">एप्लिकेशन का पूर्ण उपयोग करने के लिए मार्गदर्शिका पढ़ें।</string>
|
||||
<string name="privacy_policy_title">गोपनीयता नीति</string>
|
||||
<string name="privacy_policy_description">एप्लिकेशन की गोपनीयता नीति।</string>
|
||||
<string name="share_title">साझा करें</string>
|
||||
<string name="share_description">एप्लिकेशन को मित्रों और परिवार के साथ साझा करें।</string>
|
||||
<string name="data_title">डेटा</string>
|
||||
<string name="data_subtitle">बैकअप • रिकवरी</string>
|
||||
|
||||
<!-- Settings - Contact & Support -->
|
||||
<string name="Contact">संपर्क करें</string>
|
||||
@@ -190,6 +206,10 @@
|
||||
<string name="import_failed">नोट आयात करने में विफल</string>
|
||||
<string name="find">खोजें</string>
|
||||
<string name="replace">बदलें</string>
|
||||
<string name="record_audio">ऑडियो रिकॉर्ड करें</string>
|
||||
<string name="tap_to_start">शुरू करने के लिए टैप करें</string>
|
||||
<string name="retry">पुनः प्रयास करें</string>
|
||||
<string name="permission_required">कृपया रिकॉर्डिंग की अनुमति दें।</string>
|
||||
|
||||
<!-- To-Do -->
|
||||
<string name="To_Do">करने के कार्य</string>
|
||||
@@ -324,4 +344,12 @@
|
||||
<string name="reading_view">पठन दृश्य</string>
|
||||
<string name="editing_view">संपादन दृश्य</string>
|
||||
|
||||
<string name="shareContent">
|
||||
Flux एक आधुनिक Android प्रोडक्टिविटी ऐप है जिसे पूरी तरह Compose से बनाया गया है।\n
|
||||
यह आपको Notes, Habits, Journal, To-Do, Events आदि जैसे टेम्पलेट्स के साथ वर्कस्पेस बनाने, संपादित करने और हटाने की सुविधा देता है, साथ ही find & replace, note templates, लोकल इमेज/वीडियो/ऑडियो इम्पोर्ट, Markdown, LaTeX गणित सूत्र और Mermaid डायग्राम सपोर्ट जैसी उन्नत सुविधाएँ प्रदान करता है।\n
|
||||
अभी डाउनलोड करें:\n
|
||||
- GitHub: https://github.com/chindaronit/Flux/releases\n
|
||||
- F-Droid: https://f-droid.org/packages/com.flux/
|
||||
</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -120,6 +120,14 @@
|
||||
<string name="Backup_Description">Maak een back-up van je gegevens naar een bestand</string>
|
||||
<string name="Restore">Herstellen</string>
|
||||
<string name="Restore_Description">Herstel je gegevens van een back-upbestand</string>
|
||||
<string name="reset">Resetten</string>
|
||||
<string name="backup_frequency">Frequentie van automatische back-up</string>
|
||||
<string name="reset_database">Database resetten</string>
|
||||
<string name="clear_app_data">Alle appgegevens wissen</string>
|
||||
<string name="never">Nooit</string>
|
||||
<string name="daily">Dagelijks</string>
|
||||
<string name="weekly">Wekelijks</string>
|
||||
<string name="monthly">Maandelijks</string>
|
||||
|
||||
<!-- Settings - About -->
|
||||
<string name="About">Over</string>
|
||||
@@ -132,6 +140,14 @@
|
||||
<string name="Github_Repository">Github Repository</string>
|
||||
<string name="license">Licentie</string>
|
||||
<string name="license_gpl3_only">GPL-3.0 Only</string>
|
||||
<string name="user_guide_title">Gebruikershandleiding</string>
|
||||
<string name="user_guide_description">Lees de handleiding om de applicatie volledig te benutten.</string>
|
||||
<string name="privacy_policy_title">Privacybeleid</string>
|
||||
<string name="privacy_policy_description">Privacybeleid van de applicatie.</string>
|
||||
<string name="share_title">Delen</string>
|
||||
<string name="share_description">Deel de applicatie met vrienden en familie.</string>
|
||||
<string name="data_title">Gegevens</string>
|
||||
<string name="data_subtitle">Back-up • Herstel</string>
|
||||
|
||||
<!-- Settings - Contact & Support -->
|
||||
<string name="Contact">Contact</string>
|
||||
@@ -190,6 +206,10 @@
|
||||
<string name="import_failed">Mislukt om notitie te importeren</string>
|
||||
<string name="find">Zoeken</string>
|
||||
<string name="replace">Vervangen</string>
|
||||
<string name="record_audio">Audio opnemen</string>
|
||||
<string name="tap_to_start">Tik om te starten</string>
|
||||
<string name="retry">Opnieuw proberen</string>
|
||||
<string name="permission_required">Geef alstublieft opname-toestemming.</string>
|
||||
|
||||
<!-- To-Do -->
|
||||
<string name="To_Do">Te Doen</string>
|
||||
@@ -323,4 +343,11 @@
|
||||
<string name="reading_view">Leesweergave</string>
|
||||
<string name="editing_view">Bewerkingsweergave</string>
|
||||
|
||||
<string name="shareContent">
|
||||
Flux is een moderne Android-productiviteitsapp die volledig met Compose is gebouwd.\n
|
||||
Hiermee kun je werkruimtes maken, bewerken en verwijderen met sjablonen zoals Notities, Gewoonten, Dagboek, To-Do, Evenementen, enz., samen met geavanceerde functies zoals zoeken en vervangen, notitiesjablonen, lokale import van afbeeldingen/video/audio, Markdown, LaTeX-formules en Mermaid-diagrammen.\n
|
||||
Download en probeer het nu:\n
|
||||
- GitHub: https://github.com/chindaronit/Flux/releases\n
|
||||
- F-Droid: https://f-droid.org/packages/com.flux/
|
||||
</string>
|
||||
</resources>
|
||||
|
||||
@@ -120,6 +120,14 @@
|
||||
<string name="Backup_Description">Fazer backup dos seus dados em um arquivo</string>
|
||||
<string name="Restore">Restaurar</string>
|
||||
<string name="Restore_Description">Restaurar seus dados a partir de um arquivo de backup</string>
|
||||
<string name="reset">Redefinir</string>
|
||||
<string name="backup_frequency">Frequência do backup automático</string>
|
||||
<string name="reset_database">Redefinir banco de dados</string>
|
||||
<string name="clear_app_data">Limpar todos os dados do app</string>
|
||||
<string name="never">Nunca</string>
|
||||
<string name="daily">Diário</string>
|
||||
<string name="weekly">Semanal</string>
|
||||
<string name="monthly">Mensal</string>
|
||||
|
||||
<!-- Settings - About -->
|
||||
<string name="About">Sobre</string>
|
||||
@@ -132,6 +140,14 @@
|
||||
<string name="Github_Repository">Repositório no GitHub</string>
|
||||
<string name="license">Licença</string>
|
||||
<string name="license_gpl3_only">GPL-3.0 Only</string>
|
||||
<string name="user_guide_title">Guia do usuário</string>
|
||||
<string name="user_guide_description">Leia o guia para utilizar completamente o aplicativo.</string>
|
||||
<string name="privacy_policy_title">Política de privacidade</string>
|
||||
<string name="privacy_policy_description">Política de privacidade do aplicativo.</string>
|
||||
<string name="share_title">Compartilhar</string>
|
||||
<string name="share_description">Compartilhe o aplicativo com amigos e familiares.</string>
|
||||
<string name="data_title">Dados</string>
|
||||
<string name="data_subtitle">Backup • Recuperação</string>
|
||||
|
||||
<!-- Settings - Contact & Support -->
|
||||
<string name="Contact">Contato</string>
|
||||
@@ -190,6 +206,10 @@
|
||||
<string name="import_failed">Falha ao importar nota</string>
|
||||
<string name="find">Pesquisar</string>
|
||||
<string name="replace">Substituir</string>
|
||||
<string name="record_audio">Gravar áudio</string>
|
||||
<string name="tap_to_start">Toque para começar</string>
|
||||
<string name="retry">Tentar novamente</string>
|
||||
<string name="permission_required">Por favor conceda permissão de gravação.</string>
|
||||
|
||||
<!-- To-Do -->
|
||||
<string name="To_Do">Tarefas</string>
|
||||
@@ -324,4 +344,12 @@
|
||||
<string name="reading_view">Visualização de leitura</string>
|
||||
<string name="editing_view">Visualização de edição</string>
|
||||
|
||||
<string name="shareContent">
|
||||
Flux é um aplicativo moderno de produtividade Android construído inteiramente com Compose.\n
|
||||
Ele permite criar, editar e excluir espaços de trabalho com modelos como Notas, Hábitos, Diário, Tarefas, Eventos etc., além de recursos avançados como localizar e substituir, modelos de notas, importação local de imagem/vídeo/áudio, Markdown, fórmulas LaTeX e diagramas Mermaid.\n
|
||||
Baixe e experimente agora:\n
|
||||
- GitHub: https://github.com/chindaronit/Flux/releases\n
|
||||
- F-Droid: https://f-droid.org/packages/com.flux/
|
||||
</string>
|
||||
|
||||
</resources>
|
||||
@@ -120,6 +120,14 @@
|
||||
<string name="Backup_Description">Сохранить данные в файл</string>
|
||||
<string name="Restore">Восстановить</string>
|
||||
<string name="Restore_Description">Восстановить данные из резервной копии</string>
|
||||
<string name="reset">Сбросить</string>
|
||||
<string name="backup_frequency">Частота автоматического резервного копирования</string>
|
||||
<string name="reset_database">Сбросить базу данных</string>
|
||||
<string name="clear_app_data">Очистить все данные приложения</string>
|
||||
<string name="never">Никогда</string>
|
||||
<string name="daily">Ежедневно</string>
|
||||
<string name="weekly">Еженедельно</string>
|
||||
<string name="monthly">Ежемесячно</string>
|
||||
|
||||
<!-- Settings - About -->
|
||||
<string name="About">О приложении</string>
|
||||
@@ -132,6 +140,14 @@
|
||||
<string name="Github_Repository">Репозиторий GitHub</string>
|
||||
<string name="license">Лицензия</string>
|
||||
<string name="license_gpl3_only">GPL-3.0 Only</string>
|
||||
<string name="user_guide_title">Руководство пользователя</string>
|
||||
<string name="user_guide_description">Прочитайте руководство, чтобы полностью использовать приложение.</string>
|
||||
<string name="privacy_policy_title">Политика конфиденциальности</string>
|
||||
<string name="privacy_policy_description">Политика конфиденциальности приложения.</string>
|
||||
<string name="share_title">Поделиться</string>
|
||||
<string name="share_description">Поделитесь приложением с друзьями и семьёй.</string>
|
||||
<string name="data_title">Данные</string>
|
||||
<string name="data_subtitle">Резервное копирование • Восстановление</string>
|
||||
|
||||
<!-- Settings - Contact & Support -->
|
||||
<string name="Contact">Контакт</string>
|
||||
@@ -190,6 +206,10 @@
|
||||
<string name="import_failed">Ошибка при импорте заметки</string>
|
||||
<string name="find">Найти</string>
|
||||
<string name="replace">Заменить</string>
|
||||
<string name="record_audio">Записать аудио</string>
|
||||
<string name="tap_to_start">Нажмите, чтобы начать</string>
|
||||
<string name="retry">Повторить</string>
|
||||
<string name="permission_required">Пожалуйста, предоставьте разрешение на запись.</string>
|
||||
|
||||
<!-- To-Do -->
|
||||
<string name="To_Do">Задачи</string>
|
||||
@@ -323,4 +343,12 @@
|
||||
<string name="reading_view">Режим чтения</string>
|
||||
<string name="editing_view">Режим редактирования</string>
|
||||
|
||||
<string name="shareContent">
|
||||
Flux — это современное Android-приложение для продуктивности, полностью созданное с использованием Compose.\n
|
||||
Оно позволяет создавать, редактировать и удалять рабочие пространства с шаблонами, такими как Заметки, Привычки, Дневник, Задачи, События и др., а также поддерживает расширенные функции: поиск и замена, шаблоны заметок, локальный импорт изображений/видео/аудио, Markdown, формулы LaTeX и диаграммы Mermaid.\n
|
||||
Скачайте и попробуйте сейчас:\n
|
||||
- GitHub: https://github.com/chindaronit/Flux/releases\n
|
||||
- F-Droid: https://f-droid.org/packages/com.flux/
|
||||
</string>
|
||||
|
||||
</resources>
|
||||
@@ -120,6 +120,14 @@
|
||||
<string name="Backup_Description">Backup your data to a file</string>
|
||||
<string name="Restore">Restore</string>
|
||||
<string name="Restore_Description">Restore your data from backup file</string>
|
||||
<string name="reset">Reset</string>
|
||||
<string name="backup_frequency">Frequency of automatic backup</string>
|
||||
<string name="reset_database">Reset Database</string>
|
||||
<string name="clear_app_data">Clear all app data</string>
|
||||
<string name="never">Never</string>
|
||||
<string name="daily">Daily</string>
|
||||
<string name="weekly">Weekly</string>
|
||||
<string name="monthly">Monthly</string>
|
||||
|
||||
<!-- Settings - About -->
|
||||
<string name="About">About</string>
|
||||
@@ -132,6 +140,14 @@
|
||||
<string name="Github_Repository">Github Repository</string>
|
||||
<string name="license">License</string>
|
||||
<string name="license_gpl3_only">GPL-3.0 Only</string>
|
||||
<string name="user_guide_title">User Guide</string>
|
||||
<string name="user_guide_description">Read the guide to fully utilize the application.</string>
|
||||
<string name="privacy_policy_title">Privacy Policy</string>
|
||||
<string name="privacy_policy_description">Privacy policy of the application.</string>
|
||||
<string name="share_title">Share</string>
|
||||
<string name="share_description">Share the application with friends and family.</string>
|
||||
<string name="data_title">Data</string>
|
||||
<string name="data_subtitle">Backup • Recovery</string>
|
||||
|
||||
<!-- Settings - Contact & Support -->
|
||||
<string name="Contact">Contact</string>
|
||||
@@ -190,6 +206,10 @@
|
||||
<string name="import_failed">Failed to import note</string>
|
||||
<string name="find">Find</string>
|
||||
<string name="replace">Replace</string>
|
||||
<string name="record_audio">Record Audio</string>
|
||||
<string name="tap_to_start">Tap to start</string>
|
||||
<string name="retry">Retry</string>
|
||||
<string name="permission_required">Please provide recording permission.</string>
|
||||
|
||||
<!-- To-Do -->
|
||||
<string name="To_Do">To-Do</string>
|
||||
@@ -304,7 +324,6 @@
|
||||
<string name="Space_Content_Desc">space</string>
|
||||
<string name="Edit_Space_Content_Desc">Edit-space</string>
|
||||
|
||||
|
||||
<!-- Editor -->
|
||||
<string name="editor_title">Editor</string>
|
||||
<string name="editor_subtitle">Default View • Default Editing Mode</string>
|
||||
@@ -329,4 +348,13 @@
|
||||
<string name="editing_view">Editing View</string>
|
||||
|
||||
|
||||
<string name="shareContent">
|
||||
Flux is a modern Android productivity app built entirely with Compose.\n
|
||||
It allows you to create, edit, and delete workspace which has various templates such as Notes, Habits, Journal, To-Do, Events, etc. along with advanced functionalities such as find and replace, note templates, local image/video/audio import, Markdown, LaTeX math formulas, and Mermaid diagram support.\n
|
||||
Download and try it now:\n
|
||||
- [GitHub] https://github.com/chindaronit/Flux/releases\n
|
||||
- [F-droid] https://f-droid.org/packages/com.flux/
|
||||
</string>
|
||||
|
||||
|
||||
</resources>
|
||||
@@ -24,6 +24,8 @@ hilt = "2.57.2"
|
||||
hiltNavigationCompose = "1.3.0"
|
||||
browser = "1.9.0"
|
||||
documentfile = "1.1.0"
|
||||
workRuntimeKtx = "2.11.1"
|
||||
foundationLayout = "1.10.3"
|
||||
|
||||
[libraries]
|
||||
androidx-biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" }
|
||||
@@ -66,6 +68,8 @@ commonmark-ext-heading-anchor = { group = "org.commonmark", name = "commonmark-e
|
||||
commonmark-ext-yaml-front-matter = { group = "org.commonmark", name = "commonmark-ext-yaml-front-matter", version.ref = "commonmark" }
|
||||
androidx-browser = { group = "androidx.browser", name = "browser", version.ref = "browser" }
|
||||
androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" }
|
||||
androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "workRuntimeKtx" }
|
||||
androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "foundationLayout" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
* Audio recorder added to notes and journal.
|
||||
* Privacy Policy and User guide added in About.
|
||||
* Removal of redundant editor options in Customize Screen.
|
||||
* Automatic Backup manager added in Flux.
|
||||
@@ -0,0 +1 @@
|
||||
Remove upgrade room crash bug.
|
||||
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 978 KiB |
|
Before Width: | Height: | Size: 402 KiB After Width: | Height: | Size: 559 KiB |
|
Before Width: | Height: | Size: 909 KiB After Width: | Height: | Size: 736 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 1.1 MiB |