diff --git a/.github/workflows/build_about.yml b/.github/workflows/build_about.yml new file mode 100644 index 0000000..b9f052d --- /dev/null +++ b/.github/workflows/build_about.yml @@ -0,0 +1,61 @@ +name: Build about module + +on: + push: + branches: [ main ] + paths: + - about/** + pull_request: + branches: + - main + paths: + - about/** + + +jobs: + build: + if: ${{ ! startsWith(github.actor, 'dependabot') }} + environment: Development + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'adopt' + cache: gradle + + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@v1.0.5 + + + - name: Decrypt the keystore for signing + run: | + echo "${{ secrets.KEYSTORE_ENCRYPTED }}" > keystore.asc + gpg -d --passphrase "${{ secrets.KEYSTORE_PASSWORD }}" --batch keystore.asc > keystore.jks + + - name: Build about module + run: ./gradlew about:assembleRelease + + - name: Build release sample APK + run: ./gradlew sample:assembleRelease + + - name: Upload release arm64-v8a APK + uses: actions/upload-artifact@v3 + with: + name: release-arm64-v8a-apk + path: ./sample/build/outputs/apk/release/sample-arm64-v8a-release.apk + + - name: Upload release armeabi-v7a APK + uses: actions/upload-artifact@v3 + with: + name: release-armeabi-v7a-apk + path: ./sample/build/outputs/apk/release/sample-armeabi-v7a-release.apk + + - name: Upload release universal APK + uses: actions/upload-artifact@v3 + with: + name: release-universal-apk + path: ./sample/build/outputs/apk/release/sample-universal-release.apk diff --git a/.github/workflows/release_about.yml b/.github/workflows/release_about.yml new file mode 100644 index 0000000..2e4963e --- /dev/null +++ b/.github/workflows/release_about.yml @@ -0,0 +1,28 @@ +name: Release about + +on: + push: + tags: + - 'about*' + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'adopt' + + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@v1.0.5 + + - name: Publish about to Github + uses: gradle/gradle-build-action@v2 + with: + arguments: about:publish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/about/.gitignore b/about/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/about/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/about/build.gradle.kts b/about/build.gradle.kts new file mode 100644 index 0000000..1131b0e --- /dev/null +++ b/about/build.gradle.kts @@ -0,0 +1,90 @@ +import java.net.URI + +plugins { + id("com.android.library") + id("org.jetbrains.kotlin.android") + id("pl.allegro.tech.build.axion-release") + `maven-publish` +} + +android { + namespace = "dev.arkbuilders.components.about" + compileSdk = 34 + + defaultConfig { + minSdk = 26 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get() + } + publishing { + singleVariant("release") { + withSourcesJar() + } + } +} + +dependencies { + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + + implementation(libraries.androidx.compose.activity) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.compose.ui) + implementation(libs.androidx.compose.ui.graphics) + implementation(libs.androidx.compose.ui.tooling) + implementation(libs.androidx.compose.ui.tooling.preview) + implementation(libs.androidx.compose.material3) + + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.test.junit) + androidTestImplementation(libs.androidx.test.espresso) +} + +val libVersion: String = scmVersion.version + +publishing { + publications { + create("release") { + groupId = "dev.arkbuilders.components" + artifactId = "about" + version = libVersion + afterEvaluate { + from(components["release"]) + } + } + } + repositories { + maven { + name = "GithubPackages" + url = URI("https://maven.pkg.github.com/ARK-Builders/ark-android") + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } + } +} \ No newline at end of file diff --git a/about/consumer-rules.pro b/about/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/about/proguard-rules.pro b/about/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/about/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/about/src/androidTest/java/dev/arkbuilders/components/about/ExampleInstrumentedTest.kt b/about/src/androidTest/java/dev/arkbuilders/components/about/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..cc894ed --- /dev/null +++ b/about/src/androidTest/java/dev/arkbuilders/components/about/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package dev.arkbuilders.components.about + +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("dev.arkbuilders.components.about.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/about/src/main/AndroidManifest.xml b/about/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a5918e6 --- /dev/null +++ b/about/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/about/src/main/java/dev/arkbuilders/components/about/ArkAboutFragment.kt b/about/src/main/java/dev/arkbuilders/components/about/ArkAboutFragment.kt new file mode 100644 index 0000000..b99bfd9 --- /dev/null +++ b/about/src/main/java/dev/arkbuilders/components/about/ArkAboutFragment.kt @@ -0,0 +1,60 @@ +package dev.arkbuilders.components.about + +import android.os.Bundle +import android.view.View +import androidx.annotation.DrawableRes +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.fragment.app.Fragment +import dev.arkbuilders.components.about.presentation.ArkAbout + +class ArkAboutFragment : Fragment(R.layout.fragment_about) { + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val composeView = view.findViewById(R.id.compose_view) + val appName = requireArguments().getString(APP_NAME_KEY) + ?: error("appName can't be null") + val appLogoResID = requireArguments().getInt(APP_LOGO_KEY) + val versionName = requireArguments().getString(VERSION_NAME_KEY) + ?: error("versionName can't be null") + val privacyPolicyUrl = requireArguments().getString(PRIVACY_POLICY_URL_KEY) + ?: error("privacyPolicyUrl can't be null") + + composeView.apply { + setViewCompositionStrategy( + ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed + ) + setContent { + ArkAbout( + appName = appName, + appLogoResId = appLogoResID, + versionName = versionName, + privacyPolicyUrl = privacyPolicyUrl + ) + } + } + } + + companion object { + private const val APP_NAME_KEY = "APP_NAME_KEY" + private const val APP_LOGO_KEY = "APP_LOGO_KEY" + private const val VERSION_NAME_KEY = "VERSION_NAME_KEY" + private const val PRIVACY_POLICY_URL_KEY = "PRIVACY_POLICY_URL_KEY" + + fun create( + appName: String, + @DrawableRes + appLogoResID: Int, + versionName: String, + privacyPolicyUrl: String + ) = ArkAboutFragment().apply { + arguments = Bundle().apply { + putString(APP_NAME_KEY, appName) + putInt(APP_LOGO_KEY, appLogoResID) + putString(VERSION_NAME_KEY, versionName) + putString(PRIVACY_POLICY_URL_KEY, privacyPolicyUrl) + } + } + } +} \ No newline at end of file diff --git a/about/src/main/java/dev/arkbuilders/components/about/presentation/ArkAbout.kt b/about/src/main/java/dev/arkbuilders/components/about/presentation/ArkAbout.kt new file mode 100644 index 0000000..41d8909 --- /dev/null +++ b/about/src/main/java/dev/arkbuilders/components/about/presentation/ArkAbout.kt @@ -0,0 +1,256 @@ +package dev.arkbuilders.components.about.presentation + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import dev.arkbuilders.components.about.R +import dev.arkbuilders.components.about.presentation.theme.ArkColor +import dev.arkbuilders.components.about.presentation.ui.DonateBtn +import dev.arkbuilders.components.about.presentation.ui.QRCryptoDialog +import dev.arkbuilders.components.about.presentation.ui.SocialLink + +@Composable +fun ArkAbout( + appName: String, + @DrawableRes + appLogoResId: Int, + versionName: String, + privacyPolicyUrl: String +) { + val ctx = LocalContext.current + + var btcDialogVisible by remember { mutableStateOf(false) } + var ethDialogVisible by remember { mutableStateOf(false) } + + QRCryptoDialog( + visible = btcDialogVisible, + title = stringResource(R.string.about_donate_btc), + wallet = stringResource(R.string.about_btc_wallet), + fileName = "ArkQrBtc.jpg", + qrBitmap = R.drawable.qr_btc + ) { + btcDialogVisible = false + } + + QRCryptoDialog( + visible = ethDialogVisible, + title = stringResource(R.string.about_donate_eth), + wallet = stringResource(R.string.about_eth_wallet), + fileName = "ArkQrEth.jpg", + qrBitmap = R.drawable.qr_eth + ) { + ethDialogVisible = false + } + + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()) + ) { + Column( + modifier = Modifier.padding(horizontal = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Icon( + modifier = Modifier.padding(top = 32.dp), + painter = painterResource(id = appLogoResId), + contentDescription = "", + tint = Color.Unspecified + ) + Text( + modifier = Modifier.padding(top = 20.dp), + text = appName, + fontSize = 20.sp, + fontWeight = FontWeight.SemiBold, + color = ArkColor.TextPrimary + ) + Text( + modifier = Modifier.padding(top = 12.dp), + text = stringResource(R.string.version, versionName), + color = ArkColor.TextTertiary + ) + Text( + modifier = Modifier.padding(top = 12.dp), + text = "ARK Builders · Copyright ©2024", + color = ArkColor.TextTertiary + ) + Row( + modifier = Modifier.padding(top = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + SocialLink( + painterResource(R.drawable.ic_about_site), + text = stringResource(R.string.website) + ) { + ctx.openLink(ctx.getString(R.string.ark_website_url)) + } + SocialLink( + painterResource(R.drawable.ic_about_telegram), + text = stringResource(R.string.telegram) + ) { + ctx.openLink(ctx.getString(R.string.ark_tg_url)) + } + SocialLink( + painterResource(R.drawable.ic_about_discord), + text = stringResource(R.string.discord) + ) { + ctx.openLink(ctx.getString(R.string.ark_discord_url)) + } + } + OutlinedButton( + modifier = Modifier + .padding(top = 28.dp) + .fillMaxWidth(), + onClick = { ctx.openLink(privacyPolicyUrl) }, + border = BorderStroke( + width = 1.dp, + color = ArkColor.BorderSecondary + ), + shape = RoundedCornerShape(8.dp) + ) { + Text( + text = stringResource(R.string.privacy_policy), + fontWeight = FontWeight.SemiBold, + color = ArkColor.FGSecondary + ) + Icon( + modifier = Modifier.padding(start = 6.dp), + painter = painterResource(R.drawable.ic_external), + contentDescription = "", + tint = ArkColor.FGSecondary + ) + } + HorizontalDivider( + modifier = Modifier.padding(top = 20.dp), + thickness = 1.dp, + color = ArkColor.BorderSecondary + ) + Column { + Text( + modifier = Modifier.padding(top = 20.dp), + text = stringResource(R.string.about_support_us), + fontSize = 16.sp, + fontWeight = FontWeight.SemiBold, + color = ArkColor.TextPrimary + ) + Text( + modifier = Modifier.padding(top = 4.dp), + text = stringResource(R.string.about_we_greatly_appreciate_every_bit_of_support), + color = ArkColor.TextTertiary + ) + Row(modifier = Modifier.padding(top = 12.dp)) { + DonateBtn( + modifier = Modifier, + icon = painterResource(R.drawable.btc), + text = stringResource(R.string.about_donate_using_btc), + ) { + btcDialogVisible = true + } + DonateBtn( + modifier = Modifier.padding(start = 12.dp), + icon = painterResource(R.drawable.eth), + text = stringResource(R.string.about_donate_using_eth) + ) { + ethDialogVisible = true + } + } + Row(modifier = Modifier.padding(top = 12.dp)) { + DonateBtn( + modifier = Modifier, + icon = painterResource(R.drawable.ic_about_patreon), + text = stringResource(R.string.about_donate_on_patreon) + ) { + ctx.openLink(ctx.getString(R.string.about_ark_patreon_url)) + } + DonateBtn( + modifier = Modifier.padding(start = 12.dp), + icon = painterResource(R.drawable.ic_about_coffee), + text = stringResource(R.string.about_buy_as_a_coffee) + ) { + ctx.openLink(ctx.getString(R.string.about_ark_buy_coffee_url)) + } + } + HorizontalDivider( + modifier = Modifier.padding(top = 20.dp), + thickness = 1.dp, + color = ArkColor.BorderSecondary + ) + Row(modifier = Modifier.padding(top = 12.dp, bottom = 50.dp)) { + OutlinedButton( + modifier = Modifier, + onClick = { ctx.openLink(ctx.getString(R.string.ark_contribute_url)) }, + border = BorderStroke( + width = 1.dp, + color = ArkColor.BorderSecondary + ), + shape = RoundedCornerShape(8.dp), + contentPadding = PaddingValues(0.dp) + ) { + Text( + modifier = Modifier.padding(8.dp), + text = stringResource(R.string.about_discover_issues_to_work_on), + color = ArkColor.TextSecondary, + fontSize = 12.sp, + fontWeight = FontWeight.Medium + ) + } + OutlinedButton( + modifier = Modifier.padding(start = 12.dp), + onClick = { }, + border = BorderStroke( + width = 1.dp, + color = ArkColor.BorderSecondary + ), + shape = RoundedCornerShape(8.dp), + contentPadding = PaddingValues(0.dp), + enabled = false + ) { + Text( + modifier = Modifier.padding(8.dp), + text = stringResource(R.string.about_see_open_bounties), + color = ArkColor.TextPlaceHolder, + fontSize = 12.sp, + fontWeight = FontWeight.Medium + ) + } + } + } + } + } +} + +@Preview(showBackground = true) +@Composable +private fun PreviewArkAbout() { + ArkAbout( + appName = "App name", + appLogoResId = R.drawable.qr_btc, + versionName = "1.0.0", + privacyPolicyUrl = "" + ) +} \ No newline at end of file diff --git a/about/src/main/java/dev/arkbuilders/components/about/presentation/ContextUtils.kt b/about/src/main/java/dev/arkbuilders/components/about/presentation/ContextUtils.kt new file mode 100644 index 0000000..b047fac --- /dev/null +++ b/about/src/main/java/dev/arkbuilders/components/about/presentation/ContextUtils.kt @@ -0,0 +1,32 @@ +package dev.arkbuilders.components.about.presentation + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.widget.Toast +import dev.arkbuilders.components.about.R + +internal fun Context.openLink(url: String) { + toastIfError(this) { + startActivity(Intent(Intent.ACTION_VIEW).setData(Uri.parse(url))) + } +} + +internal fun Context.openEmail(mailTo: String) { + toastIfError(this) { + val uri = Uri.parse("mailto:$mailTo") + startActivity(Intent(Intent.ACTION_SENDTO, uri)) + } +} + +private fun toastIfError(ctx: Context, action: () -> Unit) { + try { + action() + } catch (e: Throwable) { + Toast.makeText( + ctx, + ctx.getString(R.string.oops_something_went_wrong), + Toast.LENGTH_SHORT + ).show() + } +} \ No newline at end of file diff --git a/about/src/main/java/dev/arkbuilders/components/about/presentation/theme/Color.kt b/about/src/main/java/dev/arkbuilders/components/about/presentation/theme/Color.kt new file mode 100644 index 0000000..a5216d1 --- /dev/null +++ b/about/src/main/java/dev/arkbuilders/components/about/presentation/theme/Color.kt @@ -0,0 +1,15 @@ +package dev.arkbuilders.components.about.presentation.theme + +import androidx.compose.ui.graphics.Color + +internal object ArkColor { + val TextPrimary = Color(0xFF101828) + val TextSecondary = Color(0xFF344054) + val TextTertiary = Color(0xFF475467) + val TextPlaceHolder = Color(0xFF667085) + + val FGSecondary = Color(0xFF344054) + val FGQuinary = Color(0xFF98A2B3) + val Border = Color(0xFFD0D5DD) + val BorderSecondary = Color(0xFFEAECF0) +} \ No newline at end of file diff --git a/about/src/main/java/dev/arkbuilders/components/about/presentation/ui/DonateBtn.kt b/about/src/main/java/dev/arkbuilders/components/about/presentation/ui/DonateBtn.kt new file mode 100644 index 0000000..a8de45a --- /dev/null +++ b/about/src/main/java/dev/arkbuilders/components/about/presentation/ui/DonateBtn.kt @@ -0,0 +1,67 @@ +package dev.arkbuilders.components.about.presentation.ui + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import dev.arkbuilders.components.about.R +import dev.arkbuilders.components.about.presentation.theme.ArkColor + +@Composable +internal fun DonateBtn( + modifier: Modifier, + icon: Painter, + text: String, + onClick: () -> Unit +) { + OutlinedButton( + modifier = modifier, + onClick = onClick, + border = BorderStroke( + width = 1.dp, + color = ArkColor.BorderSecondary + ), + shape = RoundedCornerShape(8.dp), + contentPadding = PaddingValues(0.dp) + ) { + Icon( + modifier = Modifier + .padding(start = 8.dp, top = 8.dp, bottom = 8.dp) + .size(20.dp), + painter = icon, + contentDescription = "", + tint = Color.Unspecified + ) + Text( + modifier = Modifier.padding(start = 8.dp, end = 8.dp), + text = text, + fontSize = 12.sp, + fontWeight = FontWeight.Medium, + color = ArkColor.FGSecondary + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun PreviewDonateBtn() { + DonateBtn( + modifier = Modifier, + icon = painterResource(R.drawable.btc), + text = "Donate", + onClick = {} + ) +} \ No newline at end of file diff --git a/about/src/main/java/dev/arkbuilders/components/about/presentation/ui/QRCryptoDialog.kt b/about/src/main/java/dev/arkbuilders/components/about/presentation/ui/QRCryptoDialog.kt new file mode 100644 index 0000000..b48e27c --- /dev/null +++ b/about/src/main/java/dev/arkbuilders/components/about/presentation/ui/QRCryptoDialog.kt @@ -0,0 +1,273 @@ +package dev.arkbuilders.components.about.presentation.ui + +import android.annotation.SuppressLint +import android.widget.Toast +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.DrawableRes +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.ClickableText +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.VerticalDivider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import dev.arkbuilders.components.about.R +import dev.arkbuilders.components.about.presentation.openEmail +import dev.arkbuilders.components.about.presentation.theme.ArkColor +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +@Composable +internal fun QRCryptoDialog( + visible: Boolean, + title: String, + wallet: String, + fileName: String, + @DrawableRes qrBitmap: Int, + onDismiss: () -> Unit +) { + if (visible.not()) + return + + Dialog( + onDismissRequest = onDismiss, + properties = DialogProperties(usePlatformDefaultWidth = false) + ) { + Content( + title = title, + wallet = wallet, + fileName = fileName, + qrBitmap = qrBitmap, + onDismiss = onDismiss + ) + } +} + +@SuppressLint("ResourceType") +@Composable +private fun Content( + title: String, + wallet: String, + fileName: String, + @DrawableRes qrBitmap: Int, + onDismiss: () -> Unit +) { + val ctx = LocalContext.current + val clipboardManager = LocalClipboardManager.current + val scope = rememberCoroutineScope() + val launcher = + rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("image/jpg")) { uri -> + uri ?: return@rememberLauncherForActivityResult + scope.launch(Dispatchers.IO) { + val input = ctx.resources.openRawResource(qrBitmap) + ctx.contentResolver.openOutputStream(uri).use { output -> + output?.let { + input.copyTo(output) + } + } + input.close() + } + } + + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .background(Color.White, RoundedCornerShape(12.dp)) + ) { + Column( + modifier = Modifier + .padding(horizontal = 16.dp) + .verticalScroll(rememberScrollState()) + ) { + Row { + Text( + modifier = Modifier.padding(top = 20.dp), + text = title, + fontWeight = FontWeight.SemiBold, + color = ArkColor.TextPrimary, + fontSize = 18.sp + ) + } + val emailText = buildAnnotatedString { + append(stringResource(R.string.about_send_email_part_1)) + pushStringAnnotation( + tag = "email", + annotation = ctx.getString(R.string.ark_support_email) + ) + withStyle(style = SpanStyle(textDecoration = TextDecoration.Underline)) { + append(ctx.getString(R.string.ark_support_email)) + } + pop() + append(stringResource(R.string.about_send_email_part_2)) + } + ClickableText( + modifier = Modifier.padding(top = 4.dp), + text = emailText, + style = TextStyle.Default.copy(color = ArkColor.TextTertiary) + ) { offset -> + emailText + .getStringAnnotations(tag = "email", offset, offset) + .firstOrNull() + ?.let { + ctx.openEmail(ctx.getString(R.string.ark_support_email)) + } + } + Image( + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp), + painter = painterResource(qrBitmap), + contentDescription = "", + contentScale = ContentScale.FillWidth + ) + Row( + modifier = Modifier + .padding(top = 24.dp) + .fillMaxWidth() + .height(45.dp) + .clip(RoundedCornerShape(8.dp)) + .border(1.dp, ArkColor.Border, RoundedCornerShape(8.dp)), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + modifier = Modifier + .weight(1f) + .padding(horizontal = 12.dp, vertical = 8.dp), + text = wallet, + fontSize = 16.sp, + maxLines = 1, + color = ArkColor.TextPlaceHolder, + overflow = TextOverflow.Ellipsis + ) + VerticalDivider( + modifier = Modifier.height(45.dp), + color = ArkColor.Border + ) + TextButton( + modifier = Modifier, + contentPadding = PaddingValues(0.dp), + onClick = { + clipboardManager.setText(AnnotatedString(wallet)) + Toast + .makeText( + ctx, + ctx.getString(R.string.about_wallet_copied), + Toast.LENGTH_SHORT + ) + .show() + }, + shape = RectangleShape + ) { + Row( + modifier = Modifier.padding( + horizontal = 16.dp, + vertical = 12.dp + ) + ) { + Icon( + painter = painterResource(R.drawable.ic_copy), + contentDescription = "", + tint = ArkColor.TextSecondary + ) + Text( + modifier = Modifier.padding(start = 6.dp), + text = stringResource(R.string.copy), + color = ArkColor.TextSecondary, + fontWeight = FontWeight.SemiBold + ) + } + } + } + OutlinedButton( + modifier = Modifier + .padding(top = 12.dp, bottom = 16.dp) + .fillMaxWidth(), + onClick = { launcher.launch(fileName) }, + border = BorderStroke( + width = 1.dp, + color = ArkColor.BorderSecondary + ), + shape = RoundedCornerShape(8.dp) + ) { + Icon( + painter = painterResource(R.drawable.ic_download), + contentDescription = "", + tint = ArkColor.TextSecondary + ) + Text( + modifier = Modifier.padding(start = 8.dp), + text = stringResource(R.string.about_download_qr_image), + fontWeight = FontWeight.SemiBold, + fontSize = 16.sp, + color = ArkColor.TextSecondary + ) + } + } + IconButton( + modifier = Modifier + .padding(end = 12.dp, top = 12.dp) + .align(Alignment.TopEnd), + onClick = { onDismiss() } + ) { + Icon( + modifier = Modifier, + painter = painterResource(id = R.drawable.ic_close), + contentDescription = "", + tint = ArkColor.FGQuinary + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun PreviewDialogContent() { + Content( + title = "Donate using Bitcoin", + wallet = "0x041220", + fileName = "1.jpg", + qrBitmap = R.drawable.btc, + onDismiss = {} + ) +} \ No newline at end of file diff --git a/about/src/main/java/dev/arkbuilders/components/about/presentation/ui/SocialLink.kt b/about/src/main/java/dev/arkbuilders/components/about/presentation/ui/SocialLink.kt new file mode 100644 index 0000000..1ec98e9 --- /dev/null +++ b/about/src/main/java/dev/arkbuilders/components/about/presentation/ui/SocialLink.kt @@ -0,0 +1,54 @@ +package dev.arkbuilders.components.about.presentation.ui + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import dev.arkbuilders.components.about.R +import dev.arkbuilders.components.about.presentation.theme.ArkColor + +@Composable +internal fun SocialLink(painter: Painter, text: String, onClick: () -> Unit) { + TextButton( + modifier = Modifier.defaultMinSize( + minWidth = 1.dp, + minHeight = 1.dp + ), + contentPadding = PaddingValues(horizontal = 4.dp, vertical = 4.dp), + onClick = { onClick() }, + shape = RoundedCornerShape(4.dp) + ) { + Icon( + modifier = Modifier.size(20.dp), + painter = painter, + contentDescription = text, + tint = Color.Unspecified + ) + Text( + modifier = Modifier.padding(start = 4.dp), + text = text, + color = ArkColor.TextTertiary + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun PreviewSocialLink() { + SocialLink( + painter = painterResource(R.drawable.ic_about_discord), + text = "Discord", + onClick = {} + ) +} \ No newline at end of file diff --git a/about/src/main/res/drawable/btc.xml b/about/src/main/res/drawable/btc.xml new file mode 100644 index 0000000..6f9033e --- /dev/null +++ b/about/src/main/res/drawable/btc.xml @@ -0,0 +1,14 @@ + + + + diff --git a/about/src/main/res/drawable/eth.xml b/about/src/main/res/drawable/eth.xml new file mode 100644 index 0000000..4a5ff45 --- /dev/null +++ b/about/src/main/res/drawable/eth.xml @@ -0,0 +1,30 @@ + + + + + + + + diff --git a/about/src/main/res/drawable/ic_about_coffee.xml b/about/src/main/res/drawable/ic_about_coffee.xml new file mode 100644 index 0000000..cff5abf --- /dev/null +++ b/about/src/main/res/drawable/ic_about_coffee.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/about/src/main/res/drawable/ic_about_discord.xml b/about/src/main/res/drawable/ic_about_discord.xml new file mode 100644 index 0000000..b41f6cb --- /dev/null +++ b/about/src/main/res/drawable/ic_about_discord.xml @@ -0,0 +1,12 @@ + + + + diff --git a/about/src/main/res/drawable/ic_about_patreon.xml b/about/src/main/res/drawable/ic_about_patreon.xml new file mode 100644 index 0000000..e6aa6e2 --- /dev/null +++ b/about/src/main/res/drawable/ic_about_patreon.xml @@ -0,0 +1,9 @@ + + + diff --git a/about/src/main/res/drawable/ic_about_site.xml b/about/src/main/res/drawable/ic_about_site.xml new file mode 100644 index 0000000..8a2bbd8 --- /dev/null +++ b/about/src/main/res/drawable/ic_about_site.xml @@ -0,0 +1,13 @@ + + + + diff --git a/about/src/main/res/drawable/ic_about_telegram.xml b/about/src/main/res/drawable/ic_about_telegram.xml new file mode 100644 index 0000000..03c2dae --- /dev/null +++ b/about/src/main/res/drawable/ic_about_telegram.xml @@ -0,0 +1,10 @@ + + + diff --git a/about/src/main/res/drawable/ic_close.xml b/about/src/main/res/drawable/ic_close.xml new file mode 100644 index 0000000..676a2d1 --- /dev/null +++ b/about/src/main/res/drawable/ic_close.xml @@ -0,0 +1,13 @@ + + + diff --git a/about/src/main/res/drawable/ic_copy.xml b/about/src/main/res/drawable/ic_copy.xml new file mode 100644 index 0000000..9675918 --- /dev/null +++ b/about/src/main/res/drawable/ic_copy.xml @@ -0,0 +1,13 @@ + + + diff --git a/about/src/main/res/drawable/ic_download.xml b/about/src/main/res/drawable/ic_download.xml new file mode 100644 index 0000000..cb59932 --- /dev/null +++ b/about/src/main/res/drawable/ic_download.xml @@ -0,0 +1,13 @@ + + + diff --git a/about/src/main/res/drawable/ic_external.xml b/about/src/main/res/drawable/ic_external.xml new file mode 100644 index 0000000..b05e5a4 --- /dev/null +++ b/about/src/main/res/drawable/ic_external.xml @@ -0,0 +1,13 @@ + + + diff --git a/about/src/main/res/drawable/qr_btc.jpg b/about/src/main/res/drawable/qr_btc.jpg new file mode 100644 index 0000000..c6c0229 Binary files /dev/null and b/about/src/main/res/drawable/qr_btc.jpg differ diff --git a/about/src/main/res/drawable/qr_eth.jpg b/about/src/main/res/drawable/qr_eth.jpg new file mode 100644 index 0000000..7607b3e Binary files /dev/null and b/about/src/main/res/drawable/qr_eth.jpg differ diff --git a/about/src/main/res/layout/fragment_about.xml b/about/src/main/res/layout/fragment_about.xml new file mode 100644 index 0000000..5d694b3 --- /dev/null +++ b/about/src/main/res/layout/fragment_about.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/about/src/main/res/values/strings.xml b/about/src/main/res/values/strings.xml new file mode 100644 index 0000000..289883c --- /dev/null +++ b/about/src/main/res/values/strings.xml @@ -0,0 +1,34 @@ + + + About + Privacy policy + Version %1$s + Copy + Oops! Something went wrong + Download QR Image + Wallet copied! + support@ark-builders.dev + "Send us the transaction id by email " + " to receive premium support. We’ll make our best to help you with any issue you encounter while using our Software." + bc1qx8n9r4uwpgrhgnamt2uew53lmrxd8tuevp7lv5 + 0x9765C5aC38175BFbd2dC7a840b63e50762B80a1b + Donate using Ethereum + Donate using Bitcoin + https://www.ark-builders.dev/ + https://t.me/ark_builders + https://www.ark-builders.dev/contribute/?tab=goodFirstIssue + https://buymeacoffee.com/arkbuilders + https://www.patreon.com/ARKBuilders + https://discord.gg/tPUJTxud + Discover issues to work on + See open bounties + Buy us a coffee + Donate on Patreon + Donate using ETH + Donate using BTC + We greatly appreciate every bit of support! + Support us + Website + Telegram + Discord + \ No newline at end of file diff --git a/about/src/test/java/dev/arkbuilders/components/about/ExampleUnitTest.kt b/about/src/test/java/dev/arkbuilders/components/about/ExampleUnitTest.kt new file mode 100644 index 0000000..c00b709 --- /dev/null +++ b/about/src/test/java/dev/arkbuilders/components/about/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package dev.arkbuilders.components.about + +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) + } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7e232ea..19dcc50 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,6 +14,10 @@ androidXTestEspresso = "3.5.1" skydovesBalloon = "1.6.4" flexbox = "3.0.0" +composeActivity = "1.9.0" +composeBom = "2024.06.00" +composeCompiler = "1.5.10" + [libraries] coil = { group = "io.coil-kt", name = "coil", version.ref = "coil" } coil-gif = { group = "io.coil-kt", name = "coil-gif", version.ref = "coil" } @@ -34,3 +38,12 @@ androidx-test-junit = { group = "androidx.test.ext", name = "junit", version.ref androidx-test-espresso = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidXTestEspresso" } skydoves-balloon = { group = "com.github.skydoves", name = "balloon", version.ref = "skydovesBalloon" } flexbox = { group = "com.google.android.flexbox", name = "flexbox", version.ref = "flexbox" } + +#Compose +androidx-compose-activity = { group = "androidx.activity", name = "activity-compose", version.ref = "composeActivity" } +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } +androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" } +androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } +androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } +androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } +androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index aef1642..5897047 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -71,6 +71,7 @@ android { dependencies { implementation(project(":filepicker")) + implementation(project(":about")) implementation(libraries.arklib) implementation("androidx.core:core-ktx:1.12.0") diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 8bdb844..4680920 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -33,6 +33,7 @@ + diff --git a/sample/src/main/java/dev/arkbuilders/sample/MainActivity.kt b/sample/src/main/java/dev/arkbuilders/sample/MainActivity.kt index 1ff5093..db201bd 100644 --- a/sample/src/main/java/dev/arkbuilders/sample/MainActivity.kt +++ b/sample/src/main/java/dev/arkbuilders/sample/MainActivity.kt @@ -17,6 +17,7 @@ import dev.arkbuilders.components.filepicker.ArkFilePickerConfig import dev.arkbuilders.components.filepicker.ArkFilePickerFragment import dev.arkbuilders.components.filepicker.ArkFilePickerMode import dev.arkbuilders.components.filepicker.onArkPathPicked +import dev.arkbuilders.sample.about.AboutActivity import dev.arkbuilders.sample.storage.StorageDemoFragment class MainActivity : AppCompatActivity() { @@ -53,6 +54,11 @@ class MainActivity : AppCompatActivity() { findViewById(R.id.btn_storage_demo).setOnClickListener { StorageDemoFragment().show(supportFragmentManager, StorageDemoFragment::class.java.name) } + + findViewById(R.id.btn_about).setOnClickListener { + val intent = Intent(this, AboutActivity::class.java) + startActivity(intent) + } } private fun getFilePickerConfig(mode: ArkFilePickerMode? = null) = ArkFilePickerConfig( diff --git a/sample/src/main/java/dev/arkbuilders/sample/about/AboutActivity.kt b/sample/src/main/java/dev/arkbuilders/sample/about/AboutActivity.kt new file mode 100644 index 0000000..1b9bc47 --- /dev/null +++ b/sample/src/main/java/dev/arkbuilders/sample/about/AboutActivity.kt @@ -0,0 +1,26 @@ +package dev.arkbuilders.sample.about + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import dev.arkbuilders.components.about.ArkAboutFragment +import dev.arkbuilders.sample.BuildConfig +import dev.arkbuilders.sample.R + +class AboutActivity: AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_about) + + val aboutFragment = ArkAboutFragment.create( + appName = getString(R.string.app_name), + appLogoResID = R.drawable.ic_launcher_foreground, + versionName = BuildConfig.VERSION_NAME, + privacyPolicyUrl = "" + ) + + supportFragmentManager + .beginTransaction() + .replace(R.id.about_content, aboutFragment) + .commit() + } +} \ No newline at end of file diff --git a/sample/src/main/res/layout/activity_about.xml b/sample/src/main/res/layout/activity_about.xml new file mode 100644 index 0000000..12aa53b --- /dev/null +++ b/sample/src/main/res/layout/activity_about.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml index 19b94cd..09d74b6 100644 --- a/sample/src/main/res/layout/activity_main.xml +++ b/sample/src/main/res/layout/activity_main.xml @@ -41,4 +41,13 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/btn_open_file_mode"/> + + \ No newline at end of file diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml index 5085fa2..0701830 100644 --- a/sample/src/main/res/values/strings.xml +++ b/sample/src/main/res/values/strings.xml @@ -8,4 +8,5 @@ New map entry Delete map entry Empty + Open About \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 265ee03..b7b6b93 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -39,3 +39,4 @@ include(":folderstree") include(":utils") include(":filepicker") include(":sample") +include(":about")