Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve OSS licenses screen #1018

Open
wants to merge 48 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
47ec25d
Add feature/licenses module
fumiya-kume Aug 29, 2023
830e3b0
Merge branch 'main' into kuu/improve-oss-licenses-screen
fumiya-kume Sep 3, 2023
aa0e419
Add empty oss license screen
fumiya-kume Sep 3, 2023
f537b83
Add empty oss license ViewModel
fumiya-kume Sep 3, 2023
fde39dc
Combine screen and ViewModel
fumiya-kume Sep 3, 2023
15c6b3b
Setup basis of license screen
fumiya-kume Sep 3, 2023
810acb4
Remove feature/lincenses
fumiya-kume Sep 3, 2023
2a326c1
Remove feature/licenses relational file
fumiya-kume Sep 3, 2023
b717b7d
Move classes
fumiya-kume Sep 3, 2023
6ddec12
Add logic to load the licenses data
fumiya-kume Sep 3, 2023
e4e55ff
Add Scaffold for OSS licenses screen
fumiya-kume Sep 3, 2023
71db515
Add UI implementation
fumiya-kume Sep 3, 2023
d2794b9
Add license detail implementation
fumiya-kume Sep 3, 2023
54e8302
Fix code format
fumiya-kume Sep 3, 2023
8cea72a
Add OssLicense screen
fumiya-kume Sep 12, 2023
0294a88
Add OssDetailLicense screen
fumiya-kume Sep 12, 2023
bde016a
Merge branch 'main' into kuu/improve-oss-licenses-screen
fumiya-kume Sep 12, 2023
dc641f3
Move License to core/model module
fumiya-kume Sep 12, 2023
c18fb1d
Oss screen move to feature / about
fumiya-kume Sep 12, 2023
1ae5174
Fix code format
fumiya-kume Sep 12, 2023
753f9a7
Update DI config with using the optional binding
fumiya-kume Sep 14, 2023
c605ed3
Delete unnecessary Dagger module
fumiya-kume Sep 14, 2023
35316a8
Rename unintentional method name for the navigation
fumiya-kume Sep 14, 2023
c7a3413
Remove un-necessary method
fumiya-kume Sep 14, 2023
be4982d
Cleanup fake class
fumiya-kume Sep 14, 2023
a7c4a4c
Cleanup duplicate logic for id of license
fumiya-kume Sep 14, 2023
6afe1e6
Using multi language support feature for the screen title
fumiya-kume Sep 14, 2023
1d0da32
Update library grouping logic
fumiya-kume Sep 14, 2023
6207404
Fix code format
fumiya-kume Sep 14, 2023
3793f2e
Merge branch 'main' into kuu/improve-oss-licenses-screen
fumiya-kume Sep 15, 2023
2c4d86f
Rename default implementation for Repository
fumiya-kume Sep 16, 2023
b172de9
Update DI config for OssLicenseDataSource
fumiya-kume Sep 16, 2023
5e5c12e
Refactoring DataFlow
fumiya-kume Sep 16, 2023
65ffeeb
Fix logic
fumiya-kume Sep 16, 2023
6e246d2
Remove duplicated libraries
fumiya-kume Sep 16, 2023
723baf9
Refactoring
fumiya-kume Sep 16, 2023
aaad120
Remove unnecessary code
fumiya-kume Sep 16, 2023
0aae559
Merge branch 'main' into kuu/improve-oss-licenses-screen
fumiya-kume Sep 16, 2023
4ba7ae4
Merge branch 'main' into kuu/improve-oss-licenses-screen
fumiya-kume Sep 16, 2023
c0c4f4d
Fix visibility
fumiya-kume Sep 16, 2023
1819113
Fix visibility
fumiya-kume Sep 16, 2023
2e08ce5
Merge branch 'main' into kuu/improve-oss-licenses-screen
fumiya-kume Sep 17, 2023
a2e5eb3
Merge branch 'main' into kuu/improve-oss-licenses-screen
fumiya-kume Sep 23, 2023
4183d96
Add screenshot testing for oss license screen
fumiya-kume Sep 23, 2023
d94c87e
Add screenshot testing for oss license detail screen
fumiya-kume Sep 23, 2023
a26cc92
revert comment out
fumiya-kume Sep 23, 2023
1255554
Try to update the build variant when CI
fumiya-kume Sep 23, 2023
ecd9b9f
Revert variant change
fumiya-kume Sep 23, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/CompareScreenshot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
workflow: UnitTest.yml
branch: main

