Skip to content

Commit

Permalink
Add the search page
Browse files Browse the repository at this point in the history
  • Loading branch information
rayworks committed Aug 6, 2023
1 parent 66829db commit 7435465
Show file tree
Hide file tree
Showing 8 changed files with 379 additions and 125 deletions.
3 changes: 3 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
</intent-filter>
</activity>

<activity android:name=".search.SearchComposeActivity"
android:theme="@style/AppThemeMD"
/>
<activity
android:name=".dashboard.DetailActivity"
android:exported="false"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,13 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Scaffold
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
Expand All @@ -34,16 +30,15 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.lifecycleScope
import coil.compose.AsyncImage
import com.rayworks.droidweekly.R
import com.rayworks.droidweekly.dashboard.ui.theme.LightBlue
import com.rayworks.droidweekly.di.KeyValueStorage
import com.rayworks.droidweekly.model.ArticleItem
import com.rayworks.droidweekly.model.OldItemRef
import com.rayworks.droidweekly.search.SearchComposeActivity
import com.rayworks.droidweekly.ui.component.FeedList
import com.rayworks.droidweekly.ui.theme.DroidWeeklyTheme
import com.rayworks.droidweekly.viewmodel.ArticleListViewModel
import dagger.hilt.android.AndroidEntryPoint
Expand Down Expand Up @@ -105,6 +100,7 @@ class NewsFeedActivity : ComponentActivity() {
appBarState = topAppBarState,
scrollBehavior = scrollBehavior,
context = this@NewsFeedActivity,
onSearch = { SearchComposeActivity.start(this@NewsFeedActivity) },
)
},
content = { innerPadding ->
Expand Down Expand Up @@ -201,6 +197,7 @@ fun FeedTopAppBar(
appBarState,
),
context: Context,
onSearch: () -> Unit,
) {
val ctx = LocalContext.current
CenterAlignedTopAppBar(
Expand All @@ -222,11 +219,7 @@ fun FeedTopAppBar(
},
actions = {
IconButton(onClick = {
Toast.makeText(
ctx,
"Search is not yet implemented ;)",
Toast.LENGTH_LONG,
).show()
onSearch.invoke()
}) {
Icon(
imageVector = Icons.Filled.Search,
Expand All @@ -240,112 +233,3 @@ fun FeedTopAppBar(
modifier = modifier,
)
}

@Composable
fun FeedList(
modifier: Modifier = Modifier,
showLoading: Boolean,
listState: List<ArticleItem>,
onViewUrl: (url: String, title: String?) -> Unit,
) {
Surface(modifier = modifier, color = MaterialTheme.colorScheme.background) {
if (showLoading) {
return@Surface Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator(
modifier = Modifier
.width(32.dp)
.height(32.dp),
color = MaterialTheme.colorScheme.primary,
strokeWidth = 2.dp,
)
}
}

if (listState.size > 1) {
println(">>> list state : $listState")
LazyColumn(
modifier,
contentPadding = PaddingValues(all = 16.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
itemsIndexed(items = listState) { index, it ->
ArticleCard(data = it) { data: ArticleItem ->
println("item '${data.title}' clicked")

if (data.linkage.isNotEmpty()) {
onViewUrl.invoke(data.linkage, data.title)
}
}

val hiddenSeparator =
it.linkage.isEmpty() || index < (listState.size - 1) && listState[index + 1].linkage.isEmpty()
if (!hiddenSeparator) {
Divider(color = Color.Black, thickness = Dp.Hairline)
}
}
}
}
}
}

@Composable
fun ArticleCard(data: ArticleItem, onItemClick: (data: ArticleItem) -> Unit) {
Box(
modifier = Modifier
.clickable {
if (data.description.isNotEmpty()) {
onItemClick.invoke(data)
}
},
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
val invalid = data.imageUrl?.isEmpty() ?: true
if (!invalid) {
AsyncImage(
model = data.imageUrl,
contentDescription = null,
modifier = Modifier
.size(72.dp)
.padding(end = 16.dp),
)
}
Column {
if (data.title.isNotEmpty()) {
val noDesc = data.description.isEmpty()
Box(
modifier = Modifier
.heightIn(min = if (noDesc) 48.dp else Dp.Unspecified)
.background(color = if (noDesc) LightBlue else Color.Transparent),
contentAlignment = Alignment.CenterStart,
) {
Text(
text = data.title,
fontSize = 16.sp,
color = if (noDesc) Color.White else MaterialTheme.colorScheme.secondary,
modifier = Modifier
.padding(
top = if (noDesc) 0.dp else 16.dp,
start = if (noDesc) 16.dp else 0.dp,
)
.fillMaxWidth(),
)
}
}

if (data.description.isNotEmpty()) {
Text(
text = data.description,
fontSize = 14.sp,
modifier = Modifier.padding(top = 16.dp, bottom = 16.dp),
)
}
}
}
}
}
7 changes: 7 additions & 0 deletions app/src/main/java/com/rayworks/droidweekly/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import javax.inject.Singleton

/***
Expand Down Expand Up @@ -50,4 +52,9 @@ object AppModule {
fun provideWebContentParser(): WebContentParser {
return WebContentParser()
}

@Provides
fun provideDispatcher(): CoroutineDispatcher {
return Dispatchers.IO
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package com.rayworks.droidweekly.search

import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.rayworks.droidweekly.dashboard.DetailActivity
import com.rayworks.droidweekly.ui.component.FeedList
import com.rayworks.droidweekly.ui.theme.DroidWeeklyTheme
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class SearchComposeActivity : ComponentActivity() {
private val searchViewModel: SearchViewModel by viewModels()

companion object {
@JvmStatic
fun start(context: Context) {
val starter = Intent(context, SearchComposeActivity::class.java)
context.startActivity(starter)
}
}

@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContent {
DroidWeeklyTheme {
Scaffold(
modifier = Modifier,
topBar = { BuildTopBar { this@SearchComposeActivity.finish() } },
) {
BuildBody(Modifier) { url, title ->
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(url)
DetailActivity.start(this@SearchComposeActivity, url, title = title)
}
}
}
}
}

@Composable
private fun BuildBody(
modifier: Modifier,
onArticleClick: (url: String, title: String?) -> Unit,
) {
val itemsFlow by searchViewModel.itemsFlow.collectAsState()

FeedList(
modifier = modifier,
showLoading = false,
listState = itemsFlow,
onViewUrl = onArticleClick,
)
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun BuildTopBar(modifier: Modifier = Modifier, onUpClick: () -> Unit) {
val input by searchViewModel.query.collectAsState()

CenterAlignedTopAppBar(
title = {
BuildEditor(
"Search",
input = input,
onSearch = { str ->
searchViewModel.setQuery(str)
},
onClear = {
searchViewModel.setQuery("")
},
)
},

modifier = modifier.statusBarsPadding(),

navigationIcon = {
IconButton(onClick = onUpClick) {
Icon(
Icons.Filled.ArrowBack,
contentDescription = null,
)
}
},
)
}

@Composable
private fun BuildEditor(
hint: String,
input: String,
onSearch: (String) -> Unit,
onClear: () -> Unit,
) {
TextField(
value = input,
onValueChange = { newValue -> onSearch(newValue) },
label = { Text(hint) },
singleLine = true,
trailingIcon = {
IconButton(onClick = { onClear.invoke() }) {
Icon(imageVector = Icons.Default.Clear, contentDescription = "")
}
},
colors = TextFieldDefaults.textFieldColors(
backgroundColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
cursorColor = MaterialTheme.colors.primary,
),
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
)
}
}
Loading

0 comments on commit 7435465

Please sign in to comment.