Compare commits

...

2 Commits

3 changed files with 142 additions and 33 deletions

View File

@ -0,0 +1,55 @@
package xyz.pixelatedw.recipe.ui.components
import android.content.Intent
import androidx.compose.foundation.layout.Box
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
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 xyz.pixelatedw.recipe.MainActivity
import xyz.pixelatedw.recipe.utils.sync
@Composable
fun ImportDropdownMenu(ctx: MainActivity, modifier: Modifier) {
var expanded by remember { mutableStateOf(false) }
Box(
modifier = modifier,
) {
IconButton(
onClick = { expanded = !expanded }
) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = "Import recipes"
)
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
DropdownMenuItem(
text = { Text("Import") },
onClick = {
ctx.sourceChooser.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE))
expanded = false
}
)
DropdownMenuItem(
text = { Text("Sync") },
onClick = {
sync(ctx)
expanded = false
}
)
}
}
}

View File

@ -1,7 +1,6 @@
package xyz.pixelatedw.recipe.ui.components
import android.content.Intent
import androidx.compose.foundation.background
import android.content.Context
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
@ -10,15 +9,19 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@ -27,6 +30,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
@ -35,9 +39,11 @@ import xyz.pixelatedw.recipe.MainActivity
import xyz.pixelatedw.recipe.R
import xyz.pixelatedw.recipe.data.RecipeWithTags
import xyz.pixelatedw.recipe.data.RecipesView
import xyz.pixelatedw.recipe.utils.sync
import xyz.pixelatedw.recipe.utils.DEFAULT_SYNC_SERVER_IP
import xyz.pixelatedw.recipe.utils.DEFAULT_SYNC_SERVER_PORT
import java.io.File
@Composable
fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
val recipes = view.recipes.collectAsState()
@ -45,6 +51,11 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
val search = view.search.collectAsState()
val filters = view.tagFilters.collectAsState()
val prefs = ctx.getPreferences(Context.MODE_PRIVATE)
var syncIp by remember { mutableStateOf(prefs.getString("syncServerIp", DEFAULT_SYNC_SERVER_IP)!!) }
var syncPort by remember { mutableIntStateOf(prefs.getInt("syncServerPort", DEFAULT_SYNC_SERVER_PORT)) }
val navController = rememberNavController()
var openDeletionDialog by remember { mutableStateOf(false) }
@ -101,13 +112,15 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
OutlinedTextField(
modifier = Modifier.weight(0.7f),
modifier = Modifier
.weight(0.7f)
.padding(end = 4.dp),
value = search.value.orEmpty(),
onValueChange = { search -> view.setSearch(search) },
)
// Tags
IconButton(
modifier = Modifier.weight(0.15f),
modifier = Modifier.weight(0.1f),
onClick = { openTagFilterDialog = true },
) {
Icon(
@ -115,10 +128,10 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
contentDescription = "Toggle tags filtering menu"
)
}
// Load / Delete
// Import / Delete
if (selectedEntries.isNotEmpty()) {
IconButton(
modifier = Modifier.weight(0.15f),
modifier = Modifier.weight(0.1f),
onClick = {
openDeletionDialog = true
},
@ -130,18 +143,19 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
)
}
} else {
ImportDropdownMenu(ctx, Modifier.weight(0.1f))
}
// Options
IconButton(
modifier = Modifier.weight(0.15f),
onClick = { sync(ctx) },
// onClick = { ctx.sourceChooser.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)) },
modifier = Modifier.weight(0.1f),
onClick = { navController.navigate("settings") },
) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = "Load recipes from filesystem"
imageVector = Icons.Default.Settings,
contentDescription = "Open settings menu"
)
}
}
}
LazyColumn {
items(recipes.value) { entry ->
if (isInSearch(entry)) {
@ -202,5 +216,43 @@ fun MainScreen(ctx: MainActivity, padding: PaddingValues, view: RecipesView) {
composable("info") {
RecipeInfo(ctx, view, navController, padding, active.value!!)
}
composable("settings") {
Column(modifier = Modifier.padding(start = 12.dp, top = 48.dp)) {
OutlinedTextField(
label = { Text("Sync Server IP") },
singleLine = true,
value = syncIp,
onValueChange = { syncIp = it },
)
OutlinedTextField(
modifier = Modifier.padding(top = 8.dp),
label = { Text("Sync Server IP") },
singleLine = true,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
value = syncPort.toString(),
onValueChange = {
syncPort = when (it.toIntOrNull()) {
null -> syncPort
else -> it.toInt()
}
},
)
Button(
modifier = Modifier.padding(top = 8.dp),
onClick = {
with (prefs.edit()) {
putString("syncServerIp", syncIp)
putInt("syncServerPort", syncPort)
apply()
}
navController.navigate("list")
},
) {
Text("Save")
}
}
}
}
}

View File

@ -1,13 +1,10 @@
package xyz.pixelatedw.recipe.utils
import android.os.Build
import android.os.FileUtils
import android.content.Context
import android.os.Handler
import android.os.Looper
import androidx.annotation.RequiresApi
import androidx.documentfile.provider.DocumentFile
import xyz.pixelatedw.recipe.MainActivity
import java.io.BufferedOutputStream
import java.io.DataInputStream
import java.io.File
import java.io.FileOutputStream
@ -16,14 +13,20 @@ import java.nio.ByteBuffer
import java.nio.charset.StandardCharsets
import java.util.concurrent.Executors
const val DEFAULT_SYNC_SERVER_IP = "192.168.0.100"
const val DEFAULT_SYNC_SERVER_PORT = 9696
fun sync(ctx: MainActivity) {
val executor = Executors.newSingleThreadExecutor()
val handler = Handler(Looper.getMainLooper())
executor.execute(Runnable() {
executor.execute {
try {
val conn = Socket("192.168.0.100", 9696)
val prefs = ctx.getPreferences(Context.MODE_PRIVATE)
val syncServerIp = prefs.getString("syncServerIp", DEFAULT_SYNC_SERVER_IP)
val syncServerPort = prefs.getInt("syncServerPort", DEFAULT_SYNC_SERVER_PORT)
val conn = Socket(syncServerIp, syncServerPort)
val stream = conn.getInputStream()
val inputStream = DataInputStream(stream)
@ -31,16 +34,16 @@ fun sync(ctx: MainActivity) {
var buffer = ByteArray(8)
inputStream.read(buffer)
val filesSent = ByteBuffer.wrap(buffer).getLong().toInt()
// println("files sent: $filesSent")
for(f in 0..<filesSent) {
for (f in 0..<filesSent) {
buffer = ByteArray(8)
inputStream.read(buffer)
val nameBufLen = ByteBuffer.wrap(buffer).getLong().toInt()
buffer = ByteArray(nameBufLen)
inputStream.read(buffer)
val fileFullPath = StandardCharsets.UTF_8.decode(ByteBuffer.wrap(buffer)).toString();
val fileFullPath =
StandardCharsets.UTF_8.decode(ByteBuffer.wrap(buffer)).toString();
val fileName = fileFullPath.split("/").last()
val filePath = fileFullPath.replace(fileName, "")
@ -62,8 +65,7 @@ fun sync(ctx: MainActivity) {
while (usedBytes != contentBufLen) {
if (usedBytes + blockSize > contentBufLen) {
blockSize = contentBufLen - usedBytes
}
else if (blockSize > contentBufLen) {
} else if (blockSize > contentBufLen) {
blockSize = contentBufLen
}
@ -87,10 +89,10 @@ fun sync(ctx: MainActivity) {
e.printStackTrace()
}
handler.post(Runnable {
handler.post {
val recipes = ctx.db.recipeWithTagsDao().getAll()
ctx.recipeView.setRecipes(recipes)
println("syncing complete")
})
})
// println("syncing complete")
}
}
}