- run: ./gradlew compareRoborazziDebug compareRoborazziDevDebug --stacktrace -Pscreenshot
- run: ./gradlew compareRoborazziDebug compareRoborazziDevRelease --stacktrace -Pscreenshot
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What problem are you experiencing with compareRoborazziDevDebug? 👀


- uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
if: ${{ always() }}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package io.github.droidkaigi.confsched2023

import android.content.Context
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import io.github.droidkaigi.confsched2023.data.di.AppAndroidBuildConfig
import io.github.droidkaigi.confsched2023.data.di.AppAndroidOssLicenseConfig
import io.github.droidkaigi.confsched2023.data.osslicense.OssLicenseDataSource
import io.github.droidkaigi.confsched2023.license.DefaultOssLicenseDataSource
import io.github.droidkaigi.confsched2023.model.BuildConfigProvider
import javax.inject.Singleton

Expand All @@ -15,6 +20,13 @@ class AppModule {
@Singleton
@AppAndroidBuildConfig
fun provideBuildConfigProvider(): BuildConfigProvider = AppBuildConfigProvider()

@Provides
@Singleton
@AppAndroidOssLicenseConfig
fun provideOssLicenseDataSourceProvider(
@ApplicationContext context: Context,
): OssLicenseDataSource = DefaultOssLicenseDataSource(context)
}

