Skip to content

Commit

Permalink
toggle expand title & description
Browse files Browse the repository at this point in the history
  • Loading branch information
danielyrovas committed Apr 26, 2024
1 parent b02c567 commit 78f5464
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 87 deletions.
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@ An unofficial Android client for [LinkDing](https://github.com/sissbruecker/link

## Features
- Save bookmarks through the Android share menu.
- Easily add tags to bookmarks.
- View recent bookmarks.
## Install
## Installation

1. Use [Obtainium](https://github.com/ImranR98/Obtainium) to install and update from GitHub, or
2. Download the latest apk from the [releases page](https://github.com/danielyrovas/linklater/releases/latest).

## TODO:
- Provide typical CRUD operations on bookmarks
- Simplify selecting/searching for tags when saving bookmarks
- Provide management operations on bookmarks: edit/delete.
- Make tag predictions smarter.
- Tests.
- Use Fastlane to release on the Play store.
- F-Droid release.

## Free Software

Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/org/yrovas/linklater/AppComponent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import org.yrovas.linklater.domain.BookmarkAPI
import org.yrovas.linklater.domain.BookmarkDataSource
import org.yrovas.linklater.domain.TagDataSource
import org.yrovas.linklater.ui.activity.AppActivity
import org.yrovas.linklater.ui.common.DestinationHost
import org.yrovas.linklater.ui.component.DestinationHost

const val TAG = "DEBUG/create"
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "preferences")
Expand Down
20 changes: 20 additions & 0 deletions app/src/main/java/org/yrovas/linklater/data/Bookmark.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,26 @@ data class Bookmark(
@SerialName("tag_names") val tags: List<String> = emptyList(),
)

fun Bookmark.showTitleOrElse(value: String): String {
return if (!title.isNullOrBlank()) {
title
} else if (!website_title.isNullOrBlank()) {
website_title
} else {
value
}
}

fun Bookmark.showDescriptionOrElse(value: String): String {
return if (!description.isNullOrBlank()) {
description
} else if (!website_description.isNullOrBlank()) {
website_description
} else {
value
}
}

fun GetBookmarksWithTags.toBookmark() = Bookmark(
id = id,
url = url,
Expand Down
40 changes: 30 additions & 10 deletions app/src/main/java/org/yrovas/linklater/ui/common/Icon.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,35 +7,55 @@ import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector

@Composable
fun Icon(painter: Painter, tint: Color, modifier: Modifier = Modifier) {
fun Icon(
painter: Painter,
tint: Color,
modifier: Modifier = Modifier,
contentDescription: String? = null,
) {
androidx.compose.material3.Icon(
modifier = modifier,
painter = painter,
tint = tint,
contentDescription = null
tint = tint, contentDescription = contentDescription
)
}

@Composable
fun Icon(painter: Painter, modifier: Modifier = Modifier) {
fun Icon(
painter: Painter,
modifier: Modifier = Modifier,
contentDescription: String? = null,
) {
androidx.compose.material3.Icon(
modifier = modifier, painter = painter, contentDescription = null
modifier = modifier,
painter = painter,
contentDescription = contentDescription
)
}

@Composable
fun Icon(imageVector: ImageVector, modifier: Modifier = Modifier) {
fun Icon(
imageVector: ImageVector,
modifier: Modifier = Modifier,
contentDescription: String? = null,
) {
androidx.compose.material3.Icon(
modifier = modifier, imageVector = imageVector, contentDescription = null
modifier = modifier,
imageVector = imageVector,
contentDescription = contentDescription
)
}

@Composable
fun Icon(imageVector: ImageVector, tint: Color, modifier: Modifier = Modifier) {
fun Icon(
imageVector: ImageVector,
tint: Color,
modifier: Modifier = Modifier,
contentDescription: String? = null,
) {
androidx.compose.material3.Icon(
modifier = modifier,
imageVector = imageVector,
tint = tint,
contentDescription = null
tint = tint, contentDescription = contentDescription
)
}
14 changes: 5 additions & 9 deletions app/src/main/java/org/yrovas/linklater/ui/common/KeyboardRow.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ import androidx.compose.ui.zIndex
fun KeyboardRow(content: @Composable () -> Unit) {
val isImeVisible = WindowInsets.isImeVisible
val density = LocalDensity.current
val offsetY =
WindowInsets.ime.getBottom(density) - WindowInsets.systemBars.getBottom(
density
)

val offsetY = WindowInsets.ime.getBottom(density) -
// take into account padding from system bars (navigation pill/buttons)
WindowInsets.systemBars.getBottom(density)

var previousOffset by remember { mutableStateOf(0) }

Expand All @@ -54,11 +54,7 @@ fun KeyboardRow(content: @Composable () -> Unit) {
contentAlignment = Alignment.BottomStart
) {
Box(modifier = Modifier
.offset {
IntOffset(0, -offsetY)
}
// .height(100.dp)
// .border(5.dp, colorScheme.tertiaryContainer)
.offset { IntOffset(0, -offsetY) }
.fillMaxWidth()) {
content()
}
Expand Down
74 changes: 48 additions & 26 deletions app/src/main/java/org/yrovas/linklater/ui/common/RefreshIcon.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.compose.animation.core.tween
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
Expand All @@ -21,13 +22,17 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import org.yrovas.linklater.ThemePreview
import org.yrovas.linklater.ui.theme.AppTheme
import kotlin.random.Random

@Composable
fun RefreshIcon(
modifier: Modifier = Modifier,
refreshing: StateFlow<Boolean>,
isRefreshing: StateFlow<Boolean>,
icon: ImageVector = Icons.Default.Refresh,
tint: Color = MaterialTheme.colorScheme.primary,
) {
Expand All @@ -36,35 +41,35 @@ fun RefreshIcon(
val linearInFastOutEasing: Easing =
remember { CubicBezierEasing(0.4f, 0.0f, 0.25f, 1.0f) }

val isRefreshing by refreshing.collectAsState()
val refreshing by isRefreshing.collectAsState()
var didRefresh by remember { mutableStateOf(false) }
val rotation = remember { Animatable(0f) }

LaunchedEffect(isRefreshing) {
launch {
val duration = 750
val target = 360f
if (isRefreshing) {
didRefresh = true
rotation.animateTo(
targetValue = 360f, animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = duration, easing = slowInFastOutEasing
), repeatMode = RepeatMode.Restart
)
LaunchedEffect(refreshing) {
val duration = 750
val target = 360f
if (refreshing) {
didRefresh = true
rotation.animateTo(
targetValue = 360f, animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = duration, easing = slowInFastOutEasing
), repeatMode = RepeatMode.Restart
)
} else if (didRefresh) {
val remainingMillis: Int = ((target - rotation.value) / target * duration).toInt()
val easing = if (remainingMillis > (duration/2)) linearInFastOutEasing else LinearEasing
rotation.animateTo(
targetValue = 360f,
initialVelocity = rotation.velocity,
animationSpec = tween(
durationMillis = remainingMillis, easing = easing
)
)
} else if (didRefresh) {
val remainingMillis: Int =
((target - rotation.value) / target * duration).toInt()
val easing =
if (remainingMillis > (duration / 2)) linearInFastOutEasing else LinearEasing
rotation.animateTo(
targetValue = 360f,
initialVelocity = rotation.velocity,
animationSpec = tween(
durationMillis = remainingMillis, easing = easing
)
rotation.snapTo(0f)
}
)
rotation.snapTo(0f)
}
}

Expand All @@ -74,3 +79,20 @@ fun RefreshIcon(
modifier = modifier.rotate(rotation.value)
)
}

@ThemePreview
@Composable
fun PreviewRefreshIcon() {
val refreshing = remember { MutableStateFlow(false) }
LaunchedEffect(Unit) {
while (true) {
delay(Random.nextLong(1000, 4000))
refreshing.emit(!refreshing.value)
}
}
AppTheme {
Surface {
RefreshIcon(isRefreshing = refreshing)
}
}
}
34 changes: 28 additions & 6 deletions app/src/main/java/org/yrovas/linklater/ui/common/TextPreference.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,36 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.filled.Build
import androidx.compose.material.icons.filled.ContentPasteGo
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material3.*
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme.colorScheme
import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.runtime.*
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
Expand Down Expand Up @@ -93,7 +114,7 @@ private fun TextPreference(
.fillMaxWidth()
.padding(padding.standard)
.clip(RoundedCornerShape(8.dp))
.clickable { showDialog = true }
.clickable { showDialog = true }
,
) {
Column {
Expand Down Expand Up @@ -181,7 +202,8 @@ private fun TextEditDialog(
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start,
modifier = Modifier.fillMaxWidth()
modifier = Modifier
.fillMaxWidth()
.clickable { showInfo = !showInfo }
.padding(padding.standard)
) {
Expand Down
Loading

0 comments on commit 78f5464

Please sign in to comment.