From ad1880f87fc3e20a092cbf13c67d60862f7d1399 Mon Sep 17 00:00:00 2001 From: Yamil Medina Date: Fri, 5 Apr 2024 20:07:27 +0200 Subject: [PATCH] feat: sketching prototype pt1 - module (WPB-6243) (#2811) --- app/build.gradle.kts | 9 ++ .../{Extensions.kt => AppExtensions.kt} | 43 +------ .../ui/common/{ThemeExt.kt => AppThemeExt.kt} | 13 -- .../ui/common/scaffold/WireScaffold.kt | 2 +- .../ui/common/snackbar/WireSnackbar.kt | 1 + .../home/conversations/ConversationScreen.kt | 2 +- .../details/GroupConversationDetailsScreen.kt | 2 +- .../home/messagecomposer/AdditionalOptions.kt | 8 +- .../messagecomposer/EnabledMessageComposer.kt | 14 +++ .../messagecomposer/MessageComposeActions.kt | 26 +++- .../state/AdditionalOptionMenuState.kt | 5 + .../util/debug/FeatureVisibilityFlags.kt | 1 + app/src/main/res/drawable/ic_drawing.xml | 10 ++ app/src/main/res/values/strings.xml | 1 + build-logic/plugins/build.gradle.kts | 5 + .../kotlin/AndroidLibraryConventionPlugin.kt | 6 +- .../src/main/kotlin/KoverConventionPlugin.kt | 76 ++++++++++++ buildSrc/build.gradle.kts | 2 +- .../kotlin/scripts/infrastructure.gradle.kts | 9 ++ .../main/kotlin/scripts/quality.gradle.kts | 50 +------- config/detekt/baseline.xml | 41 ++++--- core/ui-common/.gitignore | 1 + core/ui-common/build.gradle.kts | 34 ++++++ core/ui-common/consumer-rules.pro | 0 core/ui-common/proguard-rules.pro | 21 ++++ .../wire/android/ExampleInstrumentedTest.kt | 24 ++++ core/ui-common/src/main/AndroidManifest.xml | 20 ++++ .../com/wire/android/model/Clickable.kt | 0 .../com/wire/android/ui/common/Extensions.kt | 56 +++++++++ .../com/wire/android/ui/common/ThemeExt.kt | 33 ++++++ .../android/ui/common/button/WireButton.kt | 0 .../ui/common/button/WireButtonDefaults.kt | 0 .../ui/common/button/WireCheckBoxDefaults.kt | 0 .../android/ui/common/button/WireItemLabel.kt | 0 .../ui/common/button/WirePrimaryButton.kt | 0 .../ui/common/button/WirePrimaryIconButton.kt | 7 +- .../ui/common/button/WireSecondaryButton.kt | 0 .../common/button/WireSecondaryIconButton.kt | 17 ++- .../ui/common/button/WireTertiaryButton.kt | 0 .../common/button/WireTertiaryIconButton.kt | 17 ++- .../CenteredCircularProgressBarIndicator.kt | 0 .../progress/WireCircularProgressIndicator.kt | 0 .../progress/WireLinearProgressIndicator.kt | 0 .../common/snackbar/LocalSnackbarHostState.kt | 0 .../ui/common/snackbar/SwipeableSnackbar.kt | 4 +- .../com/wire/android/ui/theme/Accent.kt | 0 .../kotlin/com/wire/android/ui/theme/Theme.kt | 12 +- .../com/wire/android/ui/theme/ThemeOption.kt | 0 .../com/wire/android/ui/theme/ThemeUtils.kt | 0 .../wire/android/ui/theme/WireColorPalette.kt | 0 .../wire/android/ui/theme/WireColorScheme.kt | 0 .../wire/android/ui/theme/WireDimensions.kt | 0 .../wire/android/ui/theme/WireTypography.kt | 5 - .../android/ui/theme/WireTypographyBase.kt | 0 .../wire/android/util/SyncStateObserver.kt | 0 .../src/main/res/values-de/strings.xml | 21 ++++ .../ui-common/src/main/res/values/strings.xml | 22 ++++ .../wire/android/ui/common/ExampleUnitTest.kt | 34 ++++++ features/sketch/.gitignore | 1 + features/sketch/build.gradle.kts | 23 ++++ features/sketch/consumer-rules.pro | 0 features/sketch/proguard-rules.pro | 21 ++++ .../wire/android/ExampleInstrumentedTest.kt | 24 ++++ features/sketch/src/main/AndroidManifest.xml | 20 ++++ .../sketch/DrawingCanvasBottomSheet.kt | 86 ++++++++++++++ .../feature/sketch/DrawingCanvasComponent.kt | 111 ++++++++++++++++++ .../feature/sketch/DrawingCanvasViewModel.kt | 87 ++++++++++++++ .../android/feature/sketch/model/DrawMode.kt | 22 ++++ .../sketch/model/DrawingMotionEvent.kt | 22 ++++ .../sketch/model/DrawingPathProperties.kt | 83 +++++++++++++ .../feature/sketch/model/DrawingState.kt | 28 +++++ .../sketch/src/main/res/values/strings.xml | 20 ++++ .../android/feature/sketch/ExampleUnitTest.kt | 34 ++++++ features/template/.gitignore | 1 + features/template/build.gradle.kts | 19 +++ features/template/consumer-rules.pro | 0 features/template/proguard-rules.pro | 21 ++++ .../wire/android/ExampleInstrumentedTest.kt | 24 ++++ .../template/src/main/AndroidManifest.xml | 20 ++++ .../feature/template/AndroidExampleView.kt | 29 +++++ .../template/src/main/res/values/strings.xml | 20 ++++ .../feature/template/ExampleUnitTest.kt | 34 ++++++ gradle/libs.versions.toml | 4 + scripts/new_feature_module.sh | 40 +++++++ settings.gradle.kts | 22 +++- 85 files changed, 1314 insertions(+), 156 deletions(-) rename app/src/main/kotlin/com/wire/android/ui/common/{Extensions.kt => AppExtensions.kt} (76%) rename app/src/main/kotlin/com/wire/android/ui/common/{ThemeExt.kt => AppThemeExt.kt} (73%) create mode 100644 app/src/main/res/drawable/ic_drawing.xml create mode 100644 build-logic/plugins/src/main/kotlin/KoverConventionPlugin.kt create mode 100644 core/ui-common/.gitignore create mode 100644 core/ui-common/build.gradle.kts create mode 100644 core/ui-common/consumer-rules.pro create mode 100644 core/ui-common/proguard-rules.pro create mode 100644 core/ui-common/src/androidTest/java/com/wire/android/ExampleInstrumentedTest.kt create mode 100644 core/ui-common/src/main/AndroidManifest.xml rename {app => core/ui-common}/src/main/kotlin/com/wire/android/model/Clickable.kt (100%) create mode 100644 core/ui-common/src/main/kotlin/com/wire/android/ui/common/Extensions.kt create mode 100644 core/ui-common/src/main/kotlin/com/wire/android/ui/common/ThemeExt.kt rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/common/button/WireButton.kt (100%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/common/button/WireButtonDefaults.kt (100%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/common/button/WireCheckBoxDefaults.kt (100%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/common/button/WireItemLabel.kt (100%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/common/button/WirePrimaryButton.kt (100%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/common/button/WirePrimaryIconButton.kt (90%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/common/button/WireSecondaryButton.kt (100%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/common/button/WireSecondaryIconButton.kt (88%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/common/button/WireTertiaryButton.kt (100%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/common/button/WireTertiaryIconButton.kt (88%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/common/progress/CenteredCircularProgressBarIndicator.kt (100%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/common/progress/WireCircularProgressIndicator.kt (100%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/common/progress/WireLinearProgressIndicator.kt (100%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/common/snackbar/LocalSnackbarHostState.kt (100%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/common/snackbar/SwipeableSnackbar.kt (97%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/theme/Accent.kt (100%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/theme/Theme.kt (89%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/theme/ThemeOption.kt (100%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/theme/ThemeUtils.kt (100%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/theme/WireColorPalette.kt (100%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/theme/WireColorScheme.kt (100%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/theme/WireDimensions.kt (100%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/theme/WireTypography.kt (93%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/ui/theme/WireTypographyBase.kt (100%) rename {app => core/ui-common}/src/main/kotlin/com/wire/android/util/SyncStateObserver.kt (100%) create mode 100644 core/ui-common/src/main/res/values-de/strings.xml create mode 100644 core/ui-common/src/main/res/values/strings.xml create mode 100644 core/ui-common/src/test/kotlin/com/wire/android/ui/common/ExampleUnitTest.kt create mode 100644 features/sketch/.gitignore create mode 100644 features/sketch/build.gradle.kts create mode 100644 features/sketch/consumer-rules.pro create mode 100644 features/sketch/proguard-rules.pro create mode 100644 features/sketch/src/androidTest/java/com/wire/android/ExampleInstrumentedTest.kt create mode 100644 features/sketch/src/main/AndroidManifest.xml create mode 100644 features/sketch/src/main/java/com/wire/android/feature/sketch/DrawingCanvasBottomSheet.kt create mode 100644 features/sketch/src/main/java/com/wire/android/feature/sketch/DrawingCanvasComponent.kt create mode 100644 features/sketch/src/main/java/com/wire/android/feature/sketch/DrawingCanvasViewModel.kt create mode 100644 features/sketch/src/main/java/com/wire/android/feature/sketch/model/DrawMode.kt create mode 100644 features/sketch/src/main/java/com/wire/android/feature/sketch/model/DrawingMotionEvent.kt create mode 100644 features/sketch/src/main/java/com/wire/android/feature/sketch/model/DrawingPathProperties.kt create mode 100644 features/sketch/src/main/java/com/wire/android/feature/sketch/model/DrawingState.kt create mode 100644 features/sketch/src/main/res/values/strings.xml create mode 100644 features/sketch/src/test/java/com/wire/android/feature/sketch/ExampleUnitTest.kt create mode 100644 features/template/.gitignore create mode 100644 features/template/build.gradle.kts create mode 100644 features/template/consumer-rules.pro create mode 100644 features/template/proguard-rules.pro create mode 100644 features/template/src/androidTest/java/com/wire/android/ExampleInstrumentedTest.kt create mode 100644 features/template/src/main/AndroidManifest.xml create mode 100644 features/template/src/main/kotlin/com/wire/android/feature/template/AndroidExampleView.kt create mode 100644 features/template/src/main/res/values/strings.xml create mode 100644 features/template/src/test/kotlin/com/wire/android/feature/template/ExampleUnitTest.kt create mode 100755 scripts/new_feature_module.sh diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fdb9d48f64e..29d03648da4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -34,6 +34,7 @@ plugins { id(ScriptPlugins.compilation) id(ScriptPlugins.testing) id(ScriptPlugins.spotless) + id(libs.plugins.wire.kover.get().pluginId) } repositories { @@ -83,6 +84,14 @@ dependencies { implementation("com.wire.kalium:kalium-logic") implementation("com.wire.kalium:kalium-util") + // features + implementation(project(":features:sketch")) + implementation(project(":core:ui-common")) + + // kover + kover(project(":features:sketch")) + kover(project(":core:ui-common")) + // Application dependencies implementation(libs.androidx.appcompat) implementation(libs.androidx.core) diff --git a/app/src/main/kotlin/com/wire/android/ui/common/Extensions.kt b/app/src/main/kotlin/com/wire/android/ui/common/AppExtensions.kt similarity index 76% rename from app/src/main/kotlin/com/wire/android/ui/common/Extensions.kt rename to app/src/main/kotlin/com/wire/android/ui/common/AppExtensions.kt index fd48976141e..1e1a8fe5006 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/Extensions.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/AppExtensions.kt @@ -18,25 +18,20 @@ package com.wire.android.ui.common -import android.widget.Toast import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ripple.rememberRipple -import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.semantics.Role import androidx.lifecycle.Lifecycle @@ -45,13 +40,10 @@ import androidx.lifecycle.flowWithLifecycle import com.google.accompanist.placeholder.PlaceholderHighlight import com.google.accompanist.placeholder.placeholder import com.google.accompanist.placeholder.shimmer -import com.wire.android.R -import com.wire.android.model.ClickBlockParams import com.wire.android.model.Clickable import com.wire.android.ui.home.conversations.model.messagetypes.asset.UIAssetMessage import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireDimensions -import com.wire.android.util.LocalSyncStateObserver import com.wire.kalium.logic.data.message.Message import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -65,6 +57,8 @@ import java.util.Locale import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext +// todo try to move as much as we can to common + @Composable fun Modifier.selectableBackground(isSelected: Boolean, onClick: () -> Unit): Modifier = this.selectable( @@ -75,15 +69,6 @@ fun Modifier.selectableBackground(isSelected: Boolean, onClick: () -> Unit): Mod role = Role.Tab ) -@Composable -fun Tint(contentColor: Color, content: @Composable () -> Unit) { - CompositionLocalProvider(LocalContentColor provides contentColor, content = content) -} - -@Composable -fun ImageVector.Icon(modifier: Modifier = Modifier): @Composable (() -> Unit) = - { androidx.compose.material3.Icon(imageVector = this, contentDescription = "", modifier = modifier) } - @Composable fun Modifier.shimmerPlaceholder( visible: Boolean, @@ -111,29 +96,14 @@ fun Modifier.clickable(clickable: Clickable?) = clickable?.let { ) } ?: this -@Composable -fun rememberClickBlockAction(clickBlockParams: ClickBlockParams, clickAction: () -> Unit): () -> Unit { - val syncStateObserver = LocalSyncStateObserver.current - val context = LocalContext.current - return remember(clickBlockParams, syncStateObserver, clickAction) { - { - when { - clickBlockParams.blockWhenConnecting && syncStateObserver.isConnecting -> - Toast.makeText(context, context.getString(R.string.label_wait_until_connected), Toast.LENGTH_SHORT).show() - clickBlockParams.blockWhenSyncing && syncStateObserver.isSyncing -> - Toast.makeText(context, context.getString(R.string.label_wait_until_synchronised), Toast.LENGTH_SHORT).show() - else -> clickAction() - } - } - } -} - @Composable fun rememberFlow( flow: Flow, lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current ): Flow { - return remember(key1 = flow, key2 = lifecycleOwner) { flow.flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) } + return remember(key1 = flow, key2 = lifecycleOwner) { + flow.flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) + } } // TODO replace by collectAsStateWithLifecycle() after updating lifecycle version to 2.6.0-alpha01 or newer @@ -154,8 +124,7 @@ fun StateFlow.collectAsStateLifecycleAware( ): State = collectAsStateLifecycleAware(value, context) fun monthYearHeader(month: Int, year: Int): String { - val currentYear = Instant.fromEpochMilliseconds(System.currentTimeMillis()).toLocalDateTime( - TimeZone.currentSystemDefault()).year + val currentYear = Instant.fromEpochMilliseconds(System.currentTimeMillis()).toLocalDateTime(TimeZone.currentSystemDefault()).year val monthYearInstant = LocalDateTime(year = year, monthNumber = month, 1, 0, 0, 0) val monthName = monthYearInstant.month.getDisplayName(TextStyle.FULL_STANDALONE, Locale.getDefault()) diff --git a/app/src/main/kotlin/com/wire/android/ui/common/ThemeExt.kt b/app/src/main/kotlin/com/wire/android/ui/common/AppThemeExt.kt similarity index 73% rename from app/src/main/kotlin/com/wire/android/ui/common/ThemeExt.kt rename to app/src/main/kotlin/com/wire/android/ui/common/AppThemeExt.kt index 01a0a51a4c5..905d6af8b45 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/ThemeExt.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/AppThemeExt.kt @@ -18,25 +18,12 @@ package com.wire.android.ui.common -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color import com.wire.android.ui.theme.WireColorScheme -import com.wire.android.ui.theme.wireColorScheme -import com.wire.android.ui.theme.wireDimensions -import com.wire.android.ui.theme.wireTypography import com.wire.kalium.logic.data.id.ConversationId import kotlin.math.absoluteValue -@Composable -internal fun dimensions() = MaterialTheme.wireDimensions - -@Composable -internal fun colorsScheme() = MaterialTheme.wireColorScheme - -@Composable -internal fun typography() = MaterialTheme.wireTypography - @Composable internal fun WireColorScheme.conversationColor(id: ConversationId): Color { val colors = this.groupAvatarColors diff --git a/app/src/main/kotlin/com/wire/android/ui/common/scaffold/WireScaffold.kt b/app/src/main/kotlin/com/wire/android/ui/common/scaffold/WireScaffold.kt index 526f6043d2f..3c882eec5b3 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/scaffold/WireScaffold.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/scaffold/WireScaffold.kt @@ -17,7 +17,7 @@ */ package com.wire.android.ui.common.scaffold -import SwipeableSnackbar +import com.wire.android.ui.common.snackbar.SwipeableSnackbar import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.imePadding diff --git a/app/src/main/kotlin/com/wire/android/ui/common/snackbar/WireSnackbar.kt b/app/src/main/kotlin/com/wire/android/ui/common/snackbar/WireSnackbar.kt index c4f0191b9bb..26326f43daf 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/snackbar/WireSnackbar.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/snackbar/WireSnackbar.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.platform.LocalContext import com.wire.android.util.ui.UIText import kotlinx.coroutines.flow.SharedFlow +// TODO: moved to commons, when deciding about [UIText] @Composable fun SnackbarHostState.collectAndShowSnackbar( snackbarFlow: SharedFlow diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt index 93b23688e67..100882b03a2 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt @@ -18,7 +18,7 @@ package com.wire.android.ui.home.conversations -import SwipeableSnackbar +import com.wire.android.ui.common.snackbar.SwipeableSnackbar import android.annotation.SuppressLint import android.net.Uri import androidx.activity.compose.BackHandler diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt index 83085f5d298..bed7cbcf56c 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/details/GroupConversationDetailsScreen.kt @@ -18,7 +18,7 @@ package com.wire.android.ui.home.conversations.details -import SwipeableSnackbar +import com.wire.android.ui.common.snackbar.SwipeableSnackbar import androidx.annotation.StringRes import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.LocalOverscrollConfiguration diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AdditionalOptions.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AdditionalOptions.kt index e252740fc84..16277e3aac9 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AdditionalOptions.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AdditionalOptions.kt @@ -55,6 +55,7 @@ fun AdditionalOptionsMenu( onRichEditingButtonClicked: () -> Unit, onCloseRichEditingButtonClicked: () -> Unit, onRichOptionButtonClicked: (RichTextMarkdown) -> Unit, + onDrawingModeClicked: () -> Unit, modifier: Modifier = Modifier ) { Box(modifier.background(colorsScheme().messageComposerBackgroundColor)) { @@ -71,7 +72,8 @@ fun AdditionalOptionsMenu( onGifButtonClicked = onGifOptionClicked ?: {}, onSelfDeletionOptionButtonClicked = onOnSelfDeletingOptionClicked ?: {}, onRichEditingButtonClicked = onRichEditingButtonClicked, - onPingClicked = onPingOptionClicked + onPingClicked = onPingOptionClicked, + onDrawingModeClicked = onDrawingModeClicked ) } @@ -153,6 +155,7 @@ fun AttachmentAndAdditionalOptionsMenuItems( isSelfDeletingActive: Boolean, onGifButtonClicked: () -> Unit = {}, onRichEditingButtonClicked: () -> Unit = {}, + onDrawingModeClicked: () -> Unit = {}, modifier: Modifier = Modifier ) { Column(modifier.wrapContentSize()) { @@ -168,7 +171,8 @@ fun AttachmentAndAdditionalOptionsMenuItems( isSelfDeletingSettingEnabled = isSelfDeletingSettingEnabled, isSelfDeletingActive = isSelfDeletingActive, onGifButtonClicked = onGifButtonClicked, - onRichEditingButtonClicked = onRichEditingButtonClicked + onRichEditingButtonClicked = onRichEditingButtonClicked, + onDrawingModeClicked = onDrawingModeClicked ) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt index 80dccaa7bcb..97c270bba14 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt @@ -47,6 +47,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp +import com.wire.android.feature.sketch.DrawingCanvasBottomSheet import com.wire.android.ui.common.banner.SecurityClassificationBannerForConversation import com.wire.android.ui.common.bottombar.BottomNavigationBarHeight import com.wire.android.ui.common.colorsScheme @@ -257,6 +258,7 @@ fun EnabledMessageComposer( additionalOptionStateHolder.toRichTextEditing() }, onCloseRichEditingButtonClicked = additionalOptionStateHolder::toAttachmentAndAdditionalOptionsMenu, + onDrawingModeClicked = additionalOptionStateHolder::toDrawingMode ) } Box( @@ -288,6 +290,18 @@ fun EnabledMessageComposer( ) } } + + if (additionalOptionStateHolder.selectedOption == AdditionalOptionSelectItem.DrawingMode) { + DrawingCanvasBottomSheet( + onDismissSketch = { + inputStateHolder.handleBackPressed( + isImeVisible, + additionalOptionStateHolder.additionalOptionsSubMenuState + ) + }, + onSendSketch = onSendButtonClicked + ) + } } } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposeActions.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposeActions.kt index e917b3f7554..df183536f57 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposeActions.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposeActions.kt @@ -51,7 +51,8 @@ fun MessageComposeActions( onPingButtonClicked: () -> Unit, onSelfDeletionOptionButtonClicked: () -> Unit, onGifButtonClicked: () -> Unit, - onRichEditingButtonClicked: () -> Unit + onRichEditingButtonClicked: () -> Unit, + onDrawingModeClicked: () -> Unit ) { if (isEditing) { EditingActions( @@ -71,7 +72,8 @@ fun MessageComposeActions( isSelfDeletingActive, onSelfDeletionOptionButtonClicked, onPingButtonClicked, - onMentionButtonClicked + onMentionButtonClicked, + onDrawingModeClicked ) } } @@ -87,7 +89,8 @@ private fun ComposingActions( isSelfDeletingActive: Boolean, onSelfDeletionOptionButtonClicked: () -> Unit, onPingButtonClicked: () -> Unit, - onMentionButtonClicked: () -> Unit + onMentionButtonClicked: () -> Unit, + onDrawingModeClicked: () -> Unit ) { val localFeatureVisibilityFlags = LocalFeatureVisibilityFlags.current @@ -107,6 +110,12 @@ private fun ComposingActions( isSelected = selectedOption == AdditionalOptionSelectItem.RichTextEditing, onRichEditingButtonClicked ) + if (DrawingIcon) { + DrawingModeAction( + isSelected = selectedOption == AdditionalOptionSelectItem.DrawingMode, + onDrawingModeClicked + ) + } if (EmojiIcon) AddEmojiAction({}) if (GifIcon) AddGifAction(onGifButtonClicked) if (isSelfDeletingSettingEnabled) SelfDeletingMessageAction( @@ -158,6 +167,17 @@ private fun RichTextEditingAction(isSelected: Boolean, onButtonClicked: () -> Un ) } +@Composable +private fun DrawingModeAction(isSelected: Boolean, onButtonClicked: () -> Unit) { + WireSecondaryIconButton( + onButtonClicked = onButtonClicked, + clickBlockParams = ClickBlockParams(blockWhenSyncing = true, blockWhenConnecting = true), + iconResource = R.drawable.ic_drawing, + state = if (isSelected) WireButtonState.Selected else WireButtonState.Default, + contentDescription = R.string.content_description_conversation_enable_drawing_mode + ) +} + @Composable private fun AddEmojiAction(onButtonClicked: () -> Unit) { var isSelected by remember { mutableStateOf(false) } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/state/AdditionalOptionMenuState.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/state/AdditionalOptionMenuState.kt index 02c0a1dfb32..92ad6c24462 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/state/AdditionalOptionMenuState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/state/AdditionalOptionMenuState.kt @@ -40,6 +40,7 @@ enum class AdditionalOptionSubMenuState { enum class AdditionalOptionSelectItem { RichTextEditing, + DrawingMode, // it's only used to show keyboard after self deleting bottom sheet collapses SelfDeleting, @@ -94,6 +95,10 @@ class AdditionalOptionStateHolder { selectedOption = AdditionalOptionSelectItem.SelfDeleting } + fun toDrawingMode() { + selectedOption = AdditionalOptionSelectItem.DrawingMode + } + companion object { fun saver(): Saver = Saver( save = { diff --git a/app/src/main/kotlin/com/wire/android/util/debug/FeatureVisibilityFlags.kt b/app/src/main/kotlin/com/wire/android/util/debug/FeatureVisibilityFlags.kt index 09f81a9f2e3..02ee90a3c5d 100644 --- a/app/src/main/kotlin/com/wire/android/util/debug/FeatureVisibilityFlags.kt +++ b/app/src/main/kotlin/com/wire/android/util/debug/FeatureVisibilityFlags.kt @@ -55,6 +55,7 @@ object FeatureVisibilityFlags { const val UserProfileEditIcon = false const val MessageEditIcon = true const val SearchConversationMessages = true + const val DrawingIcon = false } val LocalFeatureVisibilityFlags = staticCompositionLocalOf { FeatureVisibilityFlags } diff --git a/app/src/main/res/drawable/ic_drawing.xml b/app/src/main/res/drawable/ic_drawing.xml new file mode 100644 index 00000000000..30875947c93 --- /dev/null +++ b/app/src/main/res/drawable/ic_drawing.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 43538449aee..1a0d5ad102b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -104,6 +104,7 @@ Search icon Search input Enable rich text mode button + Enable drawing mode button Rich text formatting Header Rich text formatting Bold Rich text formatting Italic diff --git a/build-logic/plugins/build.gradle.kts b/build-logic/plugins/build.gradle.kts index cd64a864634..c6616a12251 100644 --- a/build-logic/plugins/build.gradle.kts +++ b/build-logic/plugins/build.gradle.kts @@ -35,6 +35,7 @@ tasks.withType().configureEach { dependencies { compileOnly(libs.android.gradlePlugin) compileOnly(libs.kotlin.gradlePlugin) + compileOnly(libs.kover.gradlePlugin) testImplementation(libs.junit4) testImplementation(libs.kluent.core) @@ -54,5 +55,9 @@ gradlePlugin { id = libs.plugins.wire.hilt.get().pluginId implementationClass = "HiltConventionPlugin" } + register("wireKoverConventionPlugin") { + id = libs.plugins.wire.kover.get().pluginId + implementationClass = "KoverConventionPlugin" + } } } diff --git a/build-logic/plugins/src/main/kotlin/AndroidLibraryConventionPlugin.kt b/build-logic/plugins/src/main/kotlin/AndroidLibraryConventionPlugin.kt index f69e573a970..10f33469603 100644 --- a/build-logic/plugins/src/main/kotlin/AndroidLibraryConventionPlugin.kt +++ b/build-logic/plugins/src/main/kotlin/AndroidLibraryConventionPlugin.kt @@ -23,14 +23,16 @@ import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.configure -class AndroidLibraryConventionPlugin: Plugin { +class AndroidLibraryConventionPlugin : Plugin { override fun apply(target: Project): Unit = with(target) { - with(pluginManager){ + with(pluginManager) { apply("com.android.library") apply("org.jetbrains.kotlin.android") } extensions.configure { + namespace = "com.wire.android.feature.${target.name}" + // TODO: Handle flavors. Currently implemented in `variants.gradle.kts` script configureKotlinAndroid(this) defaultConfig.targetSdk = AndroidSdk.target diff --git a/build-logic/plugins/src/main/kotlin/KoverConventionPlugin.kt b/build-logic/plugins/src/main/kotlin/KoverConventionPlugin.kt new file mode 100644 index 00000000000..c3ed9f91e6f --- /dev/null +++ b/build-logic/plugins/src/main/kotlin/KoverConventionPlugin.kt @@ -0,0 +1,76 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +import kotlinx.kover.gradle.plugin.dsl.KoverReportExtension +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure + +class KoverConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + pluginManager.apply("org.jetbrains.kotlinx.kover") + + extensions.configure { + defaults { + if (project.name == "app") { + println(">> Configuring Kover devDebug variant for: ${project.name}") + mergeWith("devDebug") + } else { + println(">> Configuring Kover debug variant for: ${project.name}") + mergeWith("debug") + } + + filters { + excludes { + classes( + "*Fragment", + "*Fragment\$*", + "*Activity", + "*Activity\$*", + "*.databinding.*", + "*.BuildConfig", + "**/R.class", + "**/R\$*.class", + "**/Manifest*.*", + "**/Manifest$*.class", + "**/*Test*.*", + "*NavArgs*", + "*ComposableSingletons*", + "*_HiltModules*", + "*Hilt_*", + ) + packages( + "hilt_aggregated_deps", + "com.wire.android.di", + "dagger.hilt.internal.aggregatedroot.codegen", + "com.wire.android.ui.home.conversations.mock", + ) + annotatedBy( + "*Generated*", + "*HomeNavGraph*", + "*Destination*", + "*Composable*", + "*Preview*", + ) + } + } + } + } + } + } +} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index e65942b32bd..a9776a747ab 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -33,7 +33,7 @@ dependencies { implementation("com.android.tools.build:gradle:${klibs.versions.agp.get()}") implementation(libs.kotlin.gradlePlugin) implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:${klibs.versions.detekt.get()}") - implementation("org.jetbrains.kotlinx:kover-gradle-plugin:${klibs.versions.kover.get()}") + implementation("org.jetbrains.kotlinx:kover-gradle-plugin:${libs.versions.kover.get()}") implementation(libs.spotless.gradlePlugin) implementation(libs.android.junit5) diff --git a/buildSrc/src/main/kotlin/scripts/infrastructure.gradle.kts b/buildSrc/src/main/kotlin/scripts/infrastructure.gradle.kts index 6703c3919c4..002b525e69f 100644 --- a/buildSrc/src/main/kotlin/scripts/infrastructure.gradle.kts +++ b/buildSrc/src/main/kotlin/scripts/infrastructure.gradle.kts @@ -19,6 +19,7 @@ package scripts import findVersion +import org.gradle.configurationcache.extensions.capitalized import scripts.Variants_gradle.Default import java.util.Properties @@ -34,6 +35,14 @@ tasks.named("wrapper") { tasks.register("runUnitTests") { description = "Runs all Unit Tests." dependsOn(":app:test${Default.BUILD_VARIANT}UnitTest") + + // valid submodules path to run unit tests + val validSubprojects = setOf("core", "features") + rootProject.subprojects { + if (validSubprojects.contains(parent?.name)) { + dependsOn(":${parent?.name}:$name:test${Default.BUILD_TYPE.capitalized()}UnitTest") + } + } } tasks.register("runAcceptanceTests") { diff --git a/buildSrc/src/main/kotlin/scripts/quality.gradle.kts b/buildSrc/src/main/kotlin/scripts/quality.gradle.kts index 4ca42df005a..5f74cce755f 100644 --- a/buildSrc/src/main/kotlin/scripts/quality.gradle.kts +++ b/buildSrc/src/main/kotlin/scripts/quality.gradle.kts @@ -25,7 +25,6 @@ import io.gitlab.arturbosch.detekt.DetektCreateBaselineTask plugins { id("com.android.application") apply false id("io.gitlab.arturbosch.detekt") - id("org.jetbrains.kotlinx.kover") } dependencies { @@ -47,11 +46,11 @@ val detektAll by tasks.registering(Detekt::class) { val outputFile = "$buildDir/staticAnalysis/index.html" - setSource(files(projectDir)) + setSource(files(rootDir)) config.setFrom("$rootDir/config/detekt/detekt.yml") include("**/*.kt") - exclude("**/*.kts", "**/build/**", "/buildSrc") + exclude("**/*.kts", "**/build/**", "/buildSrc", "/kalium", "/template") baseline.set(file("$rootDir/config/detekt/baseline.xml")) @@ -72,12 +71,12 @@ tasks.withType(DetektCreateBaselineTask::class) { buildUponDefaultConfig.set(true) ignoreFailures.set(true) parallel.set(true) - setSource(files(projectDir)) + setSource(files(rootDir)) config.setFrom(files("$rootDir/config/detekt/detekt.yml")) baseline.set(file("$rootDir/config/detekt/baseline.xml")) include("**/*.kt") - exclude("**/*.kts", "**/build/**", "/buildSrc") + exclude("**/*.kts", "**/build/**", "/buildSrc", "/kalium", "/template") } tasks.register("staticCodeAnalysis") { @@ -90,44 +89,3 @@ tasks.register("testCoverage") { description = "Reports code coverage on tests within the Wire Android codebase." dependsOn("koverXmlReport") } - -koverReport { - defaults { - mergeWith("devDebug") - - filters { - excludes { - classes( - "*Fragment", - "*Fragment\$*", - "*Activity", - "*Activity\$*", - "*.databinding.*", - "*.BuildConfig", - "**/R.class", - "**/R\$*.class", - "**/Manifest*.*", - "**/Manifest$*.class", - "**/*Test*.*", - "*NavArgs*", - "*ComposableSingletons*", - "*_HiltModules*", - "*Hilt_*", - ) - packages( - "hilt_aggregated_deps", - "com.wire.android.di", - "dagger.hilt.internal.aggregatedroot.codegen", - "com.wire.android.ui.home.conversations.mock", - ) - annotatedBy( - "*Generated*", - "*HomeNavGraph*", - "*Destination*", - "*Composable*", - "*Preview*", - ) - } - } - } -} diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml index 8ef1f2885cf..036cb7bb572 100644 --- a/config/detekt/baseline.xml +++ b/config/detekt/baseline.xml @@ -9,7 +9,6 @@ ArgumentListWrapping:CodeTextField.kt$( value = value, onValueChange = { val textDigits = it.text.filter { it.isDigit() } // don't allow characters other than digits to be entered .let { it.substring(0, min(codeLength, it.length)) } // don't allow more digits than required onValueChange( CodeFieldValue( text = TextFieldValue(text = textDigits, selection = TextRange(textDigits.length)), isFullyFilled = textDigits.length == codeLength ) ) }, enabled = enabled, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, autoCorrect = false, imeAction = ImeAction.Done), keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() }), interactionSource = interactionSource, decorationBox = { Row(horizontalArrangement = Arrangement.SpaceBetween) { repeat(codeLength) { index -> if (index != 0) Spacer(modifier = Modifier .weight(1f, fill = false) .width(maxHorizontalSpacing)) Digit( char = value.text.getOrNull(index), shape = shape, colors = colors, textStyle = textStyle, selected = index == value.text.length, state = state ) } } }) ArgumentListWrapping:CodeTextField.kt$(modifier = Modifier .weight(1f, fill = false) .width(maxHorizontalSpacing)) ArgumentListWrapping:ConversationMessagesViewModelArrangement.kt$ConversationMessagesViewModelArrangement$( "key", assetMimeType, assetDataPath, assetSize, assetName, AttachmentType.fromMimeTypeString(assetMimeType) ) - ArgumentListWrapping:ConversationScreen.kt$( contentAlignment = Alignment.BottomEnd, modifier = Modifier .fillMaxSize() .background(color = colorsScheme().backgroundVariant), content = { LazyColumn( state = lazyListState, reverseLayout = true, // calculating bottom padding to have space for [UsersTypingIndicator] contentPadding = PaddingValues( bottom = dimensions().typingIndicatorHeight - dimensions().messageItemBottomPadding ), modifier = Modifier .fillMaxSize() ) { items( count = lazyPagingMessages.itemCount, key = lazyPagingMessages.itemKey { it.header.messageId }, contentType = lazyPagingMessages.itemContentType { it } ) { index -> val message: UIMessage = lazyPagingMessages[index] ?: return@items Box( contentAlignment = Alignment.Center, modifier = Modifier .fillMaxWidth() .height(dimensions().spacing56x), ) { WireCircularProgressIndicator( progressColor = MaterialTheme.wireColorScheme.secondaryText, size = dimensions().spacing24x ) } val showAuthor = rememberShouldShowHeader(index, message, lazyPagingMessages) val useSmallBottomPadding = rememberShouldHaveSmallBottomPadding(index, message, lazyPagingMessages) when (message) { is UIMessage.Regular -> { MessageItem( message = message, conversationDetailsData = conversationDetailsData, showAuthor = showAuthor, useSmallBottomPadding = useSmallBottomPadding, audioMessagesState = audioMessagesState, assetStatus = assetStatuses[message.header.messageId]?.transferStatus, onAudioClick = onAudioItemClicked, onChangeAudioPosition = onChangeAudioPosition, onLongClicked = onShowEditingOption, onAssetMessageClicked = onAssetItemClicked, onImageMessageClicked = onImageFullScreenMode, onOpenProfile = onOpenProfile, onReactionClicked = onReactionClicked, onResetSessionClicked = onResetSessionClicked, onSelfDeletingMessageRead = onSelfDeletingMessageRead, onFailedMessageCancelClicked = onFailedMessageCancelClicked, onFailedMessageRetryClicked = onFailedMessageRetryClicked, onLinkClick = onLinkClick, onReplyClickable = Clickable( enabled = true, onClick = { onNavigateToReplyOriginalMessage(message) } ), isSelectedMessage = (message.header.messageId == selectedMessageId), isInteractionAvailable = interactionAvailability == InteractionAvailability.ENABLED, ) } is UIMessage.System -> SystemMessageItem( message = message, onFailedMessageCancelClicked = onFailedMessageCancelClicked, onFailedMessageRetryClicked = onFailedMessageRetryClicked, onSelfDeletingMessageRead = onSelfDeletingMessageRead, isInteractionAvailable = interactionAvailability == InteractionAvailability.ENABLED, ) } } } JumpToLastMessageButton(lazyListState = lazyListState) }) ArgumentListWrapping:ConversationScreen.kt$( messageId = compositionState.editMessageId, editMessageText = compositionState.messageText, mentions = compositionState.selectedMentions.map { it.intoMessageMention() }) ArgumentListWrapping:ConversationTopAppBar.kt$( title = { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() // TopAppBar adds TopAppBarHorizontalPadding = 4.dp to each element, so we need to offset it to retain the desired // spacing between navigation icon button and avatar according to the designs .offset(x = -dimensions().spacing4x) .clip(RoundedCornerShape(MaterialTheme.wireDimensions.buttonCornerSize)) .clickable(onClick = onDropDownClick, enabled = isDropDownEnabled && isInteractionEnabled) ) { val conversationAvatar: ConversationAvatar = conversationInfoViewState.conversationAvatar Avatar(conversationAvatar, conversationInfoViewState) Text( text = conversationInfoViewState.conversationName.asString(), style = MaterialTheme.wireTypography.title02, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(weight = 1f, fill = false) ) ConversationVerificationIcons( conversationInfoViewState.protocolInfo, conversationInfoViewState.mlsVerificationStatus, conversationInfoViewState.proteusVerificationStatus ) if (conversationInfoViewState.legalHoldStatus == Conversation.LegalHoldStatus.ENABLED) { HorizontalSpace.x4() LegalHoldIndicator() } if (isDropDownEnabled && isInteractionEnabled) { Icon( painter = painterResource(id = R.drawable.ic_dropdown_icon), contentDescription = stringResource(R.string.content_description_drop_down_icon) ) } } }, navigationIcon = { NavigationIconButton(NavigationIconType.Back, onBackButtonClick) }, actions = { Row( horizontalArrangement = Arrangement.spacedBy(dimensions().spacing4x), modifier = Modifier.padding(end = dimensions().spacing4x) ) { if (isSearchEnabled) { WireSecondaryIconButton( onButtonClicked = onSearchButtonClick, iconResource = R.drawable.ic_search, contentDescription = R.string.content_description_conversation_search_icon, minSize = dimensions().buttonSmallMinSize, minClickableSize = DpSize( dimensions().buttonSmallMinSize.width, dimensions().buttonMinClickableSize.height ), ) } CallControlButton( hasOngoingCall = hasOngoingCall, onJoinCallButtonClick = onJoinCallButtonClick, onPhoneButtonClick = onPhoneButtonClick, isCallingEnabled = isInteractionEnabled, onPermissionPermanentlyDenied = onPermissionPermanentlyDenied, ) } }, colors = TopAppBarDefaults.centerAlignedTopAppBarColors( containerColor = MaterialTheme.colorScheme.background, titleContentColor = MaterialTheme.colorScheme.onBackground, actionIconContentColor = MaterialTheme.colorScheme.onBackground, navigationIconContentColor = MaterialTheme.colorScheme.onBackground ) ) ArgumentListWrapping:ConversationsNavigationItem.kt$ConversationsNavigationItem.All$( R.drawable.ic_conversation, R.string.conversations_all_tab_title, "conversations_all") @@ -23,7 +22,6 @@ ArgumentListWrapping:EditGuestAccessScreen.kt$( onConfirm = ::onGuestDialogConfirm, onDialogDismiss = ::onGuestDialogDismiss ) ArgumentListWrapping:EditGuestAccessScreen.kt$( onConfirm = ::removeGuestLink, onDialogDismiss = ::onRevokeDialogDismiss ) ArgumentListWrapping:EditSelfDeletingMessagesScreen.kt$( topBar = { WireCenterAlignedTopAppBar( elevation = scrollState.rememberTopBarElevationState().value, onNavigationPressed = navigator::navigateBack, title = stringResource(id = R.string.self_deleting_messages_title) ) }) - ArgumentListWrapping:Extensions.kt$( TimeZone.currentSystemDefault()) ArgumentListWrapping:FileUtil.kt$( this, getProviderAuthority(), it ) ArgumentListWrapping:FileUtil.kt$( this, getProviderAuthority(), path.toFile(), assetDisplayName ) ArgumentListWrapping:GetE2EICertificateUI.kt$( context.getActivity()!!.activityResultRegistry, forceLoginFlow = true ) @@ -58,7 +56,6 @@ ArgumentListWrapping:MarkdownHelperTest.kt$MarkdownHelperTest$(TableRow() .apply { appendChild(TableCell().apply { appendChild(Text("Cell")) }) }) ArgumentListWrapping:MarkdownHelperTest.kt$MarkdownHelperTest$(TableRow() .apply { appendChild(TableCell().apply { appendChild(Text("Header")) }) }) ArgumentListWrapping:MessageExpiration.kt$<no name provided>$( when (type) { StringResourceType.WEEKS -> R.plurals.weeks_left StringResourceType.DAYS -> R.plurals.days_left StringResourceType.HOURS -> R.plurals.hours_left StringResourceType.MINUTES -> R.plurals.minutes_left StringResourceType.SECONDS -> R.plurals.seconds_left }, quantity, quantity ) - ArgumentListWrapping:MessageItem.kt$(enabled = true, onClick = { if (isContentClickable) { onMessageClick(message.header.messageId) } }, onLongClick = remember(message) { { if (!isContentClickable && !message.isDeleted) { onLongClicked(message) } } } ) ArgumentListWrapping:MessagePreviewContentMapper.kt$( userUIText, UIText.StringResource( if (isSelfMessage) R.string.last_message_self_user_shared_asset else R.string.last_message_other_user_shared_asset ) ) ArgumentListWrapping:MessagePreviewContentMapperTest.kt$MessagePreviewContentMapperTest$( content = MessagePreviewContent.WithUser.Asset("admin", AssetType.IMAGE), isSelfMessage = false ) ArgumentListWrapping:MessagePreviewContentMapperTest.kt$MessagePreviewContentMapperTest$( content = MessagePreviewContent.WithUser.Asset("admin", AssetType.IMAGE), isSelfMessage = true ) @@ -113,7 +110,6 @@ ArgumentListWrapping:SettingsScreen.kt$("AppLockConfig " + "isAppLockEditable: ${settingsState.isAppLockEditable} isAppLockEnabled: ${settingsState.isAppLockEnabled}") ArgumentListWrapping:SystemMessageContentMapperTest.kt$SystemMessageContentMapperTest$( listOf(userId2, userId3), MessageContent.MemberChange.FailedToAdd.Type.Unknown ) ArgumentListWrapping:SystemMessageItem.kt$( res, normalStyle, boldStyle, normalColor, boldColor, errorColor, isErrorString, if (usersCount > SINGLE_EXPANDABLE_THRESHOLD) expanded else true ) - ArgumentListWrapping:Theme.kt$( LocalSnackbarHostState provides remember { SnackbarHostState() }, LocalNavigator provides rememberNavigator {}) ArgumentListWrapping:UsersTypingIndicator.kt$( initialValue = -10f, targetValue = -2f, animationSpec = infiniteRepeatable( animation = tween(ANIMATION_SPEED_MILLIS, easing = FastOutSlowInEasing), repeatMode = RepeatMode.Reverse ), label = infiniteTransition.label ) ArgumentListWrapping:WelcomeScreen.kt$( isThereActiveSession = false, maxAccountsReached = false, state = ServerConfig.DEFAULT, navigateBack = {}, navigate = {}) ArgumentListWrapping:WireActivity.kt$WireActivity$( userId = it, actions = NavigationSwitchAccountActions(navigate), onComplete = { navigate(NavigationCommand(SelfDevicesScreenDestination)) }) @@ -130,6 +126,9 @@ ArgumentListWrapping:WireNotificationManagerTest.kt$WireNotificationManagerTest$( newNotifications = any(), userId = any(), userName = TestUser.SELF_USER.handle!! ) ArgumentListWrapping:WireNotificationManagerTest.kt$WireNotificationManagerTest.Companion$( "message_id", LocalNotificationMessageAuthor("author", null), Instant.DISTANT_FUTURE, "testing text" ) ArgumentListWrapping:WireNotificationManagerTest.kt$WireNotificationManagerTest.Companion$( id, "name_${id.value}", messages, true ) + ArgumentListWrapping:WirePrimaryIconButton.kt$(40.dp, 40.dp) + ArgumentListWrapping:WirePrimaryIconButton.kt$(48.dp, 48.dp) + ArgumentListWrapping:WirePrimaryIconButton.kt$({}, false, com.google.android.material.R.drawable.m3_password_eye, 0, CircleShape, DpSize(40.dp, 40.dp), DpSize(48.dp, 48.dp)) ArgumentListWrapping:WireTextField.kt$( start = if (leadingIcon == null) 16.dp else 0.dp, end = if (trailingOrStateIcon == null) 16.dp else 0.dp, top = 2.dp, bottom = 2.dp ) ArgumentListWrapping:WireTextFieldDefaults.kt$<no name provided>$( when (state) { WireTextFieldState.Disabled -> disabledTextColor is WireTextFieldState.Error -> errorColor WireTextFieldState.Success -> successColor else -> textColor }) ArgumentListWrapping:WireTextFieldDefaults.kt$<no name provided>$( when (state) { is WireTextFieldState.Error -> errorColor else -> descriptionColor }) @@ -155,11 +154,15 @@ CommentSpacing:FeatureVisibilityFlags.kt$//long press on images → edit CommentSpacing:FeatureVisibilityFlags.kt$//long press on text → edit entry CommentSpacing:LastConversationEvent.kt$CallTime$//TODO: This could be a Long timestamp, + CommentSpacing:Theme.kt$//import com.wire.android.navigation.rememberNavigator CommentSpacing:ThemeUtils.kt$ScreenSizeDependent$//sw320dp CommentSpacing:ThemeUtils.kt$ScreenSizeDependent$//sw480dp CommentSpacing:ThemeUtils.kt$ScreenSizeDependent$//sw600dp CommentSpacing:ThemeUtils.kt$ScreenSizeDependent$//sw840dp + CommentSpacing:Versionizer.kt$Versionizer.Companion$//This is Google Play Max Version Code allowed + CommentSpacing:Versionizer.kt$Versionizer.Companion$//https://developer.android.com/studio/publish/versioning CommentWrapping:ConversationScreen.kt$/* do nothing */ + CommentWrapping:DrawingCanvasComponent.kt$/*, bitmap*/ CommentWrapping:FileUtil.kt$/* description = */ CommentWrapping:FileUtil.kt$/* isMediaScannerScannable = */ CommentWrapping:FileUtil.kt$/* length = */ @@ -184,6 +187,9 @@ KdocWrapping:HomeScreen.kt$/** TODO: Show a dialog rationale explaining why the permission is needed **/ KdocWrapping:MediaGalleryScreen.kt$/** Nothing to do **/ KdocWrapping:RecordAudioComponent.kt$/** Nothing to do **/ + MagicNumber:Versionizer.kt$Versionizer$10 + MaxLineLength:WirePrimaryIconButton.kt$WirePrimaryIconButton({}, false, com.google.android.material.R.drawable.m3_password_eye, 0, CircleShape, DpSize(40.dp, 40.dp), DpSize(48.dp, 48.dp)) + MaximumLineLength:WirePrimaryIconButton.kt$ MultiLineIfElse:AccountSwitchUseCase.kt$AccountSwitchUseCase$appLogger.i("$TAG No next account to switch to") MultiLineIfElse:AccountSwitchUseCase.kt$AccountSwitchUseCase$appLogger.i("$TAG Switching to next account: ${nextSessionId.toLogString()}") MultiLineIfElse:AdditionalOptionButton.kt$WireButtonState.Disabled @@ -306,11 +312,8 @@ MultiLineIfElse:MediaGalleryScreenState.kt$MediaGalleryScreenState$modalBottomSheetState.show() MultiLineIfElse:MessageComposeActions.kt$SelfDeletingMessageAction( isSelected = isSelfDeletingActive, onButtonClicked = onSelfDeletionOptionButtonClicked ) MultiLineIfElse:MessageCompositionHolder.kt$MessageCompositionHolder$Pair(rangeEnd, rangeEnd) - MultiLineIfElse:MessageExpiration.kt$SelfDeletionTimerHelper$selfDeletionStatus.selfDeletionStartDate + expireAfter MultiLineIfElse:MessageExpiration.kt$SelfDeletionTimerHelper.SelfDeletionTimerState.Expirable$1.toDuration(durationUnit) MultiLineIfElse:MessageExpiration.kt$SelfDeletionTimerHelper.SelfDeletionTimerState.Expirable$timeLeftForDurationUnit - MultiLineIfElse:MessageItem.kt$null - MultiLineIfElse:MessageItem.kt$remember(message) { if (isAvailable) { { onLongClicked(message) } } else { null } } MultiLineIfElse:MessagePreviewContentMapper.kt$R.string.last_message_other_changed_conversation_name MultiLineIfElse:MessagePreviewContentMapper.kt$R.string.last_message_other_user_joined_conversation MultiLineIfElse:MessagePreviewContentMapper.kt$R.string.last_message_other_user_knock @@ -382,9 +385,6 @@ MultiLineIfElse:StyledStringUtil.kt$bold { append(text) } MultiLineIfElse:SystemMessageContentMapper.kt$SystemMessageContentMapper$others(members.map { mapMemberName(user = userList.findUser(userId = it), type = SelfNameType.ResourceLowercase) }) MultiLineIfElse:SystemMessageContentMapper.kt$SystemMessageContentMapper$self() - MultiLineIfElse:SystemMessageItem.kt$dimensions().systemMessageIconLargeSize - MultiLineIfElse:SystemMessageItem.kt$dimensions().systemMessageIconSize - MultiLineIfElse:Theme.kt$arrayOf( LocalSnackbarHostState provides remember { SnackbarHostState() }, LocalNavigator provides rememberNavigator {}) MultiLineIfElse:Theme.kt$emptyArray() MultiLineIfElse:UIMessage.kt$UIMessageContent.SystemMessage.ConversationDegraded$R.drawable.ic_conversation_degraded_mls MultiLineIfElse:UIMessage.kt$UIMessageContent.SystemMessage.ConversationDegraded$R.drawable.ic_shield_holo @@ -433,6 +433,8 @@ MultiLineIfElse:WireTextField.kt$it MultiLineIfElse:WriteStorageRequestFlow.kt$onGranted() NoBlankLineBeforeRbrace:CreateAccountEmailViewState.kt$CreateAccountEmailViewState$ + NoBlankLineBeforeRbrace:DrawingCanvasBottomSheet.kt$ + NoBlankLineBeforeRbrace:DrawingCanvasViewModel.kt$DrawingCanvasViewModel$ NoBlankLineBeforeRbrace:EmailComposer.kt$EmailComposer$ NoBlankLineBeforeRbrace:FakeKaliumFileSystem.kt$FakeKaliumFileSystem$ NoBlankLineBeforeRbrace:ISOFormatter.kt$ISOFormatter$ @@ -459,7 +461,9 @@ NoConsecutiveBlankLines:ScreenStateObserver.kt$ NoConsecutiveBlankLines:SearchPeopleScreenState.kt$ NoConsecutiveBlankLines:SearchTopBar.kt$ + NoConsecutiveBlankLines:StartupBenchmarkWithLogin.kt$StartupBenchmarkWithLogin$ NoConsecutiveBlankLines:SurfaceBackgroundWrapper.kt$ + NoConsecutiveBlankLines:VersionizerTest.kt$VersionizerTest$ NoConsecutiveBlankLines:WireTextFieldDefaults.kt$ NoMultipleSpaces:ApiVersioningDialogs.kt$ NoMultipleSpaces:ShakeAnimation.kt$ @@ -481,10 +485,12 @@ NoTrailingSpaces:ScalaServerConfigDAOTest.kt$ScalaServerConfigDAOTest.Arrangement$ NoUnusedImports:ApiVersioningDialogs.kt$com.wire.android.ui.server.ApiVersioningDialogs.kt NoUnusedImports:CreateAccountDetailsScreen.kt$import com.wire.android.ui.common.scaffold.WireScaffold + NoUnusedImports:DateTimeUtilKtTest.kt$com.wire.android.util.DateTimeUtilKtTest.kt NoUnusedImports:SearchPeopleScreenState.kt$com.wire.android.ui.home.conversations.search.SearchPeopleScreenState.kt NoUnusedImports:SpeakerButton.kt$com.wire.android.ui.calling.controlbuttons.SpeakerButton.kt - NoUnusedImports:WireTypography.kt$com.wire.android.ui.theme.WireTypography.kt NoUnusedImports:ZoomableImage.kt$com.wire.android.ui.home.gallery.ZoomableImage.kt + NoWildcardImports:ExampleInstrumentedTest.kt$import org.junit.Assert.* + NoWildcardImports:ExampleUnitTest.kt$import org.junit.Assert.* ParameterListWrapping:WireButtonDefaults.kt$( enabled: Color, onEnabled: Color, enabledOutline: Color, disabled: Color, onDisabled: Color, disabledOutline: Color, selected: Color, onSelected: Color, selectedOutline: Color, error: Color, onError: Color, errorOutline: Color, positive: Color, onPositive: Color, positiveOutline: Color, ripple: Color ) ParameterListWrapping:WireTypography.kt$WireTypography$( val title01: TextStyle, val title02: TextStyle, val title03: TextStyle, val title04: TextStyle, val body01: TextStyle, val body02: TextStyle, val body03: TextStyle, val body04: TextStyle, val body05: TextStyle, val button01: TextStyle, val button02: TextStyle, val button03: TextStyle, val button04: TextStyle, val button05: TextStyle, val label01: TextStyle, val label02: TextStyle, val label03: TextStyle, val label04: TextStyle, val label05: TextStyle, val badge01: TextStyle, val subline01: TextStyle, val code01: TextStyle ) ParameterWrapping:ConversationScreen.kt$groupDetailsScreenResultRecipient: ResultRecipient<GroupConversationDetailsScreenDestination, GroupConversationDetailsNavBackArgs> @@ -757,10 +763,8 @@ SpacingBetweenDeclarationsWithAnnotations:WireColorPalette.kt$WireColorPalette$@Stable val LightRed900 = Color(0xFF3A0006) SpacingBetweenDeclarationsWithAnnotations:WirePrimaryIconButton.kt$@Preview @Composable fun PreviewWirePrimaryIconButtonLoading() SpacingBetweenDeclarationsWithAnnotations:WirePrimaryIconButton.kt$@Preview @Composable fun PreviewWirePrimaryIconButtonRound() - SpacingBetweenDeclarationsWithAnnotations:WireSecondaryIconButton.kt$@Preview @Composable fun PreviewWireSecondaryIconButtonLoading() - SpacingBetweenDeclarationsWithAnnotations:WireSecondaryIconButton.kt$@Preview @Composable fun PreviewWireSecondaryIconButtonRound() - SpacingBetweenDeclarationsWithAnnotations:WireTertiaryIconButton.kt$@Preview @Composable fun PreviewWireTertiaryIconButtonLoading() - SpacingBetweenDeclarationsWithAnnotations:WireTertiaryIconButton.kt$@Preview @Composable fun PreviewWireTertiaryIconButtonRound() + SpreadOperator:ViewModelScopedPreview.kt$ViewModelScopedPreviewProcessor$(aggregating = true, *items.mapNotNull { it.containingFile }.toTypedArray()) + SpreadOperator:ViewModelScopedPreview.kt$ViewModelScopedPreviewProcessor$(aggregating = true, *listOfNotNull(item.containingFile).toTypedArray()) UnnecessaryParenthesesBeforeTrailingLambda:ChangeHandleScreen.kt$() UnnecessaryParenthesesBeforeTrailingLambda:EndOngoingCallReceiver.kt$EndOngoingCallReceiver$() UnnecessaryParenthesesBeforeTrailingLambda:GroupOptionsScreen.kt$() @@ -781,6 +785,7 @@ UnusedParameter:WireItemLabel.kt$minHeight: Dp = dimensions().badgeSmallMinSize.height UnusedParameter:WireItemLabel.kt$minWidth: Dp = dimensions().badgeSmallMinSize.height UnusedParameter:ZoomableImage.kt$imageScale: Float = 1.0f + UnusedPrivateProperty:AndroidExampleView.kt$AndroidExampleView$private val view: View = View(context) UnusedPrivateProperty:ConnectionPolicyManagerTest.kt$ConnectionPolicyManagerTest.Companion$private val USER_ID_2 = UserId("user2", "domain2") UnusedPrivateProperty:ConversationListViewModelTest.kt$ConversationListViewModelTest.Companion$private val testConversations = TestConversationDetails.CONVERSATION_ONE_ONE UnusedPrivateProperty:DropDownMentionsSuggestions.kt$i @@ -836,14 +841,13 @@ Wrapping:DeleteMessageDialog.kt$ Wrapping:DeviceDetailsViewModel.kt$DeviceDetailsViewModel$-> Wrapping:DeviceItem.kt$( + Wrapping:DrawingCanvasViewModel.kt$DrawingCanvasViewModel$( currentPath = DrawingPathProperties().apply { strokeWidth = state.currentPath.strokeWidth color = state.currentPath.color drawMode = state.currentPath.drawMode }, pathsUndone = emptyList(), currentPosition = Offset.Unspecified, drawingMotionEvent = DrawingMotionEvent.Idle ) Wrapping:DropDownMentionsSuggestions.kt$( Wrapping:E2EIEnrollmentScreen.kt$viewModel.onCancelEnrollmentClicked(NavigationSwitchAccountActions(navigator::navigate)) Wrapping:EditGuestAccessScreen.kt${ isPasswordProtected -> coroutineScope.launch { sheetState.hide() } if (isPasswordProtected) { navigator.navigate( NavigationCommand( CreatePasswordProtectedGuestLinkScreenDestination( CreatePasswordGuestLinkNavArgs( conversationId = editGuestAccessViewModel.conversationId ) ) ) ) } else { editGuestAccessViewModel.onRequestGuestRoomLink() } } Wrapping:EditMessageMenuItems.kt${ emoji: String -> hideEditMessageMenu { onReactionClick(message.header.messageId, emoji) } } Wrapping:EditSelfDeletingMessagesScreen.kt$( Wrapping:EditSelfDeletingMessagesViewModel.kt$EditSelfDeletingMessagesViewModel$( - Wrapping:Extensions.kt$( - Wrapping:Extensions.kt$flow.flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED) Wrapping:FeatureFlagNotificationViewModelTest.kt$FeatureFlagNotificationViewModelTest.Arrangement$coreLogic.getSessionScope(any()).markGuestLinkFeatureFlagAsNotChanged Wrapping:FileManager.kt$FileManager$context.getFileName(it) Wrapping:GroupConversationDetailsScreen.kt$GroupConversationDetailsTabItem.entries.size @@ -887,7 +891,6 @@ Wrapping:MessageDetailsScreen.kt$derivedStateOf { lazyListStates[currentTabState].topBarElevation(maxAppBarElevation) } Wrapping:MessageDetailsScreen.kt$lazyListStates[currentTabState].topBarElevation(maxAppBarElevation) Wrapping:MessageExpiration.kt$<no name provided>$( when (type) { StringResourceType.WEEKS -> R.plurals.weeks_left StringResourceType.DAYS -> R.plurals.days_left StringResourceType.HOURS -> R.plurals.hours_left StringResourceType.MINUTES -> R.plurals.minutes_left StringResourceType.SECONDS -> R.plurals.seconds_left }, quantity, quantity ) - Wrapping:MessageItem.kt$( Wrapping:MessageMapperTest.kt$MessageMapperTest$( Wrapping:MessagePreviewContentMapper.kt$( userUIText, UIText.StringResource( if (isSelfMessage) R.string.last_message_self_user_shared_asset else R.string.last_message_other_user_shared_asset ) ) Wrapping:MigrateActiveAccountsUseCase.kt$MigrateActiveAccountsUseCase$it.users(listOf(activeAccount.id)) @@ -938,7 +941,6 @@ Wrapping:SingleUserMigrationWorker.kt$it.firstOrNull()?.state == WorkInfo.State.RUNNING Wrapping:SystemMessageContentMapper.kt$SystemMessageContentMapper$UIText.DynamicString(it) Wrapping:TextWithLearnMore.kt$append("This is text with a learn more link") - Wrapping:Theme.kt$( Wrapping:TypingIndicatorViewModelTest.kt$TypingIndicatorViewModelTest.Arrangement$savedStateHandle.scopedArgs<TypingIndicatorArgs>() Wrapping:UIMessage.kt$DeliveryStatusContent.PartialDelivery$failedRecipients.filter { it !in noClients.values.flatten() }.toImmutableList() Wrapping:UIMessage.kt$DeliveryStatusContent.PartialDelivery$it !in noClients.values.flatten() @@ -953,6 +955,7 @@ Wrapping:WireButton.kt$( Wrapping:WireNotificationManagerTest.kt$WireNotificationManagerTest$arrangement.connectionPolicyManager.handleConnectionOnPushNotification(TEST_AUTH_TOKEN.userId, any()) Wrapping:WireNotificationManagerTest.kt$WireNotificationManagerTest.Arrangement$markMessagesAsNotified(any<MarkMessagesAsNotifiedUseCase.UpdateTarget.SingleConversation>()) + Wrapping:WirePrimaryIconButton.kt$ Wrapping:WireTextFieldDefaults.kt$<no name provided>$( diff --git a/core/ui-common/.gitignore b/core/ui-common/.gitignore new file mode 100644 index 00000000000..42afabfd2ab --- /dev/null +++ b/core/ui-common/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/ui-common/build.gradle.kts b/core/ui-common/build.gradle.kts new file mode 100644 index 00000000000..5262f93c7f0 --- /dev/null +++ b/core/ui-common/build.gradle.kts @@ -0,0 +1,34 @@ +plugins { + id(libs.plugins.wire.android.library.get().pluginId) + id(libs.plugins.wire.kover.get().pluginId) +} + +android { + namespace = "com.wire.android.ui.common" +} + +dependencies { + implementation("com.wire.kalium:kalium-logic") + implementation(libs.androidx.core) + implementation(libs.androidx.appcompat) + implementation(libs.material) + + val composeBom = platform(libs.compose.bom) + implementation(composeBom) + implementation(libs.compose.ui) + implementation(libs.compose.foundation) + implementation(libs.compose.ui.graphics) + implementation(libs.compose.material.core) + implementation(libs.compose.material3) + implementation(libs.compose.navigation) + implementation(libs.compose.material.icons) + implementation(libs.compose.ui.preview) + debugImplementation(libs.compose.ui.tooling) + + implementation(libs.accompanist.systemUI) + implementation(libs.visibilityModifiers) + + testImplementation(libs.junit4) + androidTestImplementation(libs.androidx.test.extJunit) + androidTestImplementation(libs.androidx.espresso.core) +} diff --git a/core/ui-common/consumer-rules.pro b/core/ui-common/consumer-rules.pro new file mode 100644 index 00000000000..e69de29bb2d diff --git a/core/ui-common/proguard-rules.pro b/core/ui-common/proguard-rules.pro new file mode 100644 index 00000000000..481bb434814 --- /dev/null +++ b/core/ui-common/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/ui-common/src/androidTest/java/com/wire/android/ExampleInstrumentedTest.kt b/core/ui-common/src/androidTest/java/com/wire/android/ExampleInstrumentedTest.kt new file mode 100644 index 00000000000..b8949a2648a --- /dev/null +++ b/core/ui-common/src/androidTest/java/com/wire/android/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.wire.android + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.wire.android.test", appContext.packageName) + } +} diff --git a/core/ui-common/src/main/AndroidManifest.xml b/core/ui-common/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..44b6ed94db1 --- /dev/null +++ b/core/ui-common/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + diff --git a/app/src/main/kotlin/com/wire/android/model/Clickable.kt b/core/ui-common/src/main/kotlin/com/wire/android/model/Clickable.kt similarity index 100% rename from app/src/main/kotlin/com/wire/android/model/Clickable.kt rename to core/ui-common/src/main/kotlin/com/wire/android/model/Clickable.kt diff --git a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/Extensions.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/Extensions.kt new file mode 100644 index 00000000000..e6af44b0eb5 --- /dev/null +++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/Extensions.kt @@ -0,0 +1,56 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.ui.common + +import android.widget.Toast +import androidx.compose.material3.LocalContentColor +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext +import com.wire.android.model.ClickBlockParams +import com.wire.android.util.LocalSyncStateObserver + +@Composable +fun Tint(contentColor: Color, content: @Composable () -> Unit) { + CompositionLocalProvider(LocalContentColor provides contentColor, content = content) +} + +@Composable +fun ImageVector.Icon(modifier: Modifier = Modifier): @Composable (() -> Unit) = + { androidx.compose.material3.Icon(imageVector = this, contentDescription = "", modifier = modifier) } + +@Composable +fun rememberClickBlockAction(clickBlockParams: ClickBlockParams, clickAction: () -> Unit): () -> Unit { + val syncStateObserver = LocalSyncStateObserver.current + val context = LocalContext.current + return remember(clickBlockParams, syncStateObserver, clickAction) { + { + when { + clickBlockParams.blockWhenConnecting && syncStateObserver.isConnecting -> + Toast.makeText(context, context.getString(R.string.label_wait_until_connected), Toast.LENGTH_SHORT).show() + clickBlockParams.blockWhenSyncing && syncStateObserver.isSyncing -> + Toast.makeText(context, context.getString(R.string.label_wait_until_synchronised), Toast.LENGTH_SHORT).show() + else -> clickAction() + } + } + } +} diff --git a/core/ui-common/src/main/kotlin/com/wire/android/ui/common/ThemeExt.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/ThemeExt.kt new file mode 100644 index 00000000000..fcdf718ff53 --- /dev/null +++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/ThemeExt.kt @@ -0,0 +1,33 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.ui.common + +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import com.wire.android.ui.theme.wireColorScheme +import com.wire.android.ui.theme.wireDimensions +import com.wire.android.ui.theme.wireTypography + +@Composable +fun dimensions() = MaterialTheme.wireDimensions + +@Composable +fun colorsScheme() = MaterialTheme.wireColorScheme + +@Composable +fun typography() = MaterialTheme.wireTypography diff --git a/app/src/main/kotlin/com/wire/android/ui/common/button/WireButton.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireButton.kt similarity index 100% rename from app/src/main/kotlin/com/wire/android/ui/common/button/WireButton.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireButton.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/common/button/WireButtonDefaults.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireButtonDefaults.kt similarity index 100% rename from app/src/main/kotlin/com/wire/android/ui/common/button/WireButtonDefaults.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireButtonDefaults.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/common/button/WireCheckBoxDefaults.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireCheckBoxDefaults.kt similarity index 100% rename from app/src/main/kotlin/com/wire/android/ui/common/button/WireCheckBoxDefaults.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireCheckBoxDefaults.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/common/button/WireItemLabel.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireItemLabel.kt similarity index 100% rename from app/src/main/kotlin/com/wire/android/ui/common/button/WireItemLabel.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireItemLabel.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/common/button/WirePrimaryButton.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WirePrimaryButton.kt similarity index 100% rename from app/src/main/kotlin/com/wire/android/ui/common/button/WirePrimaryButton.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WirePrimaryButton.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/common/button/WirePrimaryIconButton.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WirePrimaryIconButton.kt similarity index 90% rename from app/src/main/kotlin/com/wire/android/ui/common/button/WirePrimaryIconButton.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WirePrimaryIconButton.kt index a5bdedfd903..911c3734876 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/button/WirePrimaryIconButton.kt +++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WirePrimaryIconButton.kt @@ -34,7 +34,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp -import com.wire.android.R import com.wire.android.model.ClickBlockParams import com.wire.android.ui.common.dimensions import com.wire.android.ui.theme.wireDimensions @@ -79,15 +78,15 @@ fun WirePrimaryIconButton( @Preview @Composable fun PreviewWirePrimaryIconButton() { - WirePrimaryIconButton({}, false, R.drawable.ic_add, 0) + WirePrimaryIconButton({}, false, com.google.android.material.R.drawable.m3_password_eye, 0) } @Preview @Composable fun PreviewWirePrimaryIconButtonLoading() { - WirePrimaryIconButton({}, true, R.drawable.ic_add, 0) + WirePrimaryIconButton({}, true, com.google.android.material.R.drawable.m3_password_eye, 0) } @Preview @Composable fun PreviewWirePrimaryIconButtonRound() { - WirePrimaryIconButton({}, false, R.drawable.ic_add, 0, CircleShape, DpSize(40.dp, 40.dp), DpSize(48.dp, 48.dp)) + WirePrimaryIconButton({}, false, com.google.android.material.R.drawable.m3_password_eye, 0, CircleShape, DpSize(40.dp, 40.dp), DpSize(48.dp, 48.dp)) } diff --git a/app/src/main/kotlin/com/wire/android/ui/common/button/WireSecondaryButton.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireSecondaryButton.kt similarity index 100% rename from app/src/main/kotlin/com/wire/android/ui/common/button/WireSecondaryButton.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireSecondaryButton.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/common/button/WireSecondaryIconButton.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireSecondaryIconButton.kt similarity index 88% rename from app/src/main/kotlin/com/wire/android/ui/common/button/WireSecondaryIconButton.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireSecondaryIconButton.kt index 70b03e8592f..5c387eb85fc 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/button/WireSecondaryIconButton.kt +++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireSecondaryIconButton.kt @@ -35,7 +35,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp -import com.wire.android.R import com.wire.android.model.ClickBlockParams import com.wire.android.ui.common.dimensions import com.wire.android.ui.theme.wireDimensions @@ -82,15 +81,25 @@ fun WireSecondaryIconButton( @Preview @Composable fun PreviewWireSecondaryIconButton() { - WireSecondaryIconButton({}, false, R.drawable.ic_add, 0) + WireSecondaryIconButton({}, false, com.google.android.material.R.drawable.m3_password_eye, 0) } + @Preview @Composable fun PreviewWireSecondaryIconButtonLoading() { - WireSecondaryIconButton({}, true, R.drawable.ic_add, 0) + WireSecondaryIconButton({}, true, com.google.android.material.R.drawable.m3_password_eye, 0) } + @Preview @Composable fun PreviewWireSecondaryIconButtonRound() { - WireSecondaryIconButton({}, false, R.drawable.ic_add, 0, CircleShape, DpSize(40.dp, 40.dp), DpSize(48.dp, 48.dp)) + WireSecondaryIconButton( + {}, + false, + com.google.android.material.R.drawable.m3_password_eye, + 0, + CircleShape, + DpSize(40.dp, 40.dp), + DpSize(48.dp, 48.dp) + ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/common/button/WireTertiaryButton.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireTertiaryButton.kt similarity index 100% rename from app/src/main/kotlin/com/wire/android/ui/common/button/WireTertiaryButton.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireTertiaryButton.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/common/button/WireTertiaryIconButton.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireTertiaryIconButton.kt similarity index 88% rename from app/src/main/kotlin/com/wire/android/ui/common/button/WireTertiaryIconButton.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireTertiaryIconButton.kt index c16fc1ca36f..b86fa89d497 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/button/WireTertiaryIconButton.kt +++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/button/WireTertiaryIconButton.kt @@ -34,7 +34,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.dp -import com.wire.android.R import com.wire.android.model.ClickBlockParams import com.wire.android.ui.common.dimensions import com.wire.android.ui.theme.wireDimensions @@ -79,15 +78,25 @@ fun WireTertiaryIconButton( @Preview @Composable fun PreviewWireTertiaryIconButton() { - WireTertiaryIconButton({}, false, R.drawable.ic_add, 0) + WireTertiaryIconButton({}, false, com.google.android.material.R.drawable.m3_password_eye, 0) } + @Preview @Composable fun PreviewWireTertiaryIconButtonLoading() { - WireTertiaryIconButton({}, true, R.drawable.ic_add, 0) + WireTertiaryIconButton({}, true, com.google.android.material.R.drawable.m3_password_eye, 0) } + @Preview @Composable fun PreviewWireTertiaryIconButtonRound() { - WireTertiaryIconButton({}, false, R.drawable.ic_add, 0, CircleShape, DpSize(40.dp, 40.dp), DpSize(48.dp, 48.dp)) + WireTertiaryIconButton( + {}, + false, + com.google.android.material.R.drawable.m3_password_eye, + 0, + CircleShape, + DpSize(40.dp, 40.dp), + DpSize(48.dp, 48.dp) + ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/common/progress/CenteredCircularProgressBarIndicator.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/progress/CenteredCircularProgressBarIndicator.kt similarity index 100% rename from app/src/main/kotlin/com/wire/android/ui/common/progress/CenteredCircularProgressBarIndicator.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/common/progress/CenteredCircularProgressBarIndicator.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/common/progress/WireCircularProgressIndicator.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/progress/WireCircularProgressIndicator.kt similarity index 100% rename from app/src/main/kotlin/com/wire/android/ui/common/progress/WireCircularProgressIndicator.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/common/progress/WireCircularProgressIndicator.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/common/progress/WireLinearProgressIndicator.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/progress/WireLinearProgressIndicator.kt similarity index 100% rename from app/src/main/kotlin/com/wire/android/ui/common/progress/WireLinearProgressIndicator.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/common/progress/WireLinearProgressIndicator.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/common/snackbar/LocalSnackbarHostState.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/snackbar/LocalSnackbarHostState.kt similarity index 100% rename from app/src/main/kotlin/com/wire/android/ui/common/snackbar/LocalSnackbarHostState.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/common/snackbar/LocalSnackbarHostState.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/common/snackbar/SwipeableSnackbar.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/snackbar/SwipeableSnackbar.kt similarity index 97% rename from app/src/main/kotlin/com/wire/android/ui/common/snackbar/SwipeableSnackbar.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/common/snackbar/SwipeableSnackbar.kt index f33a1c247b0..91e126d15b2 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/snackbar/SwipeableSnackbar.kt +++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/common/snackbar/SwipeableSnackbar.kt @@ -15,6 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. */ +package com.wire.android.ui.common.snackbar import androidx.compose.animation.core.SpringSpec import androidx.compose.foundation.ExperimentalFoundationApi @@ -106,8 +107,7 @@ fun SwipeableSnackbar( .anchoredDraggable( state = state, orientation = Orientation.Horizontal - ) - .offset { IntOffset(state.requireOffset().roundToInt(), 0) } + ).offset { IntOffset(state.requireOffset().roundToInt(), 0) } ) } diff --git a/app/src/main/kotlin/com/wire/android/ui/theme/Accent.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/theme/Accent.kt similarity index 100% rename from app/src/main/kotlin/com/wire/android/ui/theme/Accent.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/theme/Accent.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/theme/Theme.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/theme/Theme.kt similarity index 89% rename from app/src/main/kotlin/com/wire/android/ui/theme/Theme.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/theme/Theme.kt index 0956ec84f7e..50f615755d2 100644 --- a/app/src/main/kotlin/com/wire/android/ui/theme/Theme.kt +++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/theme/Theme.kt @@ -18,6 +18,7 @@ package com.wire.android.ui.theme +//import com.wire.android.navigation.rememberNavigator import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SnackbarHostState @@ -28,8 +29,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.platform.LocalInspectionMode import com.google.accompanist.systemuicontroller.rememberSystemUiController -import com.wire.android.navigation.LocalNavigator -import com.wire.android.navigation.rememberNavigator import com.wire.android.ui.common.snackbar.LocalSnackbarHostState @Composable @@ -48,9 +47,12 @@ fun WireTheme( LocalWireDimensions provides wireDimensions, // we need to provide our default content color dependent on the current colorScheme, otherwise it's Color.Black LocalContentColor provides wireColorScheme.onBackground, - *if (isPreview) arrayOf( - LocalSnackbarHostState provides remember { SnackbarHostState() }, - LocalNavigator provides rememberNavigator {}) else emptyArray(), + *if (isPreview) { + arrayOf( + LocalSnackbarHostState provides remember { SnackbarHostState() }, +// LocalNavigator provides rememberNavigator {} // todo, uncomment when we have navigation module, ignore since is prevonly + ) + } else emptyArray(), ) { MaterialTheme( colorScheme = wireColorScheme.toColorScheme(), diff --git a/app/src/main/kotlin/com/wire/android/ui/theme/ThemeOption.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/theme/ThemeOption.kt similarity index 100% rename from app/src/main/kotlin/com/wire/android/ui/theme/ThemeOption.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/theme/ThemeOption.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/theme/ThemeUtils.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/theme/ThemeUtils.kt similarity index 100% rename from app/src/main/kotlin/com/wire/android/ui/theme/ThemeUtils.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/theme/ThemeUtils.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/theme/WireColorPalette.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/theme/WireColorPalette.kt similarity index 100% rename from app/src/main/kotlin/com/wire/android/ui/theme/WireColorPalette.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/theme/WireColorPalette.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/theme/WireColorScheme.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/theme/WireColorScheme.kt similarity index 100% rename from app/src/main/kotlin/com/wire/android/ui/theme/WireColorScheme.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/theme/WireColorScheme.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/theme/WireDimensions.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/theme/WireDimensions.kt similarity index 100% rename from app/src/main/kotlin/com/wire/android/ui/theme/WireDimensions.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/theme/WireDimensions.kt diff --git a/app/src/main/kotlin/com/wire/android/ui/theme/WireTypography.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/theme/WireTypography.kt similarity index 93% rename from app/src/main/kotlin/com/wire/android/ui/theme/WireTypography.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/theme/WireTypography.kt index 84f69c6b36c..884a3dec3a9 100644 --- a/app/src/main/kotlin/com/wire/android/ui/theme/WireTypography.kt +++ b/core/ui-common/src/main/kotlin/com/wire/android/ui/theme/WireTypography.kt @@ -21,11 +21,6 @@ package com.wire.android.ui.theme import androidx.compose.material3.Typography import androidx.compose.runtime.Immutable import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.font.FontStyle -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.sp import io.github.esentsov.PackagePrivate @Immutable diff --git a/app/src/main/kotlin/com/wire/android/ui/theme/WireTypographyBase.kt b/core/ui-common/src/main/kotlin/com/wire/android/ui/theme/WireTypographyBase.kt similarity index 100% rename from app/src/main/kotlin/com/wire/android/ui/theme/WireTypographyBase.kt rename to core/ui-common/src/main/kotlin/com/wire/android/ui/theme/WireTypographyBase.kt diff --git a/app/src/main/kotlin/com/wire/android/util/SyncStateObserver.kt b/core/ui-common/src/main/kotlin/com/wire/android/util/SyncStateObserver.kt similarity index 100% rename from app/src/main/kotlin/com/wire/android/util/SyncStateObserver.kt rename to core/ui-common/src/main/kotlin/com/wire/android/util/SyncStateObserver.kt diff --git a/core/ui-common/src/main/res/values-de/strings.xml b/core/ui-common/src/main/res/values-de/strings.xml new file mode 100644 index 00000000000..61733649b07 --- /dev/null +++ b/core/ui-common/src/main/res/values-de/strings.xml @@ -0,0 +1,21 @@ + + + Bitte warten Sie, bis die App synchronisiert ist + Bitte warten Sie, bis die Internetverbindung wiederhergestellt ist + diff --git a/core/ui-common/src/main/res/values/strings.xml b/core/ui-common/src/main/res/values/strings.xml new file mode 100644 index 00000000000..af8cb93cc9f --- /dev/null +++ b/core/ui-common/src/main/res/values/strings.xml @@ -0,0 +1,22 @@ + + + ui commons + Please wait until the app is synchronized + Please wait until the Internet connection is restored + diff --git a/core/ui-common/src/test/kotlin/com/wire/android/ui/common/ExampleUnitTest.kt b/core/ui-common/src/test/kotlin/com/wire/android/ui/common/ExampleUnitTest.kt new file mode 100644 index 00000000000..b0896ca1a45 --- /dev/null +++ b/core/ui-common/src/test/kotlin/com/wire/android/ui/common/ExampleUnitTest.kt @@ -0,0 +1,34 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.ui.common + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/features/sketch/.gitignore b/features/sketch/.gitignore new file mode 100644 index 00000000000..42afabfd2ab --- /dev/null +++ b/features/sketch/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/features/sketch/build.gradle.kts b/features/sketch/build.gradle.kts new file mode 100644 index 00000000000..69fd7f00ca7 --- /dev/null +++ b/features/sketch/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + id(libs.plugins.wire.android.library.get().pluginId) + id(libs.plugins.wire.kover.get().pluginId) +} + +dependencies { + implementation(libs.androidx.core) + implementation(libs.androidx.appcompat) + implementation(libs.material) + + val composeBom = platform(libs.compose.bom) + implementation(composeBom) + implementation(libs.compose.ui) + implementation(libs.compose.foundation) + implementation(libs.compose.ui.graphics) + implementation(libs.compose.material.core) + implementation(libs.compose.material3) + implementation(libs.androidx.lifecycle.viewModelCompose) + + testImplementation(libs.junit4) + androidTestImplementation(libs.androidx.test.extJunit) + androidTestImplementation(libs.androidx.espresso.core) +} diff --git a/features/sketch/consumer-rules.pro b/features/sketch/consumer-rules.pro new file mode 100644 index 00000000000..e69de29bb2d diff --git a/features/sketch/proguard-rules.pro b/features/sketch/proguard-rules.pro new file mode 100644 index 00000000000..481bb434814 --- /dev/null +++ b/features/sketch/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/features/sketch/src/androidTest/java/com/wire/android/ExampleInstrumentedTest.kt b/features/sketch/src/androidTest/java/com/wire/android/ExampleInstrumentedTest.kt new file mode 100644 index 00000000000..b8949a2648a --- /dev/null +++ b/features/sketch/src/androidTest/java/com/wire/android/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.wire.android + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.wire.android.test", appContext.packageName) + } +} diff --git a/features/sketch/src/main/AndroidManifest.xml b/features/sketch/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..44b6ed94db1 --- /dev/null +++ b/features/sketch/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + diff --git a/features/sketch/src/main/java/com/wire/android/feature/sketch/DrawingCanvasBottomSheet.kt b/features/sketch/src/main/java/com/wire/android/feature/sketch/DrawingCanvasBottomSheet.kt new file mode 100644 index 00000000000..614e5b3f945 --- /dev/null +++ b/features/sketch/src/main/java/com/wire/android/feature/sketch/DrawingCanvasBottomSheet.kt @@ -0,0 +1,86 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.feature.sketch + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Send +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DrawingCanvasBottomSheet( + onDismissSketch: () -> Unit, + onSendSketch: () -> Unit +) { + val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + ModalBottomSheet( + dragHandle = { + Row( + Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + val scope = rememberCoroutineScope() + IconButton( + onClick = { + scope.launch { sheetState.hide() }.invokeOnCompletion { onDismissSketch() } + }, + ) { + Icon( + Icons.Filled.Close, + contentDescription = stringResource( + com.google.android.material.R.string.mtrl_picker_cancel + ) + ) + } + IconButton( + onClick = { + onSendSketch() + }, + ) { + Icon( + Icons.Filled.Send, + contentDescription = stringResource( + com.google.android.material.R.string.mtrl_picker_cancel + ) + ) + } + + } + }, + sheetState = sheetState, + onDismissRequest = { + onDismissSketch() + } + ) { + DrawingCanvasComponent() + } +} diff --git a/features/sketch/src/main/java/com/wire/android/feature/sketch/DrawingCanvasComponent.kt b/features/sketch/src/main/java/com/wire/android/feature/sketch/DrawingCanvasComponent.kt new file mode 100644 index 00000000000..7268c8c62fa --- /dev/null +++ b/features/sketch/src/main/java/com/wire/android/feature/sketch/DrawingCanvasComponent.kt @@ -0,0 +1,111 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.feature.sketch + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.awaitEachGesture +import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.nativeCanvas +import androidx.compose.ui.input.pointer.AwaitPointerEventScope +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.input.pointer.positionChange +import androidx.lifecycle.viewmodel.compose.viewModel +import com.wire.android.feature.sketch.model.DrawingMotionEvent + +@Composable +internal fun DrawingCanvasComponent( + viewModel: DrawingCanvasViewModel = viewModel() +) { + with(viewModel.state) { + val drawModifier = Modifier + .fillMaxSize() + .clipToBounds() // necessary to draw inside the canvas. + .background(MaterialTheme.colorScheme.background) + .pointerInput(Unit) { + awaitEachGesture { + handleGestures( + viewModel::onStartDrawing, + viewModel::onDraw, + viewModel::onStopDrawing + ) + } + } + Canvas(modifier = drawModifier) { + with(drawContext.canvas.nativeCanvas) { + val checkPoint = saveLayer(null, null) + when (drawingMotionEvent) { + DrawingMotionEvent.Idle -> Unit + DrawingMotionEvent.Down -> viewModel.onStartDrawingEvent() + DrawingMotionEvent.Move -> { + viewModel.onDrawEvent() + // todo: draw with selected properties, out of scope for this first ticket. + drawCircle( + center = currentPosition, + color = Color.Gray, + radius = currentPath.strokeWidth / 2, + style = Stroke(width = 1f) + ) + } + + DrawingMotionEvent.Up -> viewModel.onStopDrawingEvent() + } + paths.forEach { path -> + path.draw(this@Canvas /*, bitmap*/) + } + restoreToCount(checkPoint) + } + } + } +} + +/** + * Handles the drawing gestures in the canvas. + */ +private suspend fun AwaitPointerEventScope.handleGestures( + onStartDrawing: (Offset) -> Unit, + onDraw: (Offset) -> Unit, + onStopDrawing: () -> Unit +) { + val downEvent = awaitFirstDown() + onStartDrawing(downEvent.position) + if (downEvent.pressed != downEvent.previousPressed) { + downEvent.consume() + } + do { + val event = awaitPointerEvent() + onDraw(event.changes.first().position) + val hasNewLineDraw = event.changes + .first() + .positionChange() != Offset.Zero + if (hasNewLineDraw) { + event.changes + .first() + .consume() + } + } while (event.changes.any { it.pressed }) + onStopDrawing() +} diff --git a/features/sketch/src/main/java/com/wire/android/feature/sketch/DrawingCanvasViewModel.kt b/features/sketch/src/main/java/com/wire/android/feature/sketch/DrawingCanvasViewModel.kt new file mode 100644 index 00000000000..74fe6f6cc2c --- /dev/null +++ b/features/sketch/src/main/java/com/wire/android/feature/sketch/DrawingCanvasViewModel.kt @@ -0,0 +1,87 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.feature.sketch + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.geometry.Offset +import androidx.lifecycle.ViewModel +import com.wire.android.feature.sketch.model.DrawingMotionEvent +import com.wire.android.feature.sketch.model.DrawingPathProperties +import com.wire.android.feature.sketch.model.DrawingState + +internal class DrawingCanvasViewModel : ViewModel() { + + var state: DrawingState by mutableStateOf(DrawingState()) + private set + + /** + * Marks the start of the drawing. + */ + fun onStartDrawing(offset: Offset) { + state = state.copy(currentPosition = offset, drawingMotionEvent = DrawingMotionEvent.Down) + } + + /** + * Marks the drawing in progress. + */ + fun onDraw(offset: Offset) { + state = state.copy(currentPosition = offset, drawingMotionEvent = DrawingMotionEvent.Move) + } + + /** + * Marks the end of the drawing. + */ + fun onStopDrawing() { + state = state.copy(drawingMotionEvent = DrawingMotionEvent.Up) + } + + /** + * Stores the initial point of the drawing. + */ + fun onStartDrawingEvent() { + state = state.copy(paths = state.paths + state.currentPath).apply { + currentPath.path.moveTo(state.currentPosition.x, state.currentPosition.y) + } + } + + /** + * Stores the drawing in progress. + */ + fun onDrawEvent() { + state.currentPath.path.lineTo(state.currentPosition.x, state.currentPosition.y) + } + + /** + * Stores the end point of the drawing and performs the necessary cleanup. + */ + fun onStopDrawingEvent() { + state.currentPath.path.lineTo(state.currentPosition.x, state.currentPosition.y) + state = state.copy( + currentPath = DrawingPathProperties().apply { + strokeWidth = state.currentPath.strokeWidth + color = state.currentPath.color + drawMode = state.currentPath.drawMode + }, pathsUndone = emptyList(), + currentPosition = Offset.Unspecified, + drawingMotionEvent = DrawingMotionEvent.Idle + ) + } + +} diff --git a/features/sketch/src/main/java/com/wire/android/feature/sketch/model/DrawMode.kt b/features/sketch/src/main/java/com/wire/android/feature/sketch/model/DrawMode.kt new file mode 100644 index 00000000000..6e68c62dca3 --- /dev/null +++ b/features/sketch/src/main/java/com/wire/android/feature/sketch/model/DrawMode.kt @@ -0,0 +1,22 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.feature.sketch.model + +internal enum class DrawMode { + Pen, Eraser, None +} diff --git a/features/sketch/src/main/java/com/wire/android/feature/sketch/model/DrawingMotionEvent.kt b/features/sketch/src/main/java/com/wire/android/feature/sketch/model/DrawingMotionEvent.kt new file mode 100644 index 00000000000..b17ed2283e3 --- /dev/null +++ b/features/sketch/src/main/java/com/wire/android/feature/sketch/model/DrawingMotionEvent.kt @@ -0,0 +1,22 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.feature.sketch.model + +internal enum class DrawingMotionEvent { + Idle, Down, Move, Up +} diff --git a/features/sketch/src/main/java/com/wire/android/feature/sketch/model/DrawingPathProperties.kt b/features/sketch/src/main/java/com/wire/android/feature/sketch/model/DrawingPathProperties.kt new file mode 100644 index 00000000000..39f8eb0fdf2 --- /dev/null +++ b/features/sketch/src/main/java/com/wire/android/feature/sketch/model/DrawingPathProperties.kt @@ -0,0 +1,83 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.feature.sketch.model + +import android.graphics.Bitmap +import android.graphics.BitmapShader +import android.graphics.Shader +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.ShaderBrush +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.StrokeJoin +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.Stroke + +internal class DrawingPathProperties( + var path: Path = Path(), + var strokeWidth: Float = 10f, + var color: Color = Color.Blue, + var drawMode: DrawMode = DrawMode.Pen +) { + fun draw(scope: DrawScope, bitmap: Bitmap? = null) { + when (drawMode) { + DrawMode.Pen -> { + if (bitmap != null) { + val brush = ShaderBrush( + shader = BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT) + ) + scope.drawPath( + path = path, + brush = brush, + style = Stroke( + width = strokeWidth, + cap = StrokeCap.Round, + join = StrokeJoin.Round + ) + ) + } else { + scope.drawPath( + color = color, + path = path, + style = Stroke( + width = strokeWidth, + cap = StrokeCap.Round, + join = StrokeJoin.Round + ) + ) + } + } + + DrawMode.Eraser -> { + scope.drawPath( + color = Color.Transparent, + path = path, + style = Stroke( + width = strokeWidth, + cap = StrokeCap.Round, + join = StrokeJoin.Round + ), + blendMode = BlendMode.Clear + ) + } + + DrawMode.None -> {} + } + } +} diff --git a/features/sketch/src/main/java/com/wire/android/feature/sketch/model/DrawingState.kt b/features/sketch/src/main/java/com/wire/android/feature/sketch/model/DrawingState.kt new file mode 100644 index 00000000000..283797ea902 --- /dev/null +++ b/features/sketch/src/main/java/com/wire/android/feature/sketch/model/DrawingState.kt @@ -0,0 +1,28 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.feature.sketch.model + +import androidx.compose.ui.geometry.Offset + +internal data class DrawingState( + val paths: List = listOf(), + val pathsUndone: List = listOf(), + val drawingMotionEvent: DrawingMotionEvent = DrawingMotionEvent.Idle, + val currentPath: DrawingPathProperties = DrawingPathProperties(), + val currentPosition: Offset = Offset.Unspecified +) diff --git a/features/sketch/src/main/res/values/strings.xml b/features/sketch/src/main/res/values/strings.xml new file mode 100644 index 00000000000..9147ed8f0c8 --- /dev/null +++ b/features/sketch/src/main/res/values/strings.xml @@ -0,0 +1,20 @@ + + + Sketch module + diff --git a/features/sketch/src/test/java/com/wire/android/feature/sketch/ExampleUnitTest.kt b/features/sketch/src/test/java/com/wire/android/feature/sketch/ExampleUnitTest.kt new file mode 100644 index 00000000000..0fe155d0de8 --- /dev/null +++ b/features/sketch/src/test/java/com/wire/android/feature/sketch/ExampleUnitTest.kt @@ -0,0 +1,34 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.feature.sketch + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/features/template/.gitignore b/features/template/.gitignore new file mode 100644 index 00000000000..42afabfd2ab --- /dev/null +++ b/features/template/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/features/template/build.gradle.kts b/features/template/build.gradle.kts new file mode 100644 index 00000000000..8f553b3fa3b --- /dev/null +++ b/features/template/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + id(libs.plugins.wire.android.library.get().pluginId) + id(libs.plugins.wire.kover.get().pluginId) +} + +dependencies { + implementation(libs.androidx.core) + implementation(libs.androidx.appcompat) + implementation(libs.material) + + val composeBom = platform(libs.compose.bom) + implementation(composeBom) + implementation(libs.compose.ui) + implementation(libs.compose.foundation) + + testImplementation(libs.junit4) + androidTestImplementation(libs.androidx.test.extJunit) + androidTestImplementation(libs.androidx.espresso.core) +} diff --git a/features/template/consumer-rules.pro b/features/template/consumer-rules.pro new file mode 100644 index 00000000000..e69de29bb2d diff --git a/features/template/proguard-rules.pro b/features/template/proguard-rules.pro new file mode 100644 index 00000000000..481bb434814 --- /dev/null +++ b/features/template/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/features/template/src/androidTest/java/com/wire/android/ExampleInstrumentedTest.kt b/features/template/src/androidTest/java/com/wire/android/ExampleInstrumentedTest.kt new file mode 100644 index 00000000000..b8949a2648a --- /dev/null +++ b/features/template/src/androidTest/java/com/wire/android/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.wire.android + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.wire.android.test", appContext.packageName) + } +} diff --git a/features/template/src/main/AndroidManifest.xml b/features/template/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..44b6ed94db1 --- /dev/null +++ b/features/template/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + diff --git a/features/template/src/main/kotlin/com/wire/android/feature/template/AndroidExampleView.kt b/features/template/src/main/kotlin/com/wire/android/feature/template/AndroidExampleView.kt new file mode 100644 index 00000000000..9df89e4038b --- /dev/null +++ b/features/template/src/main/kotlin/com/wire/android/feature/template/AndroidExampleView.kt @@ -0,0 +1,29 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.feature.template + +import android.view.View +import androidx.fragment.app.Fragment + +class AndroidExampleView : Fragment() { + private val view: View = View(context) + + fun show() { + R.string.module_name + } +} diff --git a/features/template/src/main/res/values/strings.xml b/features/template/src/main/res/values/strings.xml new file mode 100644 index 00000000000..3efa6faabb4 --- /dev/null +++ b/features/template/src/main/res/values/strings.xml @@ -0,0 +1,20 @@ + + + template module + diff --git a/features/template/src/test/kotlin/com/wire/android/feature/template/ExampleUnitTest.kt b/features/template/src/test/kotlin/com/wire/android/feature/template/ExampleUnitTest.kt new file mode 100644 index 00000000000..e81b9e13da3 --- /dev/null +++ b/features/template/src/test/kotlin/com/wire/android/feature/template/ExampleUnitTest.kt @@ -0,0 +1,34 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.android.feature.template + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 855b04fcd06..de410737864 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,6 +26,7 @@ bundlizer = "0.8.0" spotless = "6.1.2" squareup-javapoet = "1.13.0" visibilityModifiers = "1.1.0" +kover = "0.7.6" # AndroidX androidx-appcompat = "1.6.1" @@ -110,6 +111,7 @@ ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } wire-android-application = { id = "com.wire.android.application" } wire-android-library = { id = "com.wire.android.library" } wire-hilt = { id = "com.wire.android.hilt" } +wire-kover = { id = "com.wire.android.kover" } [bundles] #android = ["appCompat", "activityCompose", "work", "composeMaterial", "coroutinesAndroid", "ktor", "ktor-okHttp"] @@ -125,6 +127,7 @@ googleGms-gradlePlugin = { module = "com.google.gms:google-services", version.re googleGms-location = { module = "com.google.android.gms:play-services-location", version.ref = "gms-location" } aboutLibraries-gradlePlugin = { module = "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin", version.ref = "aboutLibraries" } spotless-gradlePlugin = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" } +kover-gradlePlugin = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", version.ref = "kover" } ktx-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "ktx-serialization" } ktx-dateTime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "ktx-dateTime" } @@ -191,6 +194,7 @@ compose-ui-test-junit = { module = "androidx.compose.ui:ui-test-junit4" } compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" } compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" } compose-ui-preview = { module = "androidx.compose.ui:ui-tooling-preview" } +compose-ui-graphics = { module = "androidx.compose.ui:ui-graphics" } # Compose other compose-activity = { module = "androidx.activity:activity-compose", version.ref = "compose-activity" } diff --git a/scripts/new_feature_module.sh b/scripts/new_feature_module.sh new file mode 100755 index 00000000000..827f658cf12 --- /dev/null +++ b/scripts/new_feature_module.sh @@ -0,0 +1,40 @@ +#!/bin/bash -e + +# +# Wire +# Copyright (C) 2024 Wire Swiss GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# + +echo "Please enter your module name: " +read newModule + +cd ../features +cp -r template $newModule && cd $newModule + +# change in files +git grep -rl template . | xargs sed -i '' 's#template#'"$newModule"'#g' + +# change in package names and imports +find src/ -type f -name '*.kt' | xargs sed -i '' 's#template#'"$newModule"'#g' + +# change in xml files +find src/ -type f -name '*.xml' | xargs sed -i '' 's#template#'"$newModule"'#g' + +# change in build.gradle.kts +find . -type f -name '*.kts' | xargs sed -i '' 's#template#'"$newModule"'#g' + +# change folder names +find . -name "*template*" | awk '{a=$1; gsub(/template/,"'"$newModule"'"); printf "mv \"%s\" \"%s\"\n", a, $1}' | sh diff --git a/settings.gradle.kts b/settings.gradle.kts index 62426aa4225..17529ab4c98 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -23,15 +23,25 @@ pluginManagement { } // Include all the existent modules in the project +val basePathModules = setOf("features", "core") +val ignorableModules = setOf("buildSrc", "kalium") rootDir .walk() .maxDepth(1) - .filter { - it.name != "buildSrc" && it.name != "kalium" && it.isDirectory && - file("${it.absolutePath}/build.gradle.kts").exists() - } - .forEach { - include(":${it.name}") + .filter { project -> + basePathModules.contains(project.name) || ignorableModules.none { project.name == it } + && project.isDirectory && file("${project.absolutePath}/build.gradle.kts").exists() + }.map { rootDirFile -> + if (basePathModules.contains(rootDirFile.name)) { + rootDirFile.walk() + .maxDepth(1) + .filter { it.name != "template" && file("${it.absolutePath}/build.gradle.kts").exists() } + .map { "${rootDirFile.name}:${it.name}" }.toList() + } else { + listOf(rootDirFile.name) + } + }.forEach { + include(it) } // A work-around where we define the included builds in a different file