diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 526b4c2..a2d7c21 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -13,7 +13,6 @@
-
diff --git a/app/build.gradle b/app/build.gradle
index 32f3a7c..5405db6 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -17,14 +17,14 @@ android {
keyPassword signingProperties.getProperty("signing.release.password")
}
}
- compileSdk 31
+ compileSdk 33
defaultConfig {
applicationId "de.mr_pine.xkcdfeed"
minSdk 22
targetSdk 31
- versionCode 3
- versionName "1.1.1"
+ versionCode 4
+ versionName "1.2.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@@ -45,13 +45,12 @@ android {
}
kotlinOptions {
jvmTarget = '1.8'
- useIR = true
}
buildFeatures {
compose true
}
composeOptions {
- kotlinCompilerExtensionVersion compose_version
+ kotlinCompilerExtensionVersion "1.3.1"
}
packagingOptions {
resources {
@@ -62,17 +61,17 @@ android {
dependencies {
- implementation 'androidx.core:core-ktx:1.7.0'
- implementation 'androidx.appcompat:appcompat:1.4.1'
- implementation 'com.google.android.material:material:1.5.0'
+ implementation 'androidx.core:core-ktx:1.8.0'
+ implementation 'androidx.appcompat:appcompat:1.5.0'
+ implementation 'com.google.android.material:material:1.6.1'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.ui:ui-util:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
- implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
+ implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
- implementation 'androidx.activity:activity-compose:1.4.0'
+ implementation 'androidx.activity:activity-compose:1.5.1'
implementation platform('com.google.firebase:firebase-bom:29.0.3')
implementation 'com.google.firebase:firebase-auth-ktx'
@@ -80,7 +79,7 @@ dependencies {
implementation 'com.google.firebase:firebase-analytics-ktx'
implementation 'com.google.firebase:firebase-firestore-ktx'
- implementation 'com.google.android.gms:play-services-auth:20.1.0'
+ implementation 'com.google.android.gms:play-services-auth:20.3.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
@@ -90,7 +89,7 @@ dependencies {
implementation "com.google.accompanist:accompanist-pager:$accompanist_version"
implementation "com.google.accompanist:accompanist-pager-indicators:$accompanist_version"
implementation "com.google.accompanist:accompanist-placeholder-material:$accompanist_version"
- implementation 'androidx.navigation:navigation-compose:2.5.0-alpha03'
+ implementation 'androidx.navigation:navigation-compose:2.5.1'
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
implementation "com.android.volley:volley:1.2.1"
implementation "androidx.datastore:datastore-preferences:1.0.0"
@@ -98,7 +97,7 @@ dependencies {
implementation 'com.google.android.gms:play-services-oss-licenses:17.0.0'
- implementation "io.coil-kt:coil-compose:2.0.0-rc02"
+ implementation "io.coil-kt:coil-compose:2.2.0"
implementation 'de.mr-pine.utils:zoomables:1.1.1'
}
diff --git a/app/src/main/java/de/mr_pine/xkcdfeed/MainActivity.kt b/app/src/main/java/de/mr_pine/xkcdfeed/MainActivity.kt
index e572eb0..bd0924e 100644
--- a/app/src/main/java/de/mr_pine/xkcdfeed/MainActivity.kt
+++ b/app/src/main/java/de/mr_pine/xkcdfeed/MainActivity.kt
@@ -10,9 +10,16 @@ import android.text.format.DateFormat
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
+import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.Scaffold
import androidx.compose.material.Text
+import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.*
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.intPreferencesKey
@@ -35,6 +42,7 @@ import de.mr_pine.xkcdfeed.composables.settings.Theme
import de.mr_pine.xkcdfeed.composables.settings.settingsDataStore
import de.mr_pine.xkcdfeed.composables.single.SingleViewContentStateful
import de.mr_pine.xkcdfeed.ui.theme.XKCDFeedTheme
+import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@@ -52,13 +60,12 @@ class MainActivity : ComponentActivity() {
private lateinit var navController: NavHostController
- @OptIn(
- ExperimentalPagerApi::class,
- androidx.compose.ui.ExperimentalComposeUiApi::class,
- androidx.compose.foundation.ExperimentalFoundationApi::class,
- androidx.compose.material.ExperimentalMaterialApi::class,
- kotlinx.coroutines.ObsoleteCoroutinesApi::class
- )
+
+ @ExperimentalPagerApi
+ @ExperimentalComposeUiApi
+ @ExperimentalFoundationApi
+ @ExperimentalMaterialApi
+ @ObsoleteCoroutinesApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate: ${intent.data}")
@@ -91,6 +98,8 @@ class MainActivity : ComponentActivity() {
val singleComicViewModel: SingleComicViewModel = viewModel()
+ val scaffoldState = rememberScaffoldState()
+
val mainViewModel: MainViewModel = ViewModelProvider(
this,
MainViewModelFactory(
@@ -100,6 +109,7 @@ class MainActivity : ComponentActivity() {
navController::navigate,
this.baseContext,
loginViewModel,
+ scaffoldState.snackbarHostState,
singleComicViewModel::addToComicCache,
singleComicViewModel::setComicCacheImageLoaded
)
@@ -124,82 +134,88 @@ class MainActivity : ComponentActivity() {
}
) {
- val rootUri = "xkcd.com"
- NavHost(navController = navController, startDestination = "mainView") {
- composable(
- "mainView", deepLinks = listOf(
- navDeepLink { uriPattern = rootUri },
- navDeepLink { uriPattern = "$rootUri/" },
- navDeepLink { uriPattern = "www.$rootUri" },
- navDeepLink { uriPattern = "www.$rootUri/" },
- )
- ) {
- MainContent(mainViewModel) {
- navController.navigate("singleView/${it.id}")
- }
- lastDestination = "mainView"
- }
- composable("test") { Text("hello") }
- composable(
- route = "singleView/{number}",
- arguments = listOf(navArgument("number") { type = NavType.IntType }),
- deepLinks = listOf(
- navDeepLink { uriPattern = "$rootUri/{number}/" },
- navDeepLink { uriPattern = "$rootUri/{number}" },
- navDeepLink { uriPattern = "www.$rootUri/{number}/" },
- navDeepLink { uriPattern = "www.$rootUri/{number}" },
- )
- ) { backStackEntry ->
- val comicNumber = backStackEntry.arguments?.getInt("number")
-
- if (lastDestination != "singleView") singleComicViewModel.currentComic =
- comicNumber?.let { mainViewModel.loadComic(it) }
-
- SingleViewContentStateful(
- mainViewModel = mainViewModel,
- singleViewModel = singleComicViewModel,
- navigateHome = {
- Log.d(TAG, "onCreate: navigating home $backStackEntry, $navController")
- navController.navigateUp()
+ Scaffold(scaffoldState = scaffoldState) { paddingValues ->
+ val rootUri = "xkcd.com"
+ NavHost(navController = navController, startDestination = "mainView", modifier = Modifier.padding(paddingValues)) {
+ composable(
+ "mainView", deepLinks = listOf(
+ navDeepLink { uriPattern = rootUri },
+ navDeepLink { uriPattern = "$rootUri/" },
+ navDeepLink { uriPattern = "www.$rootUri" },
+ navDeepLink { uriPattern = "www.$rootUri/" },
+ )
+ ) {
+ MainContent(mainViewModel) {
+ navController.navigate("singleView/${it.id}")
}
- )
- lastDestination = "singleView"
- }
- composable(
- route = "singleView"
- ) {
- if (singleComicViewModel.currentComic == null) {
- singleComicViewModel.currentComic =
- mainViewModel.loadComic(mainViewModel.latestComicNumber)
+ lastDestination = "mainView"
}
- SingleViewContentStateful(
- mainViewModel = mainViewModel,
- singleViewModel = singleComicViewModel,
- navigateHome = navController::navigateUp
- )
-
- lastDestination = "singleView"
- }
- composable(route = "settings") {
- SettingsComposable(
- navigateBack = { navController.navigateUp() },
- loginViewModel = loginViewModel,
- mainViewModel = mainViewModel,
- context = this@MainActivity.baseContext,
- onLoginChanged = {
- scope.launch {
- mainViewModel.initFavoriteList(
- this@MainActivity,
- true,
- if (loginViewModel.signedIn) MainViewModel.ClearType.FIREBASE else MainViewModel.ClearType.LOCAL
+ composable("test") { Text("hello") }
+ composable(
+ route = "singleView/{number}",
+ arguments = listOf(navArgument("number") { type = NavType.IntType }),
+ deepLinks = listOf(
+ navDeepLink { uriPattern = "$rootUri/{number}/" },
+ navDeepLink { uriPattern = "$rootUri/{number}" },
+ navDeepLink { uriPattern = "www.$rootUri/{number}/" },
+ navDeepLink { uriPattern = "www.$rootUri/{number}" },
+ )
+ ) { backStackEntry ->
+ val comicNumber = backStackEntry.arguments?.getInt("number")
+
+ if (lastDestination != "singleView") singleComicViewModel.currentComic =
+ comicNumber?.let { mainViewModel.loadComic(it) }
+
+ SingleViewContentStateful(
+ mainViewModel = mainViewModel,
+ singleViewModel = singleComicViewModel,
+ navigateHome = {
+ Log.d(
+ TAG,
+ "onCreate: navigating home $backStackEntry, $navController"
)
+ navController.navigateUp()
}
+ )
+ lastDestination = "singleView"
+ }
+ composable(
+ route = "singleView"
+ ) {
+ if (singleComicViewModel.currentComic == null) {
+ singleComicViewModel.currentComic =
+ mainViewModel.loadComic(mainViewModel.latestComicNumber)
}
- )
+ SingleViewContentStateful(
+ mainViewModel = mainViewModel,
+ singleViewModel = singleComicViewModel,
+ navigateHome = navController::navigateUp
+ )
+
+ lastDestination = "singleView"
+ }
+ composable(route = "settings") {
+ SettingsComposable(
+ navigateBack = { navController.navigateUp() },
+ loginViewModel = loginViewModel,
+ mainViewModel = mainViewModel,
+ context = this@MainActivity.baseContext,
+ onLoginChanged = {
+ scope.launch {
+ mainViewModel.initFavoriteList(
+ this@MainActivity,
+ true,
+ if (loginViewModel.signedIn) MainViewModel.ClearType.FIREBASE else MainViewModel.ClearType.LOCAL
+ )
+ }
+ }
+ )
- lastDestination = "settings"
+ lastDestination = "settings"
+ }
}
}
+
}
}
}
diff --git a/app/src/main/java/de/mr_pine/xkcdfeed/MainViewModel.kt b/app/src/main/java/de/mr_pine/xkcdfeed/MainViewModel.kt
index 57c2095..404ca2f 100644
--- a/app/src/main/java/de/mr_pine/xkcdfeed/MainViewModel.kt
+++ b/app/src/main/java/de/mr_pine/xkcdfeed/MainViewModel.kt
@@ -4,9 +4,7 @@ import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.compose.foundation.lazy.LazyListState
-import androidx.compose.material.ExperimentalMaterialApi
-import androidx.compose.material.ModalBottomSheetState
-import androidx.compose.material.ModalBottomSheetValue
+import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
@@ -33,6 +31,7 @@ class MainViewModel(
val navigateTo: (String) -> Unit,
private val context: Context,
private val loginViewModel: LoginViewModel,
+ private val snackbarHostState: SnackbarHostState,
private val addToComicCache: (XKCDComic, Boolean) -> Unit,
private val setComicCacheImageLoaded: (Int, Boolean) -> Unit
) : ViewModel() {
@@ -48,30 +47,36 @@ class MainViewModel(
init { //implementation of http://www.graficaobscura.com/matrix/index.html
//RGB invert
- matrix = matrix.matrixMultiply(arrayOf(
- floatArrayOf(-1f, 0f, 0f, 0f),
- floatArrayOf(0f, -1f, 0f, 0f),
- floatArrayOf(0f, 0f, -1f, 0f),
- floatArrayOf(0f, 0f, 0f, -1f),
- ))
- matrix = matrix.matrixAdd(arrayOf(
- floatArrayOf(0f, 0f, 0f, 0f),
- floatArrayOf(0f, 0f, 0f, 0f),
- floatArrayOf(0f, 0f, 0f, 0f),
- floatArrayOf(255f, 255f, 255f, 0f),
- ))
+ matrix = matrix.matrixMultiply(
+ arrayOf(
+ floatArrayOf(-1f, 0f, 0f, 0f),
+ floatArrayOf(0f, -1f, 0f, 0f),
+ floatArrayOf(0f, 0f, -1f, 0f),
+ floatArrayOf(0f, 0f, 0f, -1f),
+ )
+ )
+ matrix = matrix.matrixAdd(
+ arrayOf(
+ floatArrayOf(0f, 0f, 0f, 0f),
+ floatArrayOf(0f, 0f, 0f, 0f),
+ floatArrayOf(0f, 0f, 0f, 0f),
+ floatArrayOf(255f, 255f, 255f, 0f),
+ )
+ )
//HSV 180 rotation
- matrix = matrix.matrixMultiply(xRotation(cos = 1/ sqrt(2f), sin = 1/ sqrt(2f)))
- matrix = matrix.matrixMultiply(yRotation(cos = sqrt(2/3f), sin = -sqrt(1/3f)))
- val transformedWeights = arrayOf(floatArrayOf(wR, wG, wB)).matrixMultiply(matrix.cutTo(3,3)).matrixMultiply(matrix.cutTo(3,3))
- val shearX = (transformedWeights[0][0]/transformedWeights[0][2])
- val shearY = (transformedWeights[0][1]/transformedWeights[0][2])
+ matrix = matrix.matrixMultiply(xRotation(cos = 1 / sqrt(2f), sin = 1 / sqrt(2f)))
+ matrix = matrix.matrixMultiply(yRotation(cos = sqrt(2 / 3f), sin = -sqrt(1 / 3f)))
+ val transformedWeights =
+ arrayOf(floatArrayOf(wR, wG, wB)).matrixMultiply(matrix.cutTo(3, 3))
+ .matrixMultiply(matrix.cutTo(3, 3))
+ val shearX = (transformedWeights[0][0] / transformedWeights[0][2])
+ val shearY = (transformedWeights[0][1] / transformedWeights[0][2])
matrix = matrix.matrixMultiply(shearZ(shearX, shearY))
matrix = matrix.matrixMultiply(zRotation(PI.toFloat()))
matrix = matrix.matrixMultiply(shearZ(-shearX, -shearY))
- matrix = matrix.matrixMultiply(yRotation(cos = sqrt(2/3f), sin = sqrt(1/3f)))
- matrix = matrix.matrixMultiply(xRotation(cos = 1/ sqrt(2f), sin = -1/ sqrt(2f)))
+ matrix = matrix.matrixMultiply(yRotation(cos = sqrt(2 / 3f), sin = sqrt(1 / 3f)))
+ matrix = matrix.matrixMultiply(xRotation(cos = 1 / sqrt(2f), sin = -1 / sqrt(2f)))
}
@@ -106,13 +111,14 @@ class MainViewModel(
var favoriteListInitialized = false
var lastClearType = ClearType.UNDEFINED
var favoriteList = mutableStateListOf()
+ var hideFavoritesList = mutableStateListOf()
var cacheList = ConcurrentLinkedQueue()
fun loadComic(id: Int): XKCDComic {
val match = cacheList.indexOfFirst { it.id == id }
return if (match == -1) {
- val newComic = XKCDComic(id, viewModelScope, context) {}
+ val newComic = XKCDComic(id, viewModelScope, context)
cacheList.add(newComic)
newComic
} else {
@@ -131,14 +137,14 @@ class MainViewModel(
if (clearType != null) lastClearType = clearType
viewModelScope.launch {
if (!loginViewModel.signedIn) {
- userDataStore.data.first { preferences ->
- // No type safety.
- Log.d(TAG, "initFavoriteList: hi :)")
- val stringList = preferences[favoriteListKey] ?: "[]"
- favoriteList.addAll(generateListFromJSON(stringList))
- addFromFavoritesList(context, clear)
- false
- }
+ val preferences = userDataStore.data.first()
+
+ // No type safety.
+ Log.d(TAG, "initFavoriteList: hi :)")
+ val stringList = preferences[favoriteListKey] ?: "[]"
+ favoriteList.addAll(generateListFromJSON(stringList))
+ addFromFavoritesList(context, clear)
+
} else {
val favoritesReference =
db.collection("Users/${loginViewModel.user?.uid}/Favorites")
@@ -230,9 +236,17 @@ class MainViewModel(
}
fun removeFavorite(xkcdComic: XKCDComic) {
+ hideFavoritesList.add(xkcdComic.id)
viewModelScope.launch {
- removeFromFavoriteList(xkcdComic.id)
- removeFromFavoriteComicList(xkcdComic)
+ if (snackbarHostState.showSnackbar(
+ "Comic removed from favorites",
+ "UNDO"
+ ) != SnackbarResult.ActionPerformed
+ ) {
+ removeFromFavoriteList(xkcdComic.id)
+ removeFromFavoriteComicList(xkcdComic)
+ }
+ hideFavoritesList.remove(xkcdComic.id)
}
}
//
@@ -287,22 +301,6 @@ class MainViewModel(
} else {
addToFavoriteComicList(comic)
}
- /*XKCDComic.getComic(
- number = number,
- coroutineScope = viewModelScope,
- context = context,
- onImageLoaded = {
- (if (to == Tab.LATEST) latestImagesLoadedMap else favoriteImagesLoadedMap)[number] =
- true
- setComicCacheImageLoaded(number, true)
- }) {
- if (to == Tab.LATEST) {
- addToLatestComicList(it)
- } else {
- addToFavoriteComicList(it)
- }
- addToComicCache(it, false)
- }*/
}
//
@@ -344,6 +342,7 @@ class MainViewModelFactory(
private val navigateTo: (String) -> Unit,
private val context: Context,
private val loginViewModel: LoginViewModel,
+ private val snackbarHostState: SnackbarHostState,
private val addToComicCache: (XKCDComic, Boolean) -> Unit,
private val setComicCacheImageLoaded: (Int, Boolean) -> Unit
) : ViewModelProvider.Factory {
@@ -358,6 +357,7 @@ class MainViewModelFactory(
navigateTo,
context,
loginViewModel,
+ snackbarHostState,
addToComicCache,
setComicCacheImageLoaded
) as T
diff --git a/app/src/main/java/de/mr_pine/xkcdfeed/XKCDComic.kt b/app/src/main/java/de/mr_pine/xkcdfeed/XKCDComic.kt
index b7a7d14..f544abb 100644
--- a/app/src/main/java/de/mr_pine/xkcdfeed/XKCDComic.kt
+++ b/app/src/main/java/de/mr_pine/xkcdfeed/XKCDComic.kt
@@ -26,16 +26,13 @@ private const val TAG = "XKCDComic"
class XKCDComic(
val id: Int,
- private val coroutineScope: CoroutineScope,
- context: Context,
- onImageLoaded: () -> Unit
+ coroutineScope: CoroutineScope,
+ context: Context
) {
var title: String? by mutableStateOf(null)
var pubDate: Calendar? by mutableStateOf(null)
var imageURL by mutableStateOf("")
var description: String? by mutableStateOf(null)
- var bitmapLight: Bitmap? by mutableStateOf(null)
- var bitmapDark: Bitmap? by mutableStateOf(null)
private val TAG = "XKCDComic"
diff --git a/app/src/main/java/de/mr_pine/xkcdfeed/composables/main/Card.kt b/app/src/main/java/de/mr_pine/xkcdfeed/composables/main/Card.kt
index 0b15eff..d744184 100644
--- a/app/src/main/java/de/mr_pine/xkcdfeed/composables/main/Card.kt
+++ b/app/src/main/java/de/mr_pine/xkcdfeed/composables/main/Card.kt
@@ -1,5 +1,8 @@
package de.mr_pine.xkcdfeed.composables.main
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.expandVertically
+import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
@@ -30,6 +33,7 @@ import coil.size.Size
import com.google.accompanist.placeholder.PlaceholderHighlight
import com.google.accompanist.placeholder.material.placeholder
import com.google.accompanist.placeholder.material.shimmer
+import de.mr_pine.xkcdfeed.MainViewModel
import de.mr_pine.xkcdfeed.XKCDComic
import de.mr_pine.xkcdfeed.toColorMatrix
import de.mr_pine.xkcdfeed.ui.theme.Amber500
@@ -70,12 +74,13 @@ val matrix = ColorMatrix(floatArrayOf(
0f, 0f, 0f, 1f, 0f
))*/
-@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
+@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ComicCard(
xkcdComic: XKCDComic,
dateFormat: DateFormat,
favoriteList: List,
+ tab: MainViewModel.Tab,
setFavorite: (XKCDComic) -> Unit,
removeFavorite: (XKCDComic) -> Unit,
invertMatrix: Array,
@@ -85,100 +90,102 @@ fun ComicCard(
val colorFilter = ColorFilter.colorMatrix(ColorMatrix(invertMatrix.toColorMatrix()))
val scope = rememberCoroutineScope()
- Card(
- elevation = 5.dp,
- backgroundColor = if (MaterialTheme.colors.isLight) Color.White else Color.Black,
- shape = MaterialTheme.shapes.medium,
- modifier = Modifier
- .fillMaxWidth()
- .clip(MaterialTheme.shapes.medium)
- .combinedClickable(
- onLongClick = { scope.launch { onLongPress(xkcdComic) } },
- onClick = { showSingle(xkcdComic) }
- )
- ) {
- MaterialTheme.colors.primarySurface
- Column(modifier = Modifier.padding(top = 4.dp, start = 8.dp, end = 8.dp, bottom = 8.dp)) {
- Row(verticalAlignment = Alignment.CenterVertically) {
- Row(verticalAlignment = Alignment.Bottom, modifier = Modifier.padding(top = 3.dp)) {
- Text(
- text = xkcdComic.title ?: "I am a title :)",
- fontWeight = FontWeight.Bold,
- modifier = Modifier.placeholder(xkcdComic.title == null)
- )
- Text(
- text = "(${xkcdComic.id})",
- modifier = Modifier
- .padding(start = 4.dp, bottom = 1.5.dp)
- .placeholder(xkcdComic.title == null),
- fontStyle = FontStyle.Italic,
- style = MaterialTheme.typography.caption
- )
- }
+ AnimatedVisibility(visible = tab == MainViewModel.Tab.LATEST || favoriteList.contains(xkcdComic.id), enter = expandVertically(), exit = shrinkVertically()) {
+ Card(
+ elevation = 5.dp,
+ backgroundColor = if (MaterialTheme.colors.isLight) Color.White else Color.Black,
+ shape = MaterialTheme.shapes.medium,
+ modifier = Modifier
+ .fillMaxWidth()
+ .clip(MaterialTheme.shapes.medium)
+ .combinedClickable(
+ onLongClick = { scope.launch { onLongPress(xkcdComic) } },
+ onClick = { showSingle(xkcdComic) }
+ )
+ ) {
+ MaterialTheme.colors.primarySurface
+ Column(modifier = Modifier.padding(top = 4.dp, start = 8.dp, end = 8.dp, bottom = 8.dp)) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Row(verticalAlignment = Alignment.Bottom, modifier = Modifier.padding(top = 3.dp)) {
+ Text(
+ text = xkcdComic.title ?: "I am a title :)",
+ fontWeight = FontWeight.Bold,
+ modifier = Modifier.placeholder(xkcdComic.title == null)
+ )
+ Text(
+ text = "(${xkcdComic.id})",
+ modifier = Modifier
+ .padding(start = 4.dp, bottom = 1.5.dp)
+ .placeholder(xkcdComic.title == null),
+ fontStyle = FontStyle.Italic,
+ style = MaterialTheme.typography.caption
+ )
+ }
- Column(
- horizontalAlignment = Alignment.End,
- modifier = Modifier.fillMaxWidth()
- ) {
- var icon = Icons.Outlined.StarOutline
- var tint = Gray400
+ Column(
+ horizontalAlignment = Alignment.End,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ var icon = Icons.Outlined.StarOutline
+ var tint = Gray400
- if (favoriteList.contains(xkcdComic.id)) {
- icon = Icons.Filled.Star
- tint = Amber500
+ if (favoriteList.contains(xkcdComic.id)) {
+ icon = Icons.Filled.Star
+ tint = Amber500
+ }
+ Icon(
+ icon,
+ "Star",
+ tint = tint,
+ modifier = Modifier.clip(MaterialTheme.shapes.small).clickable {
+ (if (!favoriteList.contains(xkcdComic.id)) setFavorite else removeFavorite)(
+ xkcdComic
+ )
+ })
}
- Icon(
- icon,
- "Star",
- tint = tint,
- modifier = Modifier.clickable {
- (if (!favoriteList.contains(xkcdComic.id)) setFavorite else removeFavorite)(
- xkcdComic
- )
- })
}
- }
- Text(
- text = xkcdComic.pubDate.let { if (it != null) dateFormat.format(it.time) else "00/00/0000" },
- fontStyle = FontStyle.Italic,
- fontSize = 12.sp,
- modifier = Modifier
- .padding(bottom = 8.dp)
- .placeholder(xkcdComic.pubDate == null)
- )
- val painter = rememberAsyncImagePainter(
- model = ImageRequest.Builder(LocalContext.current)
- .data(xkcdComic.imageURL)
- .size(Size.ORIGINAL) // Set the target size to load the image at.
- .build()
- )
-
- Image(
- painter = painter,
- //bitmap = bitmap.asImageBitmap(),
- contentDescription = "Image of the comic",
- contentScale = ContentScale.Crop,
- modifier = Modifier
- .fillMaxWidth()
- .then(
- if (painter.state is AsyncImagePainter.State.Success) Modifier else Modifier.sizeIn(
- minHeight = 200.dp
+ Text(
+ text = xkcdComic.pubDate.let { if (it != null) dateFormat.format(it.time) else "00/00/0000" },
+ fontStyle = FontStyle.Italic,
+ fontSize = 12.sp,
+ modifier = Modifier
+ .padding(bottom = 8.dp)
+ .placeholder(xkcdComic.pubDate == null)
+ )
+ val painter = rememberAsyncImagePainter(
+ model = ImageRequest.Builder(LocalContext.current)
+ .data(xkcdComic.imageURL)
+ .size(Size.ORIGINAL) // Set the target size to load the image at.
+ .build()
+ )
+
+ Image(
+ painter = painter,
+ //bitmap = bitmap.asImageBitmap(),
+ contentDescription = "Image of the comic",
+ contentScale = ContentScale.Crop,
+ modifier = Modifier
+ .fillMaxWidth()
+ .then(
+ if (painter.state is AsyncImagePainter.State.Success) Modifier else Modifier.sizeIn(
+ minHeight = 200.dp
+ )
)
- )
- .placeholder(
- painter.state !is AsyncImagePainter.State.Success,
- highlight = PlaceholderHighlight.shimmer()
- ),
- colorFilter = if (MaterialTheme.colors.isLight) null else colorFilter
- )
- Text(
- text = xkcdComic.description
- ?: "I am a description text. If you see me, something isn't working as intended. Written at 2:36 a.m.",
- style = MaterialTheme.typography.caption,
- modifier = Modifier
- .padding(top = 8.dp)
- .placeholder(xkcdComic.description == null)
- )
+ .placeholder(
+ painter.state !is AsyncImagePainter.State.Success,
+ highlight = PlaceholderHighlight.shimmer()
+ ),
+ colorFilter = if (MaterialTheme.colors.isLight) null else colorFilter
+ )
+ Text(
+ text = xkcdComic.description
+ ?: "I am a description text. If you see me, something isn't working as intended. Written at 2:36 a.m.",
+ style = MaterialTheme.typography.caption,
+ modifier = Modifier
+ .padding(top = 8.dp)
+ .placeholder(xkcdComic.description == null)
+ )
+ }
}
}
}
diff --git a/app/src/main/java/de/mr_pine/xkcdfeed/composables/main/MainComposables.kt b/app/src/main/java/de/mr_pine/xkcdfeed/composables/main/MainComposables.kt
index 534bf77..fa0944e 100644
--- a/app/src/main/java/de/mr_pine/xkcdfeed/composables/main/MainComposables.kt
+++ b/app/src/main/java/de/mr_pine/xkcdfeed/composables/main/MainComposables.kt
@@ -167,8 +167,8 @@ fun sheetContent(
@Composable
fun MainScaffold(viewModel: MainViewModel, showSingleComic: (XKCDComic) -> Unit) {
- Scaffold(topBar = { TopAppBar { viewModel.navigateTo(it) } }) {
- TabbedContent(viewModel, showSingleComic)
+ Scaffold(topBar = { TopAppBar { viewModel.navigateTo(it) } }) { paddingValues ->
+ TabbedContent(viewModel, showSingleComic, paddingValues)
}
}
@@ -193,12 +193,12 @@ fun TopAppBar(navigate: (String) -> Unit) {
//
@ExperimentalPagerApi
@Composable
-fun TabbedContent(viewModel: MainViewModel, showSingleComic: (XKCDComic) -> Unit) {
+fun TabbedContent(viewModel: MainViewModel, showSingleComic: (XKCDComic) -> Unit, paddingValues: PaddingValues) {
val tabPagerState = rememberPagerState(0)
val scope = rememberCoroutineScope()
- Column() {
+ Column(modifier = Modifier.padding(paddingValues)) {
TabRow(
selectedTabIndex = tabPagerState.currentPage,
indicator = { tabPositions ->
@@ -271,11 +271,13 @@ fun Tab1(viewModel: MainViewModel, showSingleComic: (XKCDComic) -> Unit) {
}) {
Icon(Icons.Default.History, "History")
}
- }) {
+ }) { paddingValues ->
ComicList(
list = viewModel.latestComicsList,
+ currentTab = MainViewModel.Tab.LATEST,
viewModel = viewModel,
- showSingleComic = showSingleComic
+ showSingleComic = showSingleComic,
+ paddingValues = paddingValues
)
}
@@ -293,12 +295,14 @@ fun Tab2(viewModel: MainViewModel, scope: CoroutineScope, showSingleComic: (XKCD
}) {
Icon(Icons.Default.Shuffle, "Shuffle")
}
- }) {
+ }) { paddingValues ->
ComicList(
list = viewModel.favoriteComicsList,
+ currentTab = MainViewModel.Tab.FAVORITES,
viewModel = viewModel,
showSingleComic = showSingleComic,
- state = viewModel.favListState
+ state = viewModel.favListState,
+ paddingValues = paddingValues
)
}
}
@@ -308,11 +312,14 @@ fun Tab2(viewModel: MainViewModel, scope: CoroutineScope, showSingleComic: (XKCD
fun ComicList(
list: List,
viewModel: MainViewModel,
+ currentTab: MainViewModel.Tab,
showSingleComic: (XKCDComic) -> Unit,
- state: LazyListState? = null
+ state: LazyListState? = null,
+ paddingValues: PaddingValues
) {
LazyColumn(
Modifier
+ .padding(paddingValues)
.fillMaxSize()
.background(MaterialTheme.colors.surface),
contentPadding = PaddingValues(8.dp),
@@ -329,7 +336,8 @@ fun ComicList(
ComicCard(
item,
viewModel.dateFormat,
- viewModel.favoriteList,
+ viewModel.favoriteList - viewModel.hideFavoritesList,
+ currentTab,
viewModel::addFavorite,
viewModel::removeFavorite,
viewModel.matrix,
diff --git a/app/src/main/java/de/mr_pine/xkcdfeed/composables/single/Single.kt b/app/src/main/java/de/mr_pine/xkcdfeed/composables/single/Single.kt
index e05567b..c0632c8 100644
--- a/app/src/main/java/de/mr_pine/xkcdfeed/composables/single/Single.kt
+++ b/app/src/main/java/de/mr_pine/xkcdfeed/composables/single/Single.kt
@@ -221,221 +221,6 @@ fun SingleViewContent(
}
}
-/*@ExperimentalFoundationApi
-@Composable
-fun ZoomableImage(
- bitmap: ImageBitmap,
- onSwipeLeft: (Int) -> Unit,
- onSwipeRight: (Int) -> Unit,
- getCurrentNumber: () -> Int
-) {
- val scope = rememberCoroutineScope()
-
- var scale by remember { mutableStateOf(1f) }
- var rotation by remember { mutableStateOf(0f) }
- var offset by remember { mutableStateOf(Offset.Zero) }
- val state = rememberTransformableState { zoomChange, offsetChange, rotationChange ->
- scale *= zoomChange
- rotation += rotationChange
- offset += offsetChange
- }
-
- var dragOffset by remember { mutableStateOf(Offset.Zero) }
- var imageCenter by remember { mutableStateOf(Offset.Zero) }
- var transformOffset by remember { mutableStateOf(Offset.Zero) }
-
- fun onTransformGesture(
- centroid: Offset,
- pan: Offset,
- zoom: Float,
- transformRotation: Float
- ) {
- offset += pan
- scale *= zoom
- rotation += transformRotation
-
- val x0 = centroid.x - imageCenter.x
- val y0 = centroid.y - imageCenter.y
-
- val hyp0 = sqrt(x0 * x0 + y0 * y0)
- val hyp1 = zoom * hyp0 * (if (x0 > 0) {
- 1f
- } else {
- -1f
- })
-
- val alpha0 = atan(y0 / x0)
-
- val alpha1 = alpha0 + (transformRotation * ((2 * PI) / 360))
-
- val x1 = cos(alpha1) * hyp1
- val y1 = sin(alpha1) * hyp1
-
- transformOffset =
- centroid - (imageCenter - offset) - Offset(x1.toFloat(), y1.toFloat())
- offset = transformOffset
- }
-
- Box(
- Modifier
- .pointerInput(Unit) {
- detectTapGestures(
- onDoubleTap = {
- if (scale != 1f) {
- scope.launch {
- state.animateZoomBy(1 / scale)
- }
- offset = Offset.Zero
- rotation = 0f
- } else {
- scope.launch {
- state.animateZoomBy(2f)
- }
- }
- }
- )
- }
- .pointerInput(Unit) {
- val panZoomLock = true
- forEachGesture {
- awaitPointerEventScope {
- var transformRotation = 0f
- var zoom = 1f
- var pan = Offset.Zero
- var pastTouchSlop = false
- val touchSlop = viewConfiguration.touchSlop
- var lockedToPanZoom = false
- var drag: PointerInputChange?
- var overSlop = Offset.Zero
-
- val down = awaitFirstDown(requireUnconsumed = false)
-
-
- var transformEventCounter = 0
- do {
- val event = awaitPointerEvent()
- val canceled = event.changes.fastAny { it.positionChangeConsumed() }
- var relevant = true
- if (event.changes.size > 1) {
- if (!canceled) {
- val zoomChange = event.calculateZoom()
- val rotationChange = event.calculateRotation()
- val panChange = event.calculatePan()
-
- if (!pastTouchSlop) {
- zoom *= zoomChange
- transformRotation += rotationChange
- pan += panChange
-
- val centroidSize =
- event.calculateCentroidSize(useCurrent = false)
- val zoomMotion = abs(1 - zoom) * centroidSize
- val rotationMotion =
- abs(transformRotation * PI.toFloat() * centroidSize / 180f)
- val panMotion = pan.getDistance()
-
- if (zoomMotion > touchSlop ||
- rotationMotion > touchSlop ||
- panMotion > touchSlop
- ) {
- pastTouchSlop = true
- lockedToPanZoom =
- panZoomLock && rotationMotion < touchSlop
- }
- }
-
- if (pastTouchSlop) {
- val eventCentroid =
- event.calculateCentroid(useCurrent = false)
- val effectiveRotation =
- if (lockedToPanZoom) 0f else rotationChange
- if (effectiveRotation != 0f ||
- zoomChange != 1f ||
- panChange != Offset.Zero
- ) {
- onTransformGesture(
- eventCentroid,
- panChange,
- zoomChange,
- effectiveRotation
- )
- }
- event.changes.fastForEach {
- if (it.positionChanged()) {
- it.consumeAllChanges()
- }
- }
- }
- }
- } else if (transformEventCounter > 3) relevant = false
- transformEventCounter++
- } while (!canceled && event.changes.fastAny { it.pressed } && relevant)
-
- do {
- val event = awaitPointerEvent()
- drag = awaitTouchSlopOrCancellation(down.id) { change, over ->
- change.consumePositionChange()
- overSlop = over
- }
- } while (drag != null && !drag.positionChangeConsumed())
- if (drag != null) {
- dragOffset = Offset.Zero
- if (scale !in 0.92f..1.08f) {
- offset += overSlop
- } else {
- dragOffset += overSlop
- }
- if (drag(drag.id) {
- if (scale !in 0.92f..1.08f) {
- offset += it.positionChange()
- } else {
- dragOffset += it.positionChange()
- }
- it.consumePositionChange()
- }
- ) {
- if (scale in 0.92f..1.08f) {
- val offsetX = dragOffset.x
- if (offsetX > 300) {
- onSwipeRight(getCurrentNumber())
-
- } else if (offsetX < -300) {
- onSwipeLeft(getCurrentNumber())
- }
- }
- }
- }
- }
- }
- }
- ) {
- Image(
- bitmap = bitmap,
- contentDescription = "Comic Image",
- modifier = Modifier
- .fillMaxSize()
- .clip(RectangleShape)
- .offset { IntOffset(offset.x.roundToInt(), offset.y.roundToInt()) }
- .graphicsLayer(
- scaleX = scale - 0.02f,
- scaleY = scale - 0.02f,
- rotationZ = rotation
- )
- .onGloballyPositioned { coordinates ->
- val localOffset =
- Offset(
- coordinates.size.width.toFloat() / 2,
- coordinates.size.height.toFloat() / 2
- )
- val windowOffset = coordinates.localToWindow(localOffset)
- imageCenter = coordinates.parentLayoutCoordinates?.windowToLocal(windowOffset)
- ?: Offset.Zero
- },
- contentScale = ContentScale.Fit
- )
- }
-}*/
-
@Composable
fun bottomSheetContent(
@@ -515,7 +300,7 @@ fun bottomSheetContent(
icon,
"Star",
tint = tint,
- modifier = Modifier.clickable {
+ modifier = Modifier.clip(MaterialTheme.shapes.small).clickable {
(if (!isFavorite) setFavorite else removeFavorite)(
comic
)
@@ -591,7 +376,7 @@ fun SingleViewContentStateful(
if (currentComic != null) {
SingleViewContent(
comic = currentComic,
- isFavorite = favoriteList.contains(currentComic.id),
+ isFavorite = favoriteList.contains(currentComic.id) && !mainViewModel.hideFavoritesList.contains(currentComic.id),
dateFormat = mainViewModel.dateFormat,
setFavorite = mainViewModel::addFavorite,
removeFavorite = mainViewModel::removeFavorite,
diff --git a/build.gradle b/build.gradle
index 9958ac9..3c69164 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,17 +1,17 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
- compose_version = '1.1.1'
- accompanist_version = "0.23.1"
+ compose_version = '1.2.1'
+ accompanist_version = "0.25.1"
}
repositories {
google()
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:7.1.2'
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10"
- classpath 'com.google.gms:google-services:4.3.10'
+ classpath 'com.android.tools.build:gradle:7.2.2'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10"
+ classpath 'com.google.gms:google-services:4.3.13'
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index cb5889b..227b9b2 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Mon Sep 27 22:34:46 CEST 2021
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME