Change-Id: Iee5e8bd0932d1b68770aaa1e23fa660451109601pull/2/head
parent
dc4af7e6f7
commit
d945a007c6
@ -0,0 +1 @@
|
|||||||
|
/build
|
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
plugins {
|
||||||
|
id("nowinandroid.android.library")
|
||||||
|
id("nowinandroid.android.library.compose")
|
||||||
|
id("nowinandroid.android.library.jacoco")
|
||||||
|
kotlin("kapt")
|
||||||
|
id("dagger.hilt.android.plugin")
|
||||||
|
id("nowinandroid.spotless")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
defaultConfig {
|
||||||
|
testInstrumentationRunner = "com.google.samples.apps.nowinandroid.core.testing.NiaTestRunner"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":core-model"))
|
||||||
|
implementation(project(":core-ui"))
|
||||||
|
implementation(project(":core-data"))
|
||||||
|
implementation(project(":core-common"))
|
||||||
|
|
||||||
|
testImplementation(project(":core-testing"))
|
||||||
|
androidTestImplementation(project(":core-testing"))
|
||||||
|
|
||||||
|
implementation(libs.coil.kt)
|
||||||
|
implementation(libs.coil.kt.compose)
|
||||||
|
|
||||||
|
implementation(libs.kotlinx.coroutines.android)
|
||||||
|
implementation(libs.kotlinx.datetime)
|
||||||
|
|
||||||
|
implementation(libs.androidx.hilt.navigation.compose)
|
||||||
|
implementation(libs.androidx.lifecycle.viewModelCompose)
|
||||||
|
|
||||||
|
implementation(libs.hilt.android)
|
||||||
|
kapt(libs.hilt.compiler)
|
||||||
|
|
||||||
|
// TODO : Remove this dependency once we upgrade to Android Studio Dolphin b/228889042
|
||||||
|
// These dependencies are currently necessary to render Compose previews
|
||||||
|
debugImplementation(libs.androidx.customview.poolingcontainer)
|
||||||
|
|
||||||
|
// androidx.test is forcing JUnit, 4.12. This forces it to use 4.13
|
||||||
|
configurations.configureEach {
|
||||||
|
resolutionStrategy {
|
||||||
|
force(libs.junit4)
|
||||||
|
// Temporary workaround for https://issuetracker.google.com/174733673
|
||||||
|
force("org.objenesis:objenesis:2.6")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2022 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.google.samples.apps.nowinandroid.feature.author">
|
||||||
|
|
||||||
|
</manifest>
|
@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.samples.apps.nowinandroid.feature.author
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.only
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.safeDrawing
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.icons.Icons.Filled
|
||||||
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import com.google.samples.apps.nowinandroid.core.model.data.Author
|
||||||
|
import com.google.samples.apps.nowinandroid.core.model.data.FollowableAuthor
|
||||||
|
import com.google.samples.apps.nowinandroid.core.ui.LoadingWheel
|
||||||
|
import com.google.samples.apps.nowinandroid.core.ui.component.NiaFilterChip
|
||||||
|
import com.google.samples.apps.nowinandroid.core.ui.newsResourceCardItems
|
||||||
|
import com.google.samples.apps.nowinandroid.feature.author.AuthorUiState.Loading
|
||||||
|
import com.google.samples.apps.nowinandroid.feature.author.R.string
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AuthorRoute(
|
||||||
|
onBackClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
viewModel: AuthorViewModel = hiltViewModel(),
|
||||||
|
) {
|
||||||
|
val uiState: AuthorScreenUiState by viewModel.uiState.collectAsState()
|
||||||
|
|
||||||
|
AuthorScreen(
|
||||||
|
authorState = uiState.authorState,
|
||||||
|
newsState = uiState.newsState,
|
||||||
|
modifier = modifier,
|
||||||
|
onBackClick = onBackClick,
|
||||||
|
onFollowClick = viewModel::followAuthorToggle,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
@VisibleForTesting
|
||||||
|
@Composable
|
||||||
|
internal fun AuthorScreen(
|
||||||
|
authorState: AuthorUiState,
|
||||||
|
newsState: NewsUiState,
|
||||||
|
onBackClick: () -> Unit,
|
||||||
|
onFollowClick: (Boolean) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = modifier,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Spacer(
|
||||||
|
// TODO: Replace with windowInsetsTopHeight after
|
||||||
|
// https://issuetracker.google.com/issues/230383055
|
||||||
|
Modifier.windowInsetsPadding(
|
||||||
|
WindowInsets.safeDrawing.only(WindowInsetsSides.Top)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
when (authorState) {
|
||||||
|
Loading -> {
|
||||||
|
item {
|
||||||
|
LoadingWheel(
|
||||||
|
modifier = modifier,
|
||||||
|
contentDesc = stringResource(id = string.author_loading),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AuthorUiState.Error -> {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
is AuthorUiState.Success -> {
|
||||||
|
item {
|
||||||
|
AuthorToolbar(
|
||||||
|
onBackClick = onBackClick,
|
||||||
|
onFollowClick = onFollowClick,
|
||||||
|
uiState = authorState.followableAuthor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
authorBody(
|
||||||
|
author = authorState.followableAuthor.author,
|
||||||
|
news = newsState
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Spacer(
|
||||||
|
// TODO: Replace with windowInsetsBottomHeight after
|
||||||
|
// https://issuetracker.google.com/issues/230383055
|
||||||
|
Modifier.windowInsetsPadding(
|
||||||
|
WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun LazyListScope.authorBody(
|
||||||
|
author: Author,
|
||||||
|
news: NewsUiState
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
AuthorHeader(author)
|
||||||
|
}
|
||||||
|
|
||||||
|
authorCards(news)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AuthorHeader(author: Author) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(horizontal = 24.dp)
|
||||||
|
) {
|
||||||
|
AsyncImage(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(216.dp)
|
||||||
|
.align(Alignment.CenterHorizontally)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.padding(bottom = 12.dp),
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
model = author.imageUrl,
|
||||||
|
contentDescription = "Author profile picture",
|
||||||
|
)
|
||||||
|
Text(author.name, style = MaterialTheme.typography.displayMedium)
|
||||||
|
if (author.bio.isNotEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = author.bio,
|
||||||
|
modifier = Modifier.padding(top = 24.dp),
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun LazyListScope.authorCards(news: NewsUiState) {
|
||||||
|
when (news) {
|
||||||
|
is NewsUiState.Success -> {
|
||||||
|
newsResourceCardItems(
|
||||||
|
items = news.news,
|
||||||
|
newsResourceMapper = { it },
|
||||||
|
isBookmarkedMapper = { /* TODO */ false },
|
||||||
|
onToggleBookmark = { /* TODO */ },
|
||||||
|
itemModifier = Modifier.padding(24.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is NewsUiState.Loading -> item {
|
||||||
|
LoadingWheel(contentDesc = "Loading news") // TODO
|
||||||
|
}
|
||||||
|
else -> item {
|
||||||
|
Text("Error") // TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AuthorToolbar(
|
||||||
|
uiState: FollowableAuthor,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onBackClick: () -> Unit = {},
|
||||||
|
onFollowClick: (Boolean) -> Unit = {},
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 32.dp)
|
||||||
|
) {
|
||||||
|
IconButton(onClick = { onBackClick() }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Filled.ArrowBack,
|
||||||
|
contentDescription = stringResource(id = R.string.back)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val selected = uiState.isFollowed
|
||||||
|
NiaFilterChip(
|
||||||
|
checked = selected,
|
||||||
|
onCheckedChange = onFollowClick,
|
||||||
|
) {
|
||||||
|
if (selected) {
|
||||||
|
Text(stringResource(id = string.author_following))
|
||||||
|
} else {
|
||||||
|
Text(stringResource(id = string.author_not_following))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun AuthorBodyPreview() {
|
||||||
|
MaterialTheme {
|
||||||
|
LazyColumn {
|
||||||
|
authorBody(
|
||||||
|
author = Author(
|
||||||
|
id = "0",
|
||||||
|
name = "Android Dev",
|
||||||
|
bio = "Works on Compose",
|
||||||
|
twitter = "dev",
|
||||||
|
mediumPage = "",
|
||||||
|
imageUrl = "",
|
||||||
|
),
|
||||||
|
news = NewsUiState.Success(emptyList())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.samples.apps.nowinandroid.feature.author
|
||||||
|
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.google.samples.apps.nowinandroid.core.data.repository.AuthorsRepository
|
||||||
|
import com.google.samples.apps.nowinandroid.core.data.repository.NewsRepository
|
||||||
|
import com.google.samples.apps.nowinandroid.core.model.data.Author
|
||||||
|
import com.google.samples.apps.nowinandroid.core.model.data.FollowableAuthor
|
||||||
|
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
|
||||||
|
import com.google.samples.apps.nowinandroid.core.result.Result
|
||||||
|
import com.google.samples.apps.nowinandroid.core.result.asResult
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class AuthorViewModel @Inject constructor(
|
||||||
|
savedStateHandle: SavedStateHandle,
|
||||||
|
private val authorsRepository: AuthorsRepository,
|
||||||
|
newsRepository: NewsRepository
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val authorId: String = checkNotNull(
|
||||||
|
savedStateHandle[AuthorDestinationsArgs.AUTHOR_ID_ARG]
|
||||||
|
)
|
||||||
|
|
||||||
|
// Observe the followed authors, as they could change over time.
|
||||||
|
private val followedAuthorIdsStream: Flow<Result<Set<String>>> =
|
||||||
|
authorsRepository.getFollowedAuthorIdsStream().asResult()
|
||||||
|
|
||||||
|
// Observe author information
|
||||||
|
private val author: Flow<Result<Author>> = authorsRepository.getAuthorStream(
|
||||||
|
id = authorId
|
||||||
|
).asResult()
|
||||||
|
|
||||||
|
// Observe the News for this author
|
||||||
|
private val newsStream: Flow<Result<List<NewsResource>>> =
|
||||||
|
newsRepository.getNewsResourcesStream(
|
||||||
|
filterAuthorIds = setOf(element = authorId),
|
||||||
|
filterTopicIds = emptySet()
|
||||||
|
).asResult()
|
||||||
|
|
||||||
|
val uiState: StateFlow<AuthorScreenUiState> =
|
||||||
|
combine(
|
||||||
|
followedAuthorIdsStream,
|
||||||
|
author,
|
||||||
|
newsStream
|
||||||
|
) { followedAuthorsResult, authorResult, newsResult ->
|
||||||
|
val author: AuthorUiState =
|
||||||
|
if (authorResult is Result.Success && followedAuthorsResult is Result.Success) {
|
||||||
|
val followed = followedAuthorsResult.data.contains(authorId)
|
||||||
|
AuthorUiState.Success(
|
||||||
|
followableAuthor = FollowableAuthor(
|
||||||
|
author = authorResult.data,
|
||||||
|
isFollowed = followed
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else if (
|
||||||
|
authorResult is Result.Loading || followedAuthorsResult is Result.Loading
|
||||||
|
) {
|
||||||
|
AuthorUiState.Loading
|
||||||
|
} else {
|
||||||
|
AuthorUiState.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
val news: NewsUiState = when (newsResult) {
|
||||||
|
is Result.Success -> NewsUiState.Success(newsResult.data)
|
||||||
|
is Result.Loading -> NewsUiState.Loading
|
||||||
|
is Result.Error -> NewsUiState.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthorScreenUiState(author, news)
|
||||||
|
}
|
||||||
|
.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.WhileSubscribed(5_000),
|
||||||
|
initialValue = AuthorScreenUiState(AuthorUiState.Loading, NewsUiState.Loading)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun followAuthorToggle(followed: Boolean) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
authorsRepository.toggleFollowedAuthorId(authorId, followed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface AuthorUiState {
|
||||||
|
data class Success(val followableAuthor: FollowableAuthor) : AuthorUiState
|
||||||
|
object Error : AuthorUiState
|
||||||
|
object Loading : AuthorUiState
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface NewsUiState {
|
||||||
|
data class Success(val news: List<NewsResource>) : NewsUiState
|
||||||
|
object Error : NewsUiState
|
||||||
|
object Loading : NewsUiState
|
||||||
|
}
|
||||||
|
|
||||||
|
data class AuthorScreenUiState(
|
||||||
|
val authorState: AuthorUiState,
|
||||||
|
val newsState: NewsUiState
|
||||||
|
)
|
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.google.samples.apps.nowinandroid.feature.author
|
||||||
|
|
||||||
|
import com.google.samples.apps.nowinandroid.feature.author.AuthorDestinationsArgs.AUTHOR_ID_ARG
|
||||||
|
import com.google.samples.apps.nowinandroid.feature.author.InterestsScreens.AUTHOR_SCREEN
|
||||||
|
|
||||||
|
object AuthorDestinations {
|
||||||
|
const val AUTHOR_ROUTE = "$AUTHOR_SCREEN/{$AUTHOR_ID_ARG}"
|
||||||
|
}
|
||||||
|
|
||||||
|
object AuthorDestinationsArgs {
|
||||||
|
const val AUTHOR_ID_ARG = "authorId"
|
||||||
|
}
|
||||||
|
|
||||||
|
object InterestsScreens {
|
||||||
|
const val AUTHOR_SCREEN = "author"
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright 2022 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
<resources>
|
||||||
|
<string name="author">Author</string>
|
||||||
|
<string name="author_loading">Loading author</string>
|
||||||
|
<string name="author_following">FOLLOWING</string>
|
||||||
|
<string name="author_not_following">NOT FOLLOWING</string>
|
||||||
|
</resources>
|
Loading…
Reference in new issue