diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index d6afcd19..7ef4bf7d 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -6,7 +6,7 @@ This code review checklist is intended to serve as a starting point for the auth - [ ] Automated tests: Did you add appropriate automated tests for any code changes? - [ ] Manual tests: Did you update the [manual tests](../blob/main/docs/testing/manual_testing) as appropriate? _While we aim for automated testing of the application, some aspects require manual testing. If you had to manually test something during development of this pull request, write those steps down._ - [ ] Code coverage: Did you check the code coverage report for the automated tests? _While we are not looking for perfect coverage, the tool can point out potential cases that have been missed._ -- [ ] Documentation: Did you update documentation as appropiate? (e.g [README.md](../blob/main/README.md), etc.) +- [ ] Documentation: Did you update documentation as appropriate? (e.g [README.md](../blob/main/README.md), etc.) - [ ] Run the app: Did you run the app and try the changes? - [ ] Screenshots: Did you provide before and after UI screenshots in the description of this pull request? _This is only applicable for changes that modify the UI._ - [ ] Rebase and squash: Did you pull in the latest changes from the main branch and squash your commits before assigning a reviewer? _Having your code up to date and squashed will make it easier for others to review. Use best judgement when squashing commits, as some changes (such as refactoring) might be easier to review as a separate commit._ diff --git a/.gitignore b/.gitignore index bf49da05..ffc7bf5e 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ bin/ build/ gen/ local.properties +/.idea/deploymentTargetDropDown.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..f2c71e49 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,4 @@ +# Default ignored files +/shelf/ +/workspace.xml +misc.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 00000000..52f1cea3 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +zcash-android-app \ No newline at end of file diff --git a/.idea/runConfigurations/clean.xml b/.idea/runConfigurations/clean.xml new file mode 100644 index 00000000..269467e9 --- /dev/null +++ b/.idea/runConfigurations/clean.xml @@ -0,0 +1,23 @@ + + + + + + + true + true + false + + + \ No newline at end of file diff --git a/.idea/runConfigurations/dependencyUpdates.xml b/.idea/runConfigurations/dependencyUpdates.xml new file mode 100644 index 00000000..98b02f01 --- /dev/null +++ b/.idea/runConfigurations/dependencyUpdates.xml @@ -0,0 +1,23 @@ + + + + + + + true + true + false + + + \ No newline at end of file diff --git a/.idea/runConfigurations/detektAll.xml b/.idea/runConfigurations/detektAll.xml new file mode 100644 index 00000000..a845280d --- /dev/null +++ b/.idea/runConfigurations/detektAll.xml @@ -0,0 +1,23 @@ + + + + + + + true + true + false + + + \ No newline at end of file diff --git a/.idea/runConfigurations/lint.xml b/.idea/runConfigurations/lint.xml new file mode 100644 index 00000000..8d4552ba --- /dev/null +++ b/.idea/runConfigurations/lint.xml @@ -0,0 +1,23 @@ + + + + + + + true + true + false + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index a10e13b9..52115758 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,7 @@ While we aim to continue improving this sample, it is not an official product. If you'd like to compile this application from source, please see our [Setup Documentation](docs/Setup.md) to get started. # Reporting an issue -If you wish to report a security issue, please follow our [Responsible Disclosure guidelines](https://github.com/zcash/ZcashLightClientKit/blob/master/responsible_disclosure.md). See the [Wallet App Threat Model](https://zcash.readthedocs.io/en/latest/rtd_pages/wallet_threat_model.html) for more information about the security and privacy limitations of the wallet. There are some known security and privacy limitations: -- Traffic analysis, like in other cryptocurrency wallets, can leak some privacy of the user. -- The wallet requires a trust in the server to display accurate transaction information. +If you wish to report a security issue, please follow our [Responsible Disclosure guidelines](https://github.com/zcash/ZcashLightClientKit/blob/master/responsible_disclosure.md). See the [Wallet App Threat Model](https://zcash.readthedocs.io/en/latest/rtd_pages/wallet_threat_model.html) for more information about the security and privacy limitations of the wallet. If you'd like to report a technical issue or feature request for the Android Wallet, please file a [GitHub issue](https://github.com/zcash/secant-android-wallet/issues/new/choose). @@ -21,3 +19,16 @@ General Zcash questions and/or support requests and are best directed to either: # Contributing Contributions are very much welcomed! Please read our [Contributing Guidelines](docs/CONTRIBUTING.md) to learn about our process. + +# Forking +If you plan to fork the project to create a new app of your own, please make the following changes. (If you're making a GitHub fork to contribute back to the project, these steps are not necessary.) + +1. Change the app name under app/ +1. Remove any copyrighted ZCash or Electric Coin Company icons, logos, or assets +1. Change the package name + 1. Under [app/build.gradle.kts](app/build.gradle.kts), change the package name of the application + 1. Under [app/proguard-project.txt](app/proguard-project.txt), change the `-repackageclasses` directive to your own package name + +# Known Issues + +1. During builds, a warning will be printed that says "Unable to detect AGP versions for included builds. All projects in the build should use the same AGP version." This can be safely ignored. The version under build-conventions is the same as the version used elsewhere in the application. \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 00000000..ca253012 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,103 @@ +plugins { + id("com.android.application") + kotlin("android") + id("kotlin-parcelize") + id("androidx.navigation.safeargs") + id("zcash.android-build-conventions") +} + +val packageName = "cash.z.ecc.android" + +android { + defaultConfig { + applicationId = packageName + versionCode = 1 + versionName = "1.0" + } + + buildFeatures { + viewBinding = true + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.compose.get() + } + + flavorDimensions.add("network") + + productFlavors { + // would rather name them "testnet" and "mainnet" but product flavor names cannot start with the word "test" + create("zcashtestnet") { + dimension = "network" + applicationId = "$packageName.testnet" // allow to be installed alongside mainnet + matchingFallbacks.addAll(listOf("zcashtestnet", "debug")) + } + + create("zcashmainnet") { + dimension = "network" + applicationId = packageName + matchingFallbacks.addAll(listOf("zcashmainnet", "release")) + } + } + + buildTypes { + getByName("release").apply { + isMinifyEnabled = project.property("IS_MINIFY_ENABLED").toString().toBoolean() + proguardFiles.addAll( + listOf( + getDefaultProguardFile("proguard-android-optimize.txt"), + File("proguard-project.txt") + ) + ) + } + } + + signingConfigs { + val releaseKeystorePath = project.property("ZCASH_RELEASE_KEYSTORE_PATH").toString() + val releaseKeystorePassword = project.property("ZCASH_RELEASE_KEYSTORE_PASSWORD").toString() + val releaseKeyAlias = project.property("ZCASH_RELEASE_KEY_ALIAS").toString() + val releaseKeyAliasPassword = + project.property("ZCASH_RELEASE_KEY_ALIAS_PASSWORD").toString() + val isReleaseSigningConfigured = listOf( + releaseKeystorePath, + releaseKeystorePassword, + releaseKeyAlias, + releaseKeyAliasPassword + ).all { !it.isNullOrBlank() } + + if (isReleaseSigningConfigured) { + // If this block doesn't execute, the output will be unsigned + create("release").apply { + storeFile = File(releaseKeystorePath) + storePassword = releaseKeystorePassword + keyAlias = releaseKeyAlias + keyPassword = releaseKeyAliasPassword + } + } + } + + // TODO [#5]: Figure out how to move this into the build-conventions + testCoverage { + jacocoVersion = libs.versions.jacoco.get() + } + + // TODO [#6]: Figure out how to move this into the build-conventions + kotlinOptions { + jvmTarget = libs.versions.java.get() + } +} + +dependencies { + implementation(libs.androidx.activity) + implementation(libs.androidx.annotation) + implementation(libs.androidx.core) + implementation(libs.bundles.androidx.compose) + implementation(libs.google.material) + implementation(libs.kotlin) + implementation(libs.kotlinx.coroutines.android) + implementation(libs.kotlinx.coroutines.core) + implementation(libs.zcash) + + androidTestImplementation(libs.bundles.androidx.test) +} diff --git a/app/proguard-project.txt b/app/proguard-project.txt new file mode 100644 index 00000000..3fd87cff --- /dev/null +++ b/app/proguard-project.txt @@ -0,0 +1,9 @@ +# This improves obfuscation. +-repackageclasses 'cash.z.ecc.android' + +# Ensure that stacktraces are reversible. +-renamesourcefileattribute SourceFile +-keepattributes SourceFile,LineNumberTable + +# Generate the combined proguard configuration for debugging. +-printconfiguration build/outputs/proguard-config.txt \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..d59e98a7 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/cash/z/ecc/android/app/App.kt b/app/src/main/java/cash/z/ecc/android/app/App.kt new file mode 100644 index 00000000..9b28b058 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/app/App.kt @@ -0,0 +1,19 @@ +package cash.z.ecc.android.app + +import android.app.Application +import cash.z.ecc.android.sdk.demoapp.BuildConfig +import cash.z.ecc.android.sdk.ext.TroubleshootingTwig +import cash.z.ecc.android.sdk.ext.Twig + +class App : Application() { + + override fun onCreate() { + super.onCreate() + + if (BuildConfig.DEBUG) { + StrictModeHelper.enableStrictMode() + } + + Twig.plant(TroubleshootingTwig()) + } +} diff --git a/app/src/main/java/cash/z/ecc/android/app/MainActivity.kt b/app/src/main/java/cash/z/ecc/android/app/MainActivity.kt new file mode 100644 index 00000000..605bf5ba --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/app/MainActivity.kt @@ -0,0 +1,38 @@ +package cash.z.ecc.android.app + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import cash.z.ecc.android.app.ui.theme.MyApplicationTheme + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + MyApplicationTheme { + // A surface container using the 'background' color from the theme + Surface(color = MaterialTheme.colors.background) { + Greeting("Android") + } + } + } + } +} + +@Composable +fun Greeting(name: String) { + Text(text = "Hello $name!") +} + +@Preview(showBackground = true) +@Composable +fun DefaultPreview() { + MyApplicationTheme { + Greeting("Android") + } +} diff --git a/app/src/main/java/cash/z/ecc/android/app/StrictModeHelper.kt b/app/src/main/java/cash/z/ecc/android/app/StrictModeHelper.kt new file mode 100644 index 00000000..fc43cad5 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/app/StrictModeHelper.kt @@ -0,0 +1,61 @@ +package cash.z.ecc.android.app + +import android.annotation.SuppressLint +import android.os.Build +import android.os.Handler +import android.os.Looper +import android.os.StrictMode + + +object StrictModeHelper { + + fun enableStrictMode() { + configureStrictMode() + + // Workaround for Android bug + // https://issuetracker.google.com/issues/36951662 + // Not needed if target O_MR1 and running on O_MR1 + // Don't really need to check target, because of Google Play enforcement on targetSdkVersion for app updates + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1) { + Handler(Looper.getMainLooper()).postAtFrontOfQueue { configureStrictMode() } + } + } + + @SuppressLint("NewApi") + private fun configureStrictMode() { + StrictMode.enableDefaults() + + StrictMode.setThreadPolicy( + StrictMode.ThreadPolicy.Builder().apply { + detectAll() + penaltyLog() + }.build() + ) + + // Don't enable missing network tags, because those are noisy. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + StrictMode.setVmPolicy( + StrictMode.VmPolicy.Builder().apply { + detectActivityLeaks() + detectCleartextNetwork() + detectContentUriWithoutPermission() + detectFileUriExposure() + detectLeakedClosableObjects() + detectLeakedRegistrationObjects() + detectLeakedSqlLiteObjects() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + // Disable because this is mostly flagging Android X and Play Services + // builder.detectNonSdkApiUsage(); + } + }.build() + ) + } else { + StrictMode.setVmPolicy( + StrictMode.VmPolicy.Builder().apply { + detectAll() + penaltyLog() + }.build() + ) + } + } +} diff --git a/app/src/main/java/cash/z/ecc/android/app/ui/theme/Color.kt b/app/src/main/java/cash/z/ecc/android/app/ui/theme/Color.kt new file mode 100644 index 00000000..3d0af38c --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/app/ui/theme/Color.kt @@ -0,0 +1,15 @@ +package cash.z.ecc.android.app.ui.theme + +import androidx.compose.ui.graphics.Color + +@Suppress("MagicNumber") +val Purple200 = Color(0xFFBB86FC) + +@Suppress("MagicNumber") +val Purple500 = Color(0xFF6200EE) + +@Suppress("MagicNumber") +val Purple700 = Color(0xFF3700B3) + +@Suppress("MagicNumber") +val Teal200 = Color(0xFF03DAC5) diff --git a/app/src/main/java/cash/z/ecc/android/app/ui/theme/Shape.kt b/app/src/main/java/cash/z/ecc/android/app/ui/theme/Shape.kt new file mode 100644 index 00000000..a529f4ec --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/app/ui/theme/Shape.kt @@ -0,0 +1,11 @@ +package cash.z.ecc.android.app.ui.theme + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Shapes +import androidx.compose.ui.unit.dp + +val Shapes = Shapes( + small = RoundedCornerShape(4.dp), + medium = RoundedCornerShape(4.dp), + large = RoundedCornerShape(0.dp) +) diff --git a/app/src/main/java/cash/z/ecc/android/app/ui/theme/Theme.kt b/app/src/main/java/cash/z/ecc/android/app/ui/theme/Theme.kt new file mode 100644 index 00000000..2ed41724 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/app/ui/theme/Theme.kt @@ -0,0 +1,47 @@ +package cash.z.ecc.android.app.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material.MaterialTheme +import androidx.compose.material.darkColors +import androidx.compose.material.lightColors +import androidx.compose.runtime.Composable + +private val DarkColorPalette = darkColors( + primary = Purple200, + primaryVariant = Purple700, + secondary = Teal200 +) + +private val LightColorPalette = lightColors( + primary = Purple500, + primaryVariant = Purple700, + secondary = Teal200 + + /* Other default colors to override + background = Color.White, + surface = Color.White, + onPrimary = Color.White, + onSecondary = Color.Black, + onBackground = Color.Black, + onSurface = Color.Black, + */ +) + +@Composable +fun MyApplicationTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable() () -> Unit +) { + val colors = if (darkTheme) { + DarkColorPalette + } else { + LightColorPalette + } + + MaterialTheme( + colors = colors, + typography = Typography, + shapes = Shapes, + content = content + ) +} diff --git a/app/src/main/java/cash/z/ecc/android/app/ui/theme/Type.kt b/app/src/main/java/cash/z/ecc/android/app/ui/theme/Type.kt new file mode 100644 index 00000000..2ff203ed --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/app/ui/theme/Type.kt @@ -0,0 +1,28 @@ +package cash.z.ecc.android.app.ui.theme + +import androidx.compose.material.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + body1 = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp + ) + /* Other default text styles to override + button = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.W500, + fontSize = 14.sp + ), + caption = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 12.sp + ) + */ +) diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..95bbf453 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..74852f7d --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..184de9f5 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..184de9f5 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..a571e600 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000..61da551c Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..c41dd285 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000..db5080a7 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..6dba46da Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000..da31a871 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..15ac6817 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..b216f2d3 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..f25a4197 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..e96783cc Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 00000000..203e2191 --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..b17f0a12 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,11 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..7335191b --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Demo App + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 00000000..65789b57 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,25 @@ + + + + + + +