Change-Id: I60125e6152c2e8e4fc89d0f547521f283259f9de # Conflicts: # build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidCompose.ktpull/1576/head
@ -1,25 +0,0 @@
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "gradle"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
registries: "*"
|
||||
labels: [ "version update" ]
|
||||
groups:
|
||||
kotlin-ksp:
|
||||
patterns:
|
||||
- "org.jetbrains.kotlin:*"
|
||||
- "org.jetbrains.kotlin.jvm"
|
||||
- "com.google.devtools.ksp"
|
||||
open-pull-requests-limit: 10
|
||||
registries:
|
||||
maven-google:
|
||||
type: "maven-repository"
|
||||
url: "https://maven.google.com"
|
||||
replaces-base: true
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"local>android/.github:renovate-config"
|
||||
],
|
||||
"baseBranches": [
|
||||
"main"
|
||||
]
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
name: NightlyBaselineProfiles
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '42 4 * * *'
|
||||
|
||||
jobs:
|
||||
baseline_profiles:
|
||||
name: "Generate Baseline Profiles"
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Enable KVM group perms
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
ls /dev/kvm
|
||||
|
||||
- name: Copy CI gradle.properties
|
||||
run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: 17
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Accept licenses
|
||||
run: yes | sdkmanager --licenses || true
|
||||
|
||||
- name: Check build-logic
|
||||
run: ./gradlew check -p build-logic
|
||||
|
||||
- name: Setup GMD
|
||||
run: ./gradlew :benchmarks:pixel6Api33Setup
|
||||
--info
|
||||
-Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true
|
||||
-Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect"
|
||||
|
||||
# This generates both baseline and startup profile and adds them into the generated folder
|
||||
- name: Generate Baseline Profile
|
||||
run: ./gradlew :app:generateReleaseBaselineProfile
|
||||
-Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=baselineprofile
|
||||
-Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect"
|
||||
--stacktrace
|
@ -1,2 +1,2 @@
|
||||
# This file can be used to trigger an internal build by changing the number below
|
||||
3
|
||||
2
|
||||
|
@ -1,9 +0,0 @@
|
||||
# Fix for Retrofit issue https://github.com/square/retrofit/issues/3751
|
||||
# Keep generic signature of Call, Response (R8 full mode strips signatures from non-kept items).
|
||||
-keep,allowobfuscation,allowshrinking interface retrofit2.Call
|
||||
-keep,allowobfuscation,allowshrinking class retrofit2.Response
|
||||
|
||||
# With R8 full mode generic signatures are stripped for classes that are not
|
||||
# kept. Suspend functions are wrapped in continuations where the type argument
|
||||
# is used.
|
||||
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2024 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.util
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.core.util.Consumer
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
|
||||
/**
|
||||
* Convenience wrapper for dark mode checking
|
||||
*/
|
||||
val Configuration.isSystemInDarkTheme
|
||||
get() = (uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
|
||||
|
||||
/**
|
||||
* Registers listener for configuration changes to retrieve whether system is in dark theme or not.
|
||||
* Immediately upon subscribing, it sends the current value and then registers listener for changes.
|
||||
*/
|
||||
fun ComponentActivity.isSystemInDarkTheme() = callbackFlow {
|
||||
channel.trySend(resources.configuration.isSystemInDarkTheme)
|
||||
|
||||
val listener = Consumer<Configuration> {
|
||||
channel.trySend(it.isSystemInDarkTheme)
|
||||
}
|
||||
|
||||
addOnConfigurationChangedListener(listener)
|
||||
|
||||
awaitClose { removeOnConfigurationChangedListener(listener) }
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.conflate()
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 126 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 260 KiB After Width: | Height: | Size: 276 KiB |
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 141 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 198 KiB |
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 80 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 185 KiB |
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 211 KiB After Width: | Height: | Size: 216 KiB |
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 97 KiB |
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2024 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
|
||||
|
||||
import androidx.benchmark.macro.ExperimentalMetricApi
|
||||
import androidx.benchmark.macro.StartupTimingMetric
|
||||
import androidx.benchmark.macro.TraceSectionMetric
|
||||
|
||||
/**
|
||||
* Custom Metrics to measure baseline profile effectiveness.
|
||||
*/
|
||||
class BaselineProfileMetrics {
|
||||
companion object {
|
||||
/**
|
||||
* A [TraceSectionMetric] that tracks the time spent in JIT compilation.
|
||||
*
|
||||
* This number should go down when a baseline profile is applied properly.
|
||||
*/
|
||||
@OptIn(ExperimentalMetricApi::class)
|
||||
val jitCompilationMetric = TraceSectionMetric("JIT Compiling %", label = "JIT compilation")
|
||||
|
||||
/**
|
||||
* A [TraceSectionMetric] that tracks the time spent in class initialization.
|
||||
*
|
||||
* This number should go down when a baseline profile is applied properly.
|
||||
*/
|
||||
@OptIn(ExperimentalMetricApi::class)
|
||||
val classInitMetric = TraceSectionMetric("L%/%;", label = "ClassInit")
|
||||
|
||||
/**
|
||||
* Metrics relevant to startup and baseline profile effectiveness measurement.
|
||||
*/
|
||||
@OptIn(ExperimentalMetricApi::class)
|
||||
val allMetrics = listOf(StartupTimingMetric(), jitCompilationMetric, classInitMetric)
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
/*
|
||||
* 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.core.data.model
|
||||
|
||||
import com.google.samples.apps.nowinandroid.core.network.model.NetworkNewsResource
|
||||
import com.google.samples.apps.nowinandroid.core.network.model.NetworkNewsResourceExpanded
|
||||
import com.google.samples.apps.nowinandroid.core.network.model.NetworkTopic
|
||||
import kotlinx.datetime.Instant
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class NetworkEntityKtTest {
|
||||
|
||||
@Test
|
||||
fun network_topic_can_be_mapped_to_topic_entity() {
|
||||
val networkModel = NetworkTopic(
|
||||
id = "0",
|
||||
name = "Test",
|
||||
shortDescription = "short description",
|
||||
longDescription = "long description",
|
||||
url = "URL",
|
||||
imageUrl = "image URL",
|
||||
)
|
||||
val entity = networkModel.asEntity()
|
||||
|
||||
assertEquals("0", entity.id)
|
||||
assertEquals("Test", entity.name)
|
||||
assertEquals("short description", entity.shortDescription)
|
||||
assertEquals("long description", entity.longDescription)
|
||||
assertEquals("URL", entity.url)
|
||||
assertEquals("image URL", entity.imageUrl)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun network_news_resource_can_be_mapped_to_news_resource_entity() {
|
||||
val networkModel =
|
||||
NetworkNewsResource(
|
||||
id = "0",
|
||||
title = "title",
|
||||
content = "content",
|
||||
url = "url",
|
||||
headerImageUrl = "headerImageUrl",
|
||||
publishDate = Instant.fromEpochMilliseconds(1),
|
||||
type = "Article 📚",
|
||||
)
|
||||
val entity = networkModel.asEntity()
|
||||
|
||||
assertEquals("0", entity.id)
|
||||
assertEquals("title", entity.title)
|
||||
assertEquals("content", entity.content)
|
||||
assertEquals("url", entity.url)
|
||||
assertEquals("headerImageUrl", entity.headerImageUrl)
|
||||
assertEquals(Instant.fromEpochMilliseconds(1), entity.publishDate)
|
||||
assertEquals("Article 📚", entity.type)
|
||||
|
||||
val expandedNetworkModel =
|
||||
NetworkNewsResourceExpanded(
|
||||
id = "0",
|
||||
title = "title",
|
||||
content = "content",
|
||||
url = "url",
|
||||
headerImageUrl = "headerImageUrl",
|
||||
publishDate = Instant.fromEpochMilliseconds(1),
|
||||
type = "Article 📚",
|
||||
)
|
||||
|
||||
val entityFromExpanded = expandedNetworkModel.asEntity()
|
||||
|
||||
assertEquals("0", entityFromExpanded.id)
|
||||
assertEquals("title", entityFromExpanded.title)
|
||||
assertEquals("content", entityFromExpanded.content)
|
||||
assertEquals("url", entityFromExpanded.url)
|
||||
assertEquals("headerImageUrl", entityFromExpanded.headerImageUrl)
|
||||
assertEquals(Instant.fromEpochMilliseconds(1), entityFromExpanded.publishDate)
|
||||
assertEquals("Article 📚", entityFromExpanded.type)
|
||||
}
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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.core.data.model
|
||||
|
||||
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
|
||||
import com.google.samples.apps.nowinandroid.core.model.data.Topic
|
||||
import com.google.samples.apps.nowinandroid.core.network.model.NetworkNewsResource
|
||||
import com.google.samples.apps.nowinandroid.core.network.model.NetworkTopic
|
||||
import com.google.samples.apps.nowinandroid.core.network.model.asExternalModel
|
||||
import kotlinx.datetime.Instant
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class NetworkEntityTest {
|
||||
|
||||
@Test
|
||||
fun networkTopicMapsToDatabaseModel() {
|
||||
val networkModel = NetworkTopic(
|
||||
id = "0",
|
||||
name = "Test",
|
||||
shortDescription = "short description",
|
||||
longDescription = "long description",
|
||||
url = "URL",
|
||||
imageUrl = "image URL",
|
||||
)
|
||||
val entity = networkModel.asEntity()
|
||||
|
||||
assertEquals("0", entity.id)
|
||||
assertEquals("Test", entity.name)
|
||||
assertEquals("short description", entity.shortDescription)
|
||||
assertEquals("long description", entity.longDescription)
|
||||
assertEquals("URL", entity.url)
|
||||
assertEquals("image URL", entity.imageUrl)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun networkNewsResourceMapsToDatabaseModel() {
|
||||
val networkModel =
|
||||
NetworkNewsResource(
|
||||
id = "0",
|
||||
title = "title",
|
||||
content = "content",
|
||||
url = "url",
|
||||
headerImageUrl = "headerImageUrl",
|
||||
publishDate = Instant.fromEpochMilliseconds(1),
|
||||
type = "Article 📚",
|
||||
)
|
||||
val entity = networkModel.asEntity()
|
||||
|
||||
assertEquals("0", entity.id)
|
||||
assertEquals("title", entity.title)
|
||||
assertEquals("content", entity.content)
|
||||
assertEquals("url", entity.url)
|
||||
assertEquals("headerImageUrl", entity.headerImageUrl)
|
||||
assertEquals(Instant.fromEpochMilliseconds(1), entity.publishDate)
|
||||
assertEquals("Article 📚", entity.type)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun networkTopicMapsToExternalModel() {
|
||||
val networkTopic = NetworkTopic(
|
||||
id = "0",
|
||||
name = "Test",
|
||||
shortDescription = "short description",
|
||||
longDescription = "long description",
|
||||
url = "URL",
|
||||
imageUrl = "imageUrl",
|
||||
)
|
||||
|
||||
val expected = Topic(
|
||||
id = "0",
|
||||
name = "Test",
|
||||
shortDescription = "short description",
|
||||
longDescription = "long description",
|
||||
url = "URL",
|
||||
imageUrl = "imageUrl",
|
||||
)
|
||||
|
||||
assertEquals(expected, networkTopic.asExternalModel())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun networkNewsResourceMapsToExternalModel() {
|
||||
val networkNewsResource = NetworkNewsResource(
|
||||
id = "0",
|
||||
title = "title",
|
||||
content = "content",
|
||||
url = "url",
|
||||
headerImageUrl = "headerImageUrl",
|
||||
publishDate = Instant.fromEpochMilliseconds(1),
|
||||
type = "Article 📚",
|
||||
topics = listOf("1", "2"),
|
||||
)
|
||||
|
||||
val networkTopics = listOf(
|
||||
NetworkTopic(
|
||||
id = "1",
|
||||
name = "Test 1",
|
||||
shortDescription = "short description 1",
|
||||
longDescription = "long description 1",
|
||||
url = "url 1",
|
||||
imageUrl = "imageUrl 1",
|
||||
),
|
||||
NetworkTopic(
|
||||
id = "2",
|
||||
name = "Test 2",
|
||||
shortDescription = "short description 2",
|
||||
longDescription = "long description 2",
|
||||
url = "url 2",
|
||||
imageUrl = "imageUrl 2",
|
||||
),
|
||||
)
|
||||
|
||||
val expected = NewsResource(
|
||||
id = "0",
|
||||
title = "title",
|
||||
content = "content",
|
||||
url = "url",
|
||||
headerImageUrl = "headerImageUrl",
|
||||
publishDate = Instant.fromEpochMilliseconds(1),
|
||||
type = "Article 📚",
|
||||
topics = networkTopics.map(NetworkTopic::asExternalModel),
|
||||
)
|
||||
assertEquals(expected, networkNewsResource.asExternalModel(networkTopics))
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2024 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.core.datastore.test
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.updateAndGet
|
||||
|
||||
class InMemoryDataStore<T>(initialValue: T) : DataStore<T> {
|
||||
override val data = MutableStateFlow(initialValue)
|
||||
override suspend fun updateData(
|
||||
transform: suspend (it: T) -> T,
|
||||
) = data.updateAndGet { transform(it) }
|
||||
}
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.1 KiB |