class AppBuildConfigProvider(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ import co.touchlab.kermit.Logger
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
import io.github.droidkaigi.confsched2023.about.aboutScreenRoute
import io.github.droidkaigi.confsched2023.about.navigateAboutScreen
import io.github.droidkaigi.confsched2023.about.navigateOssLicenseDetailScreen
import io.github.droidkaigi.confsched2023.about.navigateOssLicenseScreen
import io.github.droidkaigi.confsched2023.about.nestedAboutScreen
import io.github.droidkaigi.confsched2023.about.ossLicenseDetailScreen
import io.github.droidkaigi.confsched2023.about.ossLicenseScreen
import io.github.droidkaigi.confsched2023.achievements.achievementsScreenRoute
import io.github.droidkaigi.confsched2023.achievements.navigateAchievementsScreen
import io.github.droidkaigi.confsched2023.achievements.nestedAchievementsScreen
Expand Down Expand Up @@ -125,6 +129,19 @@ private fun KaigiNavHost(
onBackClick = navController::popBackStack,
onStaffClick = externalNavController::navigate,
)
ossLicenseScreen(
onLicenseClick = { license ->
navController.navigateOssLicenseDetailScreen(license)
},
onUpClick = {
navController.navigateUp()
},
)
ossLicenseDetailScreen(
onUpClick = {
navController.navigateUp()
},
)
// For KMP, we are not using navigation abstraction for contributors screen
composable(contributorsScreenRoute) {
val lifecycleOwner = LocalLifecycleOwner.current
Expand Down Expand Up @@ -171,7 +188,7 @@ private fun NavGraphBuilder.mainScreen(
Sponsors -> navController.navigateSponsorsScreen()
CodeOfConduct -> { externalNavController.navigate(url = "$portalBaseUrl/about/code-of-conduct") }
Contributors -> navController.navigate(contributorsScreenRoute)
License -> externalNavController.navigateToLicenseScreen()
License -> navController.navigateOssLicenseScreen()
Medium -> externalNavController.navigate(url = "https://medium.com/droidkaigi")
PrivacyPolicy -> {
externalNavController.navigate(url = "$portalBaseUrl/about/privacy")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package io.github.droidkaigi.confsched2023.license

import android.content.Context
import io.github.droidkaigi.confsched2023.R
import io.github.droidkaigi.confsched2023.data.osslicense.OssLicenseDataSource
import io.github.droidkaigi.confsched2023.model.License
import io.github.droidkaigi.confsched2023.model.OssLicenseGroup
import io.github.droidkaigi.confsched2023.ui.Inject
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okio.BufferedSource
import okio.buffer
import okio.source

public class DefaultOssLicenseDataSource @Inject constructor(
private val context: Context,
) : OssLicenseDataSource {

override suspend fun license(): List<OssLicenseGroup> {
return withContext(context = Dispatchers.IO) {
readLicenses()
.groupByCategory()
.map {
OssLicenseGroup(
title = it.key,
licenses = it.value,
)
}
.toPersistentList()
}
}

private fun readLicenses(): List<License> {
val licenseData = readLicensesFile().toRowList()
return readLicensesMetaFile().toRowList()
.map {
val (position, name) = it.split(' ', limit = 2)
val (offset, length) = position.split(':').map { it.toInt() }

val id = name.replace(' ', '-')
val licensesText = kotlin.runCatching {
licenseData.subList(offset, offset + length).joinToString()
}.getOrNull() ?: ""

License(
id = id,
name = name,
licensesText = licensesText,
)
}
.distinctBy { it.id }
}

private fun List<License>.groupByCategory(): Map<String, List<License>> {
val categoryList = listOf(
"Android Support",
"Android Datastore",
"Android ",
"Compose UI",
"Compose Material3",
"Compose ",
"AndroidX lifecycle",
"AndroidX ",
"Kotlin",
"Dagger",
"Firebase",
"Ktorfit",
"okhttp",
"ktor",
)
return groupBy { license ->
categoryList.firstOrNull {
license.name.startsWith(
prefix = it,
ignoreCase = true,
)
} ?: "etc"
}
}

private fun readLicensesMetaFile(): BufferedSource {
return context.resources.openRawResource(R.raw.third_party_license_metadata)
.source()
.buffer()
}

private fun readLicensesFile(): BufferedSource {
return context.resources.openRawResource(R.raw.third_party_licenses)
.source()
.buffer()
}

private fun BufferedSource.toRowList(): List<String> {
val list: MutableList<String> = mutableListOf()
while (true) {
val line = readUtf8Line() ?: break
list.add(line)
}
return list
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,22 @@ import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import io.github.droidkaigi.confsched2023.data.osslicense.OssLicenseDataSource
import io.github.droidkaigi.confsched2023.model.BuildConfigProvider
import io.github.droidkaigi.confsched2023.model.License
import io.github.droidkaigi.confsched2023.model.OssLicenseGroup
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import java.util.Optional
import javax.inject.Qualifier
import javax.inject.Singleton

@Qualifier
public annotation class AppAndroidBuildConfig

@Qualifier
public annotation class AppAndroidOssLicenseConfig

@Module
@InstallIn(SingletonComponent::class)
public class BuildConfigProviderModule {
Expand All @@ -25,6 +33,16 @@ public class BuildConfigProviderModule {
} else {
EmptyBuildConfigProvider
}

@Provides
@Singleton
public fun provideOssLicenseRepositoryProvider(
@AppAndroidOssLicenseConfig ossLicenseDataSourceOptional: Optional<OssLicenseDataSource>,
): OssLicenseDataSource = if (ossLicenseDataSourceOptional.isPresent) {
ossLicenseDataSourceOptional.get()
} else {
EmptyOssLicenseDataSource
}
}

@InstallIn(SingletonComponent::class)
Expand All @@ -35,7 +53,32 @@ public abstract class AppAndroidBuildConfigModule {
public abstract fun bindBuildConfigProvider(): BuildConfigProvider
}

@InstallIn(SingletonComponent::class)
@Module
public abstract class AppAndroidOssLicenseModule {
@BindsOptionalOf
@AppAndroidOssLicenseConfig
public abstract fun bindOssLicenseDataStoreProvider(): OssLicenseDataSource
}

private object EmptyBuildConfigProvider : BuildConfigProvider {
override val versionName: String = ""
override val debugBuild: Boolean = false
}

private object EmptyOssLicenseDataSource : OssLicenseDataSource {
override suspend fun license(): PersistentList<OssLicenseGroup> {
return persistentListOf(
OssLicenseGroup(
"dummy",
listOf(
License(
"id",
"name",
"license text",
),
),
),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.github.droidkaigi.confsched2023.data.osslicense

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import io.github.droidkaigi.confsched2023.model.OssLicenseRepository
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
public class OssLicenseRepositoryModule {
@Provides
@Singleton
public fun provideOssLicenseRepository(
ossLicenseDataSource: OssLicenseDataSource,
): OssLicenseRepository {
return DefaultOssLicenseRepository(
ossLicenseDataSource,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.github.droidkaigi.confsched2023.data.osslicense

import io.github.droidkaigi.confsched2023.model.OssLicenseGroup
import io.github.droidkaigi.confsched2023.model.OssLicenseRepository
import io.github.droidkaigi.confsched2023.ui.Inject
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.onStart

public class DefaultOssLicenseRepository @Inject constructor(
private val ossLicenseDataSource: OssLicenseDataSource,
) : OssLicenseRepository {

private val ossLicenseStateFlow =
MutableStateFlow<PersistentList<OssLicenseGroup>>(persistentListOf())

override fun licenseData(): Flow<PersistentList<OssLicenseGroup>> {
return ossLicenseStateFlow.onStart {
val ossLicenseData = ossLicenseDataSource.license().toPersistentList()
ossLicenseStateFlow.emit(ossLicenseData)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.github.droidkaigi.confsched2023.data.osslicense

import io.github.droidkaigi.confsched2023.model.OssLicenseGroup

public interface OssLicenseDataSource {
public suspend fun license(): List<OssLicenseGroup>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.github.droidkaigi.confsched2023.model

data class License(
val id: String,
val name: String,
val licensesText: String,
)

data class OssLicenseGroup(
val title: String,
val licenses: List<License>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.github.droidkaigi.confsched2023.model

import kotlinx.collections.immutable.PersistentList
import kotlinx.coroutines.flow.Flow

public interface OssLicenseRepository {
public fun licenseData(): Flow<PersistentList<OssLicenseGroup>>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package io.github.droidkaigi.confsched2023.testing.robot

import androidx.compose.ui.test.isRoot
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import com.github.takahirom.roborazzi.captureRoboImage
import io.github.droidkaigi.confsched2023.about.OssLicenseDetailScreen
import io.github.droidkaigi.confsched2023.designsystem.theme.KaigiTheme
import io.github.droidkaigi.confsched2023.testing.RobotTestRule
import io.github.droidkaigi.confsched2023.testing.coroutines.runTestWithLogging
import kotlinx.coroutines.test.TestDispatcher
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds

class OssLicenseDetailScreenRobot @Inject constructor(
private val testDispatcher: TestDispatcher,
) {
@Inject lateinit var robotTestRule: RobotTestRule
private lateinit var composeTestRule: AndroidComposeTestRule<*, *>
operator fun invoke(
block: OssLicenseDetailScreenRobot.() -> Unit,
) {
runTestWithLogging(timeout = 30.seconds) {
[email protected] = robotTestRule.composeTestRule
block()
}
}

fun setupOssLicenseDetailScreenContent() {
composeTestRule.setContent {
KaigiTheme {
OssLicenseDetailScreen(
onUpClick = {},
)
}
}
waitUntilIdle()
}

fun checkScreenCapture() {
composeTestRule
.onNode(isRoot())
.captureRoboImage()
}

fun waitUntilIdle() {
composeTestRule.waitForIdle()
testDispatcher.scheduler.advanceUntilIdle()
}
}
Loading
Loading