From 68c1dbbfb36072db83ec6726bbd6bde6c437adc4 Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Fri, 29 Mar 2024 14:48:45 +0000
Subject: [PATCH 01/37] Add https://mrmans0n.github.io/compose-rules/.
---
TODO | 1 -
app/build.gradle | 1 +
detekt.yml | 88 ++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 89 insertions(+), 1 deletion(-)
diff --git a/TODO b/TODO
index 0610b51..8a5ae08 100644
--- a/TODO
+++ b/TODO
@@ -1,5 +1,4 @@
* Try out JetPack compose.
- - add detekt plugin.
* NDK crash reporting
diff --git a/app/build.gradle b/app/build.gradle
index 99fff37..3e711da 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -129,6 +129,7 @@ dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
detektPlugins("io.gitlab.arturbosch.detekt:detekt-rules-libraries:1.23.6")
detektPlugins("io.gitlab.arturbosch.detekt:detekt-rules-ruleauthors:1.23.6")
+ detektPlugins 'io.nlopez.compose.rules:detekt:0.3.12'
}
task writeVersionFile {
diff --git a/detekt.yml b/detekt.yml
index a3968ad..51d7095 100644
--- a/detekt.yml
+++ b/detekt.yml
@@ -1,3 +1,91 @@
empty-blocks:
EmptyFunctionBlock:
ignoreOverridden: true
+Compose:
+ ComposableAnnotationNaming:
+ active: true
+ ComposableNaming:
+ active: true
+ # -- You can optionally disable the checks in this rule for regex matches against the composable name (e.g. molecule presenters)
+ # allowedComposableFunctionNames: .*Presenter,.*MoleculePresenter
+ ComposableParamOrder:
+ active: true
+ # -- You can optionally have a list of types to be treated as lambdas (e.g. typedefs or fun interfaces not picked up automatically)
+ # treatAsLambda: MyLambdaType
+ CompositionLocalAllowlist:
+ active: true
+ # -- You can optionally define a list of CompositionLocals that are allowed here
+ # allowedCompositionLocals: LocalSomething,LocalSomethingElse
+ CompositionLocalNaming:
+ active: true
+ ContentEmitterReturningValues:
+ active: true
+ # -- You can optionally add your own composables here
+ # contentEmitters: MyComposable,MyOtherComposable
+ DefaultsVisibility:
+ active: true
+ LambdaParameterInRestartableEffect:
+ active: true
+ # -- You can optionally have a list of types to be treated as lambdas (e.g. typedefs or fun interfaces not picked up automatically)
+ # treatAsLambda: MyLambdaType
+ ModifierClickableOrder:
+ active: true
+ # -- You can optionally add your own Modifier types
+ # customModifiers: BananaModifier,PotatoModifier
+ ModifierComposable:
+ active: true
+ # -- You can optionally add your own Modifier types
+ # customModifiers: BananaModifier,PotatoModifier
+ ModifierMissing:
+ active: true
+ # -- You can optionally control the visibility of which composables to check for here
+ # -- Possible values are: `only_public`, `public_and_internal` and `all` (default is `only_public`)
+ # checkModifiersForVisibility: only_public
+ # -- You can optionally add your own Modifier types
+ # customModifiers: BananaModifier,PotatoModifier
+ ModifierNaming:
+ active: true
+ # -- You can optionally add your own Modifier types
+ # customModifiers: BananaModifier,PotatoModifier
+ ModifierNotUsedAtRoot:
+ active: true
+ # -- You can optionally add your own composables here
+ # contentEmitters: MyComposable,MyOtherComposable
+ # -- You can optionally add your own Modifier types
+ # customModifiers: BananaModifier,PotatoModifier
+ ModifierReused:
+ active: true
+ # -- You can optionally add your own Modifier types
+ # customModifiers: BananaModifier,PotatoModifier
+ ModifierWithoutDefault:
+ active: true
+ MultipleEmitters:
+ active: true
+ # -- You can optionally add your own composables here that will count as content emitters
+ # contentEmitters: MyComposable,MyOtherComposable
+ # -- You can add composables here that you don't want to count as content emitters (e.g. custom dialogs or modals)
+ # contentEmittersDenylist: MyNonEmitterComposable
+ MutableParams:
+ active: true
+ MutableStateParam:
+ active: true
+ PreviewAnnotationNaming:
+ active: true
+ PreviewPublic:
+ active: true
+ RememberMissing:
+ active: true
+ RememberContentMissing:
+ active: true
+ UnstableCollections:
+ active: true
+ ViewModelForwarding:
+ active: true
+ # -- You can optionally use this rule on things other than types ending in "ViewModel" or "Presenter" (which are the defaults). You can add your own via a regex here:
+ # allowedStateHolderNames: .*ViewModel,.*Presenter
+ # -- You can optionally add an allowlist for Composable names that won't be affected by this rule
+ # allowedForwarding: .*Content,.*FancyStuff
+ ViewModelInjection:
+ active: true
+ # -- You can optionally add your own ViewModel factories here
+ # viewModelFactories: hiltViewModel,potatoViewModel
From 3123acff5c9cccfefd7940184cc4667a4c214b51 Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Fri, 29 Mar 2024 15:09:25 +0000
Subject: [PATCH 02/37] Add JetPack Compose dependencies and config.
---
app/build.gradle | 17 +++++++++++++++++
build.gradle | 2 +-
dependencies.gradle | 5 +++++
3 files changed, 23 insertions(+), 1 deletion(-)
create mode 100644 dependencies.gradle
diff --git a/app/build.gradle b/app/build.gradle
index 3e711da..621472f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -8,6 +8,8 @@ plugins {
id 'io.gitlab.arturbosch.detekt'
}
+apply from: '../dependencies.gradle'
+
static String getBranchName() {
final branchCommand = ["git", "symbolic-ref", "HEAD", "--short"]
return branchCommand.execute().text.trim()
@@ -57,6 +59,7 @@ android {
buildFeatures {
viewBinding = true
buildConfig = true
+ compose = true
}
signingConfigs {
release {
@@ -75,6 +78,9 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
+ composeOptions {
+ kotlinCompilerExtensionVersion = project.ext.kotlinCompilerExtensionVersion
+ }
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
@@ -100,7 +106,16 @@ detekt {
dependencies {
def lifecycle_version = "2.6.2"
def room_version = "2.6.1"
+ def composeBom = platform('androidx.compose:compose-bom:2024.03.00')
implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ // JetPack Compose
+ implementation composeBom
+ implementation 'androidx.compose.material3:material3'
+ implementation 'androidx.compose.ui:ui-tooling-preview'
+ debugImplementation 'androidx.compose.ui:ui-tooling'
+ debugImplementation 'androidx.compose.ui:ui-test-manifest'
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1"
implementation 'androidx.annotation:annotation:1.7.0'
@@ -127,6 +142,8 @@ dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+ androidTestImplementation composeBom
+ androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
detektPlugins("io.gitlab.arturbosch.detekt:detekt-rules-libraries:1.23.6")
detektPlugins("io.gitlab.arturbosch.detekt:detekt-rules-ruleauthors:1.23.6")
detektPlugins 'io.nlopez.compose.rules:detekt:0.3.12'
diff --git a/build.gradle b/build.gradle
index b6a8faf..7c9f460 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.9.21'
+ apply from: 'dependencies.gradle'
repositories {
google()
mavenCentral()
diff --git a/dependencies.gradle b/dependencies.gradle
new file mode 100644
index 0000000..eccbfe4
--- /dev/null
+++ b/dependencies.gradle
@@ -0,0 +1,5 @@
+ext {
+ kotlin_version = '1.9.21'
+ // https://developer.android.com/jetpack/androidx/releases/compose-kotlin#pre-release_kotlin_compatibility
+ kotlinCompilerExtensionVersion = '1.5.7'
+}
From a9fe1282c06c7feff60376aa2363e9998e4cb68c Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Fri, 3 May 2024 16:32:14 +0100
Subject: [PATCH 03/37] Start to reimplement OnboardingActivity using Jetpack
Compose.
---
app/build.gradle | 1 +
.../merging/onboarding/OnboardingActivity.kt | 139 +++++++++++-------
2 files changed, 85 insertions(+), 55 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 621472f..bb9e9c0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -113,6 +113,7 @@ dependencies {
implementation composeBom
implementation 'androidx.compose.material3:material3'
implementation 'androidx.compose.ui:ui-tooling-preview'
+ implementation 'androidx.activity:activity-compose'
debugImplementation 'androidx.compose.ui:ui-tooling'
debugImplementation 'androidx.compose.ui:ui-test-manifest'
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt
index fe43a2e..c048b9f 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt
@@ -1,18 +1,30 @@
package uk.me.jeremygreen.merging.onboarding
import android.content.Intent
-import android.graphics.Color
-import android.net.Uri
import android.os.Bundle
-import android.view.View
-import android.webkit.WebView
-import android.webkit.WebViewClient
+import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
-import uk.me.jeremygreen.merging.databinding.OnboardingBinding
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FloatingActionButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import uk.me.jeremygreen.merging.R
import uk.me.jeremygreen.merging.main.MainActivity
import uk.me.jeremygreen.merging.model.AppViewModel
-
internal class OnboardingActivity: AppCompatActivity() {
companion object {
@@ -23,67 +35,84 @@ internal class OnboardingActivity: AppCompatActivity() {
}
- private lateinit var binding: OnboardingBinding
private lateinit var appViewModel: AppViewModel
// Activity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- binding = OnboardingBinding.inflate(layoutInflater)
- setContentView(binding.root)
appViewModel = AppViewModel.getInstance(this, application)
- setSupportActionBar(binding.onboardingToolbar)
- binding.onboardingWebView.loadUrl(PRIVACY_HTML)
- binding.onboardingWebView.setBackgroundColor(Color.TRANSPARENT)
- binding.onboardingWebView.webViewClient = object : WebViewClient() {
- override fun onPageFinished(view: WebView?, url: String?) {
- binding.onboardingAcceptCheckbox.visibility = View.VISIBLE
- }
- override fun shouldOverrideUrlLoading(webView: WebView?, url: String): Boolean {
- if (url.startsWith("mailto:")) {
- val intent = Intent(Intent.ACTION_VIEW)
- intent.setData(Uri.parse(url))
- startActivity(intent)
- return true
- }
- return false
- }
- }
- }
-
- // Activity
- override fun onResume() {
- super.onResume()
- binding.onboardingAcceptButton.setOnClickListener {
- binding.onboardingAcceptButton.setOnClickListener(null)
- appViewModel.acceptOnboarding(version)
- val intent = Intent(this, MainActivity::class.java)
- intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_TASK_ON_HOME
- startActivity(intent)
- finish()
- }
- binding.onboardingAcceptCheckbox.setOnClickListener {
- updateFabState()
+ setContent {
+ Onboarding()
}
- updateFabState()
+ //setSupportActionBar(binding.onboardingToolbar)
+// binding.onboardingWebView.loadUrl(PRIVACY_HTML)
+// binding.onboardingWebView.setBackgroundColor(Color.TRANSPARENT)
+// binding.onboardingWebView.webViewClient = object : WebViewClient() {
+// override fun onPageFinished(view: WebView?, url: String?) {
+// binding.onboardingAcceptCheckbox.visibility = View.VISIBLE
+// }
+// override fun shouldOverrideUrlLoading(webView: WebView?, url: String): Boolean {
+// if (url.startsWith("mailto:")) {
+// val intent = Intent(Intent.ACTION_VIEW)
+// intent.setData(Uri.parse(url))
+// startActivity(intent)
+// return true
+// }
+// return false
+// }
+// }
}
/**
* Update FloatingActionButton properties etc.
*/
- private fun updateFabState() {
- if (binding.onboardingAcceptCheckbox.isChecked) {
- binding.onboardingAcceptButton.show()
- } else {
- binding.onboardingAcceptButton.hide()
- }
- }
+// private fun updateFabState() {
+// if (binding.onboardingAcceptCheckbox.isChecked) {
+// binding.onboardingAcceptButton.show()
+// } else {
+// binding.onboardingAcceptButton.hide()
+// }
+// }
- // Activity
- override fun onPause() {
- binding.onboardingAcceptButton.setOnClickListener(null)
- binding.onboardingAcceptCheckbox.setOnClickListener(null)
- super.onPause()
+
+ @OptIn(ExperimentalMaterial3Api::class)
+ @Composable
+ fun Onboarding() {
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ colors = TopAppBarDefaults.topAppBarColors(
+ containerColor = MaterialTheme.colorScheme.primaryContainer,
+ titleContentColor = MaterialTheme.colorScheme.primary,
+ ),
+ title = { Text(text = stringResource(R.string.appName)) }
+ )
+ },
+ floatingActionButton = {
+ FloatingActionButton(
+ onClick = { appViewModel.acceptOnboarding(version)
+ val intent = Intent(this, MainActivity::class.java)
+ intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_TASK_ON_HOME
+ startActivity(intent)
+ finish()
+
+ }
+ ) {
+ Icon(
+ Icons.Default.Add,
+ contentDescription = "Add"
+ )
+ }
+ }
+ ) { innerPadding ->
+ Column(
+ modifier = Modifier
+ .padding(innerPadding),
+ verticalArrangement = Arrangement.spacedBy(16.dp),
+ ) {
+ Text(text = "onboarding text")
+ }
+ }
}
}
From 3ece853dabbb883ed4927acf8930bfbfa7d7de4a Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Fri, 3 May 2024 16:35:04 +0100
Subject: [PATCH 04/37] TODO
---
TODO | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/TODO b/TODO
index 8a5ae08..0f5dcc3 100644
--- a/TODO
+++ b/TODO
@@ -1,4 +1,12 @@
-* Try out JetPack compose.
+* Try out Jetpack Compose.
+ - checkbox
+ - webview
+ - webview rendering refinements
+ - is double click protection required?
+ - remove onboarding.xml
+ - styling of onboarding doesn't match rest of app
+ - convert all app to Jetpack Compose?
+ - adjust styling?
* NDK crash reporting
From 4ae64a5d8d5a1f6353fa2a3db29f3115c108b1e1 Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Fri, 3 May 2024 17:01:22 +0100
Subject: [PATCH 05/37] Add Switch for onboarding text agreement.
---
TODO | 6 ++-
.../merging/onboarding/OnboardingActivity.kt | 38 +++++++++++++------
2 files changed, 30 insertions(+), 14 deletions(-)
diff --git a/TODO b/TODO
index 0f5dcc3..a904078 100644
--- a/TODO
+++ b/TODO
@@ -1,9 +1,11 @@
* Try out Jetpack Compose.
- - checkbox
+ X checkbox
- webview
+ - remove onboarding.xml
- webview rendering refinements
+ - checkbox animation
+ - AnimatedVisibility?
- is double click protection required?
- - remove onboarding.xml
- styling of onboarding doesn't match rest of app
- convert all app to Jetpack Compose?
- adjust styling?
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt
index c048b9f..f3d2877 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt
@@ -14,10 +14,15 @@ import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
@@ -78,6 +83,7 @@ internal class OnboardingActivity: AppCompatActivity() {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Onboarding() {
+ var agreed by rememberSaveable { mutableStateOf(false) }
Scaffold(
topBar = {
TopAppBar(
@@ -89,19 +95,22 @@ internal class OnboardingActivity: AppCompatActivity() {
)
},
floatingActionButton = {
- FloatingActionButton(
- onClick = { appViewModel.acceptOnboarding(version)
- val intent = Intent(this, MainActivity::class.java)
- intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_TASK_ON_HOME
- startActivity(intent)
- finish()
-
+ if (agreed) {
+ FloatingActionButton(
+ onClick = {
+ appViewModel.acceptOnboarding(version)
+ val intent = Intent(this, MainActivity::class.java)
+ intent.flags =
+ Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_TASK_ON_HOME
+ startActivity(intent)
+ finish()
+ },
+ ) {
+ Icon(
+ Icons.Default.Add,
+ contentDescription = "Add"
+ )
}
- ) {
- Icon(
- Icons.Default.Add,
- contentDescription = "Add"
- )
}
}
) { innerPadding ->
@@ -111,6 +120,11 @@ internal class OnboardingActivity: AppCompatActivity() {
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
Text(text = "onboarding text")
+ Switch(
+ checked = agreed,
+ onCheckedChange = {
+ agreed = it
+ })
}
}
}
From 712e06f4157b5f69233d372fb418c6a315cfd5a8 Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Fri, 3 May 2024 18:07:45 +0100
Subject: [PATCH 06/37] Scale in/out animation for FloatingActionButton.
---
TODO | 3 +--
.../merging/onboarding/OnboardingActivity.kt | 11 +++++++++--
2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/TODO b/TODO
index a904078..a1207aa 100644
--- a/TODO
+++ b/TODO
@@ -3,8 +3,7 @@
- webview
- remove onboarding.xml
- webview rendering refinements
- - checkbox animation
- - AnimatedVisibility?
+ X checkbox animation
- is double click protection required?
- styling of onboarding doesn't match rest of app
- convert all app to Jetpack Compose?
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt
index f3d2877..071c776 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt
@@ -4,6 +4,9 @@ import android.content.Intent
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.scaleIn
+import androidx.compose.animation.scaleOut
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
@@ -95,11 +98,15 @@ internal class OnboardingActivity: AppCompatActivity() {
)
},
floatingActionButton = {
- if (agreed) {
+ AnimatedVisibility(
+ visible = agreed,
+ enter = scaleIn(),
+ exit = scaleOut()
+ ) {
FloatingActionButton(
onClick = {
appViewModel.acceptOnboarding(version)
- val intent = Intent(this, MainActivity::class.java)
+ val intent = Intent(this@OnboardingActivity, MainActivity::class.java)
intent.flags =
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_TASK_ON_HOME
startActivity(intent)
From 8c16184b33a90e3c73e4f474455e26409032ce27 Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Fri, 3 May 2024 21:13:41 +0100
Subject: [PATCH 07/37] Add WebView for onboarding text.
---
.../merging/onboarding/OnboardingActivity.kt | 49 +++++++------------
1 file changed, 18 insertions(+), 31 deletions(-)
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt
index 071c776..418e242 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt
@@ -2,6 +2,7 @@ package uk.me.jeremygreen.merging.onboarding
import android.content.Intent
import android.os.Bundle
+import android.webkit.WebView
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
@@ -29,6 +30,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
import uk.me.jeremygreen.merging.R
import uk.me.jeremygreen.merging.main.MainActivity
import uk.me.jeremygreen.merging.model.AppViewModel
@@ -52,40 +54,11 @@ internal class OnboardingActivity: AppCompatActivity() {
setContent {
Onboarding()
}
- //setSupportActionBar(binding.onboardingToolbar)
-// binding.onboardingWebView.loadUrl(PRIVACY_HTML)
-// binding.onboardingWebView.setBackgroundColor(Color.TRANSPARENT)
-// binding.onboardingWebView.webViewClient = object : WebViewClient() {
-// override fun onPageFinished(view: WebView?, url: String?) {
-// binding.onboardingAcceptCheckbox.visibility = View.VISIBLE
-// }
-// override fun shouldOverrideUrlLoading(webView: WebView?, url: String): Boolean {
-// if (url.startsWith("mailto:")) {
-// val intent = Intent(Intent.ACTION_VIEW)
-// intent.setData(Uri.parse(url))
-// startActivity(intent)
-// return true
-// }
-// return false
-// }
-// }
}
- /**
- * Update FloatingActionButton properties etc.
- */
-// private fun updateFabState() {
-// if (binding.onboardingAcceptCheckbox.isChecked) {
-// binding.onboardingAcceptButton.show()
-// } else {
-// binding.onboardingAcceptButton.hide()
-// }
-// }
-
-
@OptIn(ExperimentalMaterial3Api::class)
@Composable
- fun Onboarding() {
+ private fun Onboarding() {
var agreed by rememberSaveable { mutableStateOf(false) }
Scaffold(
topBar = {
@@ -126,7 +99,7 @@ internal class OnboardingActivity: AppCompatActivity() {
.padding(innerPadding),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
- Text(text = "onboarding text")
+ WebView(url = PRIVACY_HTML)
Switch(
checked = agreed,
onCheckedChange = {
@@ -136,4 +109,18 @@ internal class OnboardingActivity: AppCompatActivity() {
}
}
+ @Composable
+ private fun WebView(
+ @Suppress("SameParameterValue") url: String
+ ) {
+ AndroidView(
+ factory = { context ->
+ return@AndroidView WebView(context)
+ },
+ update = {
+ it.loadUrl(url)
+ }
+ )
+ }
+
}
From 39933cea3004868e3f21abf40e5cc702a0df763e Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Fri, 3 May 2024 21:14:29 +0100
Subject: [PATCH 08/37] Remove unused onboarding.xml.
---
app/src/main/res/layout/onboarding.xml | 61 --------------------------
1 file changed, 61 deletions(-)
delete mode 100644 app/src/main/res/layout/onboarding.xml
diff --git a/app/src/main/res/layout/onboarding.xml b/app/src/main/res/layout/onboarding.xml
deleted file mode 100644
index 2117bb8..0000000
--- a/app/src/main/res/layout/onboarding.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
From e823f1be6562c5537fce9751619a38b4dad3c39e Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Fri, 3 May 2024 21:26:16 +0100
Subject: [PATCH 09/37] Suppress detekt warning about Composable function names
that start with capital letter.
https://detekt.dev/docs/introduction/compose/#functionnaming-for-compose.
---
detekt.yml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/detekt.yml b/detekt.yml
index 51d7095..c5dbb84 100644
--- a/detekt.yml
+++ b/detekt.yml
@@ -1,6 +1,10 @@
empty-blocks:
EmptyFunctionBlock:
ignoreOverridden: true
+naming:
+ FunctionNaming:
+ ignoreAnnotated:
+ - 'Composable'
Compose:
ComposableAnnotationNaming:
active: true
From 9c5c40fb452372e14f33602c03ebe476e5de8182 Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Fri, 3 May 2024 21:27:19 +0100
Subject: [PATCH 10/37] TODO
---
TODO | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/TODO b/TODO
index a1207aa..3014c1d 100644
--- a/TODO
+++ b/TODO
@@ -1,9 +1,5 @@
* Try out Jetpack Compose.
- X checkbox
- - webview
- - remove onboarding.xml
- - webview rendering refinements
- X checkbox animation
+ - fix layout
- is double click protection required?
- styling of onboarding doesn't match rest of app
- convert all app to Jetpack Compose?
From 466f76dfc925334470411415a53bc8145f08b7af Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Mon, 6 May 2024 16:48:15 +0100
Subject: [PATCH 11/37] Add back code that delays showing rest of onboarding UI
until the WebView has loaded.
---
.../merging/onboarding/OnboardingActivity.kt | 28 +++++++++++++------
1 file changed, 20 insertions(+), 8 deletions(-)
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt
index 418e242..bf7bd51 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt
@@ -3,6 +3,7 @@ package uk.me.jeremygreen.merging.onboarding
import android.content.Intent
import android.os.Bundle
import android.webkit.WebView
+import android.webkit.WebViewClient
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
@@ -25,6 +26,7 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
@@ -60,6 +62,7 @@ internal class OnboardingActivity: AppCompatActivity() {
@Composable
private fun Onboarding() {
var agreed by rememberSaveable { mutableStateOf(false) }
+ var webViewLoaded by remember { mutableStateOf(false) }
Scaffold(
topBar = {
TopAppBar(
@@ -99,23 +102,32 @@ internal class OnboardingActivity: AppCompatActivity() {
.padding(innerPadding),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
- WebView(url = PRIVACY_HTML)
- Switch(
- checked = agreed,
- onCheckedChange = {
- agreed = it
- })
+ WebView(url = PRIVACY_HTML, onLoaded = { webViewLoaded = true })
+ if (webViewLoaded) {
+ Switch(
+ checked = agreed,
+ onCheckedChange = {
+ agreed = it
+ })
+ }
}
}
}
@Composable
private fun WebView(
- @Suppress("SameParameterValue") url: String
+ @Suppress("SameParameterValue") url: String,
+ onLoaded: () -> Unit
) {
AndroidView(
factory = { context ->
- return@AndroidView WebView(context)
+ return@AndroidView WebView(context).apply {
+ webViewClient = object : WebViewClient() {
+ override fun onPageFinished(view: WebView?, url: String?) {
+ onLoaded()
+ }
+ }
+ }
},
update = {
it.loadUrl(url)
From c5bc3ee408e6a930914a7d3327d27448b36f3443 Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Mon, 6 May 2024 16:48:32 +0100
Subject: [PATCH 12/37] Code formatting.
---
.../uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt
index bf7bd51..c97f230 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt
@@ -98,8 +98,7 @@ internal class OnboardingActivity: AppCompatActivity() {
}
) { innerPadding ->
Column(
- modifier = Modifier
- .padding(innerPadding),
+ modifier = Modifier.padding(innerPadding),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
WebView(url = PRIVACY_HTML, onLoaded = { webViewLoaded = true })
From 88df056d84eee57c4a1bac0e6d8de5e2ad7a7a0f Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Mon, 6 May 2024 17:00:20 +0100
Subject: [PATCH 13/37] Indent onboarding accept Switch.
---
TODO | 2 --
.../merging/onboarding/OnboardingActivity.kt | 15 ++++++++++-----
2 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/TODO b/TODO
index 3014c1d..35b11c4 100644
--- a/TODO
+++ b/TODO
@@ -1,6 +1,4 @@
* Try out Jetpack Compose.
- - fix layout
- - is double click protection required?
- styling of onboarding doesn't match rest of app
- convert all app to Jetpack Compose?
- adjust styling?
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt
index c97f230..51f48f4 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/onboarding/OnboardingActivity.kt
@@ -103,11 +103,16 @@ internal class OnboardingActivity: AppCompatActivity() {
) {
WebView(url = PRIVACY_HTML, onLoaded = { webViewLoaded = true })
if (webViewLoaded) {
- Switch(
- checked = agreed,
- onCheckedChange = {
- agreed = it
- })
+ Column(
+ modifier = Modifier.padding(horizontal = 16.dp),
+ ) {
+ Switch(
+ checked = agreed,
+ onCheckedChange = {
+ agreed = it
+ }
+ )
+ }
}
}
}
From 5c4e5ccf819bc3db83cd2bd8789d9e3e409a6e6e Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Mon, 6 May 2024 17:26:44 +0100
Subject: [PATCH 14/37] Change AboutActivity to JetPack compose.
---
TODO | 1 +
.../merging/about/AboutActivity.kt | 79 +++++++++++++++----
2 files changed, 64 insertions(+), 16 deletions(-)
diff --git a/TODO b/TODO
index 35b11c4..ce980c2 100644
--- a/TODO
+++ b/TODO
@@ -1,4 +1,5 @@
* Try out Jetpack Compose.
+ - deduplicate shared layout code.
- styling of onboarding doesn't match rest of app
- convert all app to Jetpack Compose?
- adjust styling?
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/about/AboutActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/about/AboutActivity.kt
index e017de6..c91223f 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/about/AboutActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/about/AboutActivity.kt
@@ -1,7 +1,26 @@
package uk.me.jeremygreen.merging.about
import android.os.Bundle
+import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
import uk.me.jeremygreen.merging.BuildConfig
import uk.me.jeremygreen.merging.R
import uk.me.jeremygreen.merging.databinding.AboutBinding
@@ -10,29 +29,57 @@ internal class AboutActivity: AppCompatActivity() {
private val versionName by lazy { packageManager.getPackageInfo(packageName, 0).versionName }
- private lateinit var binding: AboutBinding
-
// Activity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- binding = AboutBinding.inflate(layoutInflater)
- setContentView(binding.root)
- setSupportActionBar(binding.aboutToolbar)
- binding.appVersion.text = getString(R.string.version, BuildConfig.APPLICATION_ID, versionName)
+ setContent { About() }
}
- // Activity
- override fun onResume() {
- super.onResume()
- binding.aboutToolbar.setNavigationOnClickListener {
- finish()
+ @OptIn(ExperimentalMaterial3Api::class)
+ @Composable
+ fun About() {
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ colors = TopAppBarDefaults.topAppBarColors(
+ containerColor = MaterialTheme.colorScheme.primaryContainer,
+ titleContentColor = MaterialTheme.colorScheme.primary,
+ ),
+ title = { Text(text = stringResource(R.string.actionAbout)) },
+ navigationIcon = {
+ IconButton(onClick = { finish() }) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Filled.ArrowBack,
+ contentDescription = "Back",
+ tint = Color.White
+ )
+ }
+ }
+ )
+ }
+ ) { innerPadding ->
+ Column(
+ modifier = Modifier.padding(innerPadding),
+ ) {
+ Column(modifier = Modifier.padding(16.dp)) {
+ Text(stringResource(R.string.version, BuildConfig.APPLICATION_ID, versionName))
+ }
+ }
}
}
- // Activity
- override fun onPause() {
- super.onPause()
- binding.aboutToolbar.setNavigationOnClickListener(null)
- }
+// // Activity
+// override fun onResume() {
+// super.onResume()
+// binding.aboutToolbar.setNavigationOnClickListener {
+// finish()
+// }
+// }
+//
+// // Activity
+// override fun onPause() {
+// super.onPause()
+// binding.aboutToolbar.setNavigationOnClickListener(null)
+// }
}
From f216387807351e16175a5b168bdfe367775188ad Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Mon, 6 May 2024 22:40:40 +0100
Subject: [PATCH 15/37] detekt wants reusable Composible functions to have
modifier. Avoid by making the component private.
https://mrmans0n.github.io/compose-rules/rules/#when-should-i-expose-modifier-parameters.
Remove commented out code too.
---
.../jeremygreen/merging/about/AboutActivity.kt | 16 +---------------
1 file changed, 1 insertion(+), 15 deletions(-)
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/about/AboutActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/about/AboutActivity.kt
index c91223f..9fc65c2 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/about/AboutActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/about/AboutActivity.kt
@@ -37,7 +37,7 @@ internal class AboutActivity: AppCompatActivity() {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
- fun About() {
+ private fun About() {
Scaffold(
topBar = {
TopAppBar(
@@ -68,18 +68,4 @@ internal class AboutActivity: AppCompatActivity() {
}
}
-// // Activity
-// override fun onResume() {
-// super.onResume()
-// binding.aboutToolbar.setNavigationOnClickListener {
-// finish()
-// }
-// }
-//
-// // Activity
-// override fun onPause() {
-// super.onPause()
-// binding.aboutToolbar.setNavigationOnClickListener(null)
-// }
-
}
From efa820a754fa749211cc24ea1de37351614f95bb Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Tue, 7 May 2024 09:57:17 +0100
Subject: [PATCH 16/37] Refactor AboutActivity About component.
---
.../merging/about/AboutActivity.kt | 32 ++++++++++++-------
1 file changed, 20 insertions(+), 12 deletions(-)
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/about/AboutActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/about/AboutActivity.kt
index 9fc65c2..f56a391 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/about/AboutActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/about/AboutActivity.kt
@@ -46,26 +46,34 @@ internal class AboutActivity: AppCompatActivity() {
titleContentColor = MaterialTheme.colorScheme.primary,
),
title = { Text(text = stringResource(R.string.actionAbout)) },
- navigationIcon = {
- IconButton(onClick = { finish() }) {
- Icon(
- imageVector = Icons.AutoMirrored.Filled.ArrowBack,
- contentDescription = "Back",
- tint = Color.White
- )
- }
- }
+ navigationIcon = { BackButton() }
)
}
) { innerPadding ->
Column(
modifier = Modifier.padding(innerPadding),
) {
- Column(modifier = Modifier.padding(16.dp)) {
- Text(stringResource(R.string.version, BuildConfig.APPLICATION_ID, versionName))
- }
+ AboutText()
}
}
}
+ @Composable
+ private fun AboutText() {
+ Column(modifier = Modifier.padding(16.dp)) {
+ Text(stringResource(R.string.version, BuildConfig.APPLICATION_ID, versionName))
+ }
+ }
+
+ @Composable
+ private fun BackButton() {
+ IconButton(onClick = { finish() }) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Filled.ArrowBack,
+ contentDescription = "Back",
+ tint = Color.White
+ )
+ }
+ }
+
}
From b02f6eece8a270138cede3c563fb7f2964189458 Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Tue, 7 May 2024 10:17:13 +0100
Subject: [PATCH 17/37] Remove about.xml layout.
---
app/src/main/res/layout/about.xml | 42 -------------------------------
1 file changed, 42 deletions(-)
delete mode 100644 app/src/main/res/layout/about.xml
diff --git a/app/src/main/res/layout/about.xml b/app/src/main/res/layout/about.xml
deleted file mode 100644
index 76f4267..0000000
--- a/app/src/main/res/layout/about.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
From 54091696a0161f178517ab98db7b314a8fd8905c Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Tue, 7 May 2024 10:17:16 +0100
Subject: [PATCH 18/37] TODO
---
TODO | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/TODO b/TODO
index ce980c2..a9ca64d 100644
--- a/TODO
+++ b/TODO
@@ -1,8 +1,12 @@
* Try out Jetpack Compose.
- - deduplicate shared layout code.
- - styling of onboarding doesn't match rest of app
- - convert all app to Jetpack Compose?
- - adjust styling?
+ - update to material 3 design
+ x onboarding
+ x about
+ - licences
+ - https://github.com/google/play-services-plugins/issues/248
+ - https://stackoverflow.com/questions/76852715/how-to-use-open-source-notices-library-with-jetpack-compose
+ - https://github.com/jaredsburrows/gradle-license-plugin
+ - main
* NDK crash reporting
From 39ca679d6ec3fde41bd594f6f80dfb568920e095 Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Tue, 7 May 2024 11:01:19 +0100
Subject: [PATCH 19/37] Remove unused imports.
---
.../main/java/uk/me/jeremygreen/merging/about/AboutActivity.kt | 2 --
1 file changed, 2 deletions(-)
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/about/AboutActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/about/AboutActivity.kt
index f56a391..43191ad 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/about/AboutActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/about/AboutActivity.kt
@@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
-import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
@@ -23,7 +22,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import uk.me.jeremygreen.merging.BuildConfig
import uk.me.jeremygreen.merging.R
-import uk.me.jeremygreen.merging.databinding.AboutBinding
internal class AboutActivity: AppCompatActivity() {
From c462aea19df8e634400275fa5b853376d93f8def Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Thu, 9 May 2024 16:42:51 +0100
Subject: [PATCH 20/37] TODO
---
TODO | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/TODO b/TODO
index a9ca64d..f25e582 100644
--- a/TODO
+++ b/TODO
@@ -6,6 +6,10 @@
- https://github.com/google/play-services-plugins/issues/248
- https://stackoverflow.com/questions/76852715/how-to-use-open-source-notices-library-with-jetpack-compose
- https://github.com/jaredsburrows/gradle-license-plugin
+ - tasks generate html, but aren't run automatically when build
+ - ./gradlew tasks breaks with AndroidSourceSet with name 'developDebugAndroidTest' not found.
+ => a pain to add dependencies
+ -
- main
* NDK crash reporting
From f038f55b77d014b9f498885030d2187c6ec41c3a Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Thu, 9 May 2024 16:43:22 +0100
Subject: [PATCH 21/37] Partial attempt at using
https://github.com/jaredsburrows/gradle-license-plugin.
---
.gitignore | 3 +++
app/build.gradle | 7 +++++--
.../java/uk/me/jeremygreen/merging/main/MainActivity.kt | 7 ++-----
build.gradle | 2 +-
4 files changed, 11 insertions(+), 8 deletions(-)
diff --git a/.gitignore b/.gitignore
index ccbf754..6e38d7f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,6 @@
.project
.settings
.classpath
+
+# https://github.com/jaredsburrows/gradle-license-plugin
+open_source_licenses.html
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index bb9e9c0..5088874 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,11 +1,11 @@
plugins {
id 'com.android.application'
- id 'com.google.android.gms.oss-licenses-plugin'
id 'kotlin-android'
id 'com.google.devtools.ksp'
id 'com.google.gms.google-services'
id 'com.google.firebase.crashlytics'
id 'io.gitlab.arturbosch.detekt'
+ id 'com.jaredsburrows.license'
}
apply from: '../dependencies.gradle'
@@ -98,6 +98,10 @@ android {
namespace 'uk.me.jeremygreen.merging'
}
+licenseReport {
+ useVariantSpecificAssetDirs = true
+}
+
detekt {
buildUponDefaultConfig = true
config.setFrom("../detekt.yml")
@@ -135,7 +139,6 @@ dependencies {
implementation 'com.google.firebase:firebase-crashlytics' // versioned with BOM
implementation 'com.google.firebase:firebase-analytics' // versioned with BOM
implementation 'com.google.android.gms:play-services-mlkit-face-detection:16.1.4'
- implementation 'com.google.android.gms:play-services-oss-licenses:17.0.0'
implementation 'com.google.android.material:material:1.10.0'
ksp "androidx.room:room-compiler:${room_version}"
testImplementation "androidx.arch.core:core-testing:2.1.0"
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
index c90508f..fec9f82 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
@@ -11,7 +11,6 @@ import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import androidx.viewpager2.widget.ViewPager2
-import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import uk.me.jeremygreen.merging.BuildConfig
@@ -80,8 +79,7 @@ internal class MainActivity : AppCompatActivity() {
this.appViewModel.allImages().observe(this) { images ->
this.pagerAdapter.setImages(images)
}
- val licencesTitle = resources.getString(R.string.actionLicences)
- OssLicensesMenuActivity.setActivityTitle(licencesTitle)
+ val licencesTitle = resources.getString(R.string.actionLicences) //
}
private fun screenView(screenName: String?) {
@@ -118,8 +116,7 @@ internal class MainActivity : AppCompatActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.actionLicences -> {
- val intent = Intent(this, OssLicensesMenuActivity::class.java)
- startActivity(intent)
+ // TODO
true
}
R.id.actionAbout -> {
diff --git a/build.gradle b/build.gradle
index 7c9f460..625d765 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,7 +9,7 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:8.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
- classpath 'com.google.android.gms:oss-licenses-plugin:0.10.6'
+ classpath 'com.jaredsburrows:gradle-license-plugin:0.9.7'
classpath 'com.google.gms:google-services:4.4.0'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1'
classpath 'io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.3'
From bb0832c04369b64289ad6aee1a42eca424165abb Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Thu, 9 May 2024 16:43:53 +0100
Subject: [PATCH 22/37] Revert "Partial attempt at using
https://github.com/jaredsburrows/gradle-license-plugin."
This reverts commit f038f55b77d014b9f498885030d2187c6ec41c3a.
---
.gitignore | 3 ---
app/build.gradle | 7 ++-----
.../java/uk/me/jeremygreen/merging/main/MainActivity.kt | 7 +++++--
build.gradle | 2 +-
4 files changed, 8 insertions(+), 11 deletions(-)
diff --git a/.gitignore b/.gitignore
index 6e38d7f..ccbf754 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,3 @@
.project
.settings
.classpath
-
-# https://github.com/jaredsburrows/gradle-license-plugin
-open_source_licenses.html
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 5088874..bb9e9c0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,11 +1,11 @@
plugins {
id 'com.android.application'
+ id 'com.google.android.gms.oss-licenses-plugin'
id 'kotlin-android'
id 'com.google.devtools.ksp'
id 'com.google.gms.google-services'
id 'com.google.firebase.crashlytics'
id 'io.gitlab.arturbosch.detekt'
- id 'com.jaredsburrows.license'
}
apply from: '../dependencies.gradle'
@@ -98,10 +98,6 @@ android {
namespace 'uk.me.jeremygreen.merging'
}
-licenseReport {
- useVariantSpecificAssetDirs = true
-}
-
detekt {
buildUponDefaultConfig = true
config.setFrom("../detekt.yml")
@@ -139,6 +135,7 @@ dependencies {
implementation 'com.google.firebase:firebase-crashlytics' // versioned with BOM
implementation 'com.google.firebase:firebase-analytics' // versioned with BOM
implementation 'com.google.android.gms:play-services-mlkit-face-detection:16.1.4'
+ implementation 'com.google.android.gms:play-services-oss-licenses:17.0.0'
implementation 'com.google.android.material:material:1.10.0'
ksp "androidx.room:room-compiler:${room_version}"
testImplementation "androidx.arch.core:core-testing:2.1.0"
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
index fec9f82..c90508f 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
@@ -11,6 +11,7 @@ import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import androidx.viewpager2.widget.ViewPager2
+import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import uk.me.jeremygreen.merging.BuildConfig
@@ -79,7 +80,8 @@ internal class MainActivity : AppCompatActivity() {
this.appViewModel.allImages().observe(this) { images ->
this.pagerAdapter.setImages(images)
}
- val licencesTitle = resources.getString(R.string.actionLicences) //
+ val licencesTitle = resources.getString(R.string.actionLicences)
+ OssLicensesMenuActivity.setActivityTitle(licencesTitle)
}
private fun screenView(screenName: String?) {
@@ -116,7 +118,8 @@ internal class MainActivity : AppCompatActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.actionLicences -> {
- // TODO
+ val intent = Intent(this, OssLicensesMenuActivity::class.java)
+ startActivity(intent)
true
}
R.id.actionAbout -> {
diff --git a/build.gradle b/build.gradle
index 625d765..7c9f460 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,7 +9,7 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:8.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
- classpath 'com.jaredsburrows:gradle-license-plugin:0.9.7'
+ classpath 'com.google.android.gms:oss-licenses-plugin:0.10.6'
classpath 'com.google.gms:google-services:4.4.0'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1'
classpath 'io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.3'
From 025cbf6bf0750cb74deb74685813ff1bb6fe5e67 Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Wed, 29 May 2024 11:24:38 +0100
Subject: [PATCH 23/37] Replace
com.google.android.gms:play-services-oss-licenses with
https://github.com/mikepenz/AboutLibraries.
---
TODO | 10 +--
app/build.gradle | 4 +-
app/src/main/AndroidManifest.xml | 5 ++
.../merging/licences/LicencesActivity.kt | 72 +++++++++++++++++++
.../jeremygreen/merging/main/MainActivity.kt | 6 +-
build.gradle | 2 +-
dependencies.gradle | 1 +
7 files changed, 85 insertions(+), 15 deletions(-)
create mode 100644 app/src/main/java/uk/me/jeremygreen/merging/licences/LicencesActivity.kt
diff --git a/TODO b/TODO
index f25e582..51bdc82 100644
--- a/TODO
+++ b/TODO
@@ -2,15 +2,9 @@
- update to material 3 design
x onboarding
x about
- - licences
- - https://github.com/google/play-services-plugins/issues/248
- - https://stackoverflow.com/questions/76852715/how-to-use-open-source-notices-library-with-jetpack-compose
- - https://github.com/jaredsburrows/gradle-license-plugin
- - tasks generate html, but aren't run automatically when build
- - ./gradlew tasks breaks with AndroidSourceSet with name 'developDebugAndroidTest' not found.
- => a pain to add dependencies
- -
+ X licences
- main
+ - android:theme="@style/AppTheme.NoActionBar" required in manifest still?
* NDK crash reporting
diff --git a/app/build.gradle b/app/build.gradle
index bb9e9c0..758b1b4 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,6 +1,6 @@
plugins {
id 'com.android.application'
- id 'com.google.android.gms.oss-licenses-plugin'
+ id 'com.mikepenz.aboutlibraries.plugin'
id 'kotlin-android'
id 'com.google.devtools.ksp'
id 'com.google.gms.google-services'
@@ -135,7 +135,7 @@ dependencies {
implementation 'com.google.firebase:firebase-crashlytics' // versioned with BOM
implementation 'com.google.firebase:firebase-analytics' // versioned with BOM
implementation 'com.google.android.gms:play-services-mlkit-face-detection:16.1.4'
- implementation 'com.google.android.gms:play-services-oss-licenses:17.0.0'
+ implementation "com.mikepenz:aboutlibraries-compose-m3:${aboutLibsVersion}"
implementation 'com.google.android.material:material:1.10.0'
ksp "androidx.room:room-compiler:${room_version}"
testImplementation "androidx.arch.core:core-testing:2.1.0"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 606c4ef..8d37c95 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -36,6 +36,11 @@
android:theme="@style/AppTheme.NoActionBar"
android:exported="false">
+
+
+ Column(
+ modifier = Modifier.padding(innerPadding),
+ ) {
+ LibrariesContainer(
+ Modifier.fillMaxSize()
+ )
+ }
+ }
+ }
+
+ @Composable
+ private fun BackButton() {
+ IconButton(onClick = { finish() }) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Filled.ArrowBack,
+ contentDescription = "Back",
+ tint = Color.White
+ )
+ }
+ }
+
+}
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
index c90508f..22a472f 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
@@ -11,13 +11,13 @@ import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import androidx.viewpager2.widget.ViewPager2
-import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import uk.me.jeremygreen.merging.BuildConfig
import uk.me.jeremygreen.merging.R
import uk.me.jeremygreen.merging.about.AboutActivity
import uk.me.jeremygreen.merging.databinding.MainBinding
+import uk.me.jeremygreen.merging.licences.LicencesActivity
import uk.me.jeremygreen.merging.model.AppViewModel
import java.io.File
import java.util.*
@@ -80,8 +80,6 @@ internal class MainActivity : AppCompatActivity() {
this.appViewModel.allImages().observe(this) { images ->
this.pagerAdapter.setImages(images)
}
- val licencesTitle = resources.getString(R.string.actionLicences)
- OssLicensesMenuActivity.setActivityTitle(licencesTitle)
}
private fun screenView(screenName: String?) {
@@ -118,7 +116,7 @@ internal class MainActivity : AppCompatActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.actionLicences -> {
- val intent = Intent(this, OssLicensesMenuActivity::class.java)
+ val intent = Intent(this, LicencesActivity::class.java)
startActivity(intent)
true
}
diff --git a/build.gradle b/build.gradle
index 7c9f460..8d76952 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,7 +9,7 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:8.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
- classpath 'com.google.android.gms:oss-licenses-plugin:0.10.6'
+ classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$aboutLibsVersion"
classpath 'com.google.gms:google-services:4.4.0'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1'
classpath 'io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.3'
diff --git a/dependencies.gradle b/dependencies.gradle
index eccbfe4..0d1553c 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -2,4 +2,5 @@ ext {
kotlin_version = '1.9.21'
// https://developer.android.com/jetpack/androidx/releases/compose-kotlin#pre-release_kotlin_compatibility
kotlinCompilerExtensionVersion = '1.5.7'
+ aboutLibsVersion = '11.2.0'
}
From f49854021c01e94cd3a6ed7afb8c0d6d084f9f57 Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Wed, 29 May 2024 12:51:12 +0100
Subject: [PATCH 24/37] TODO
---
TODO | 2 ++
1 file changed, 2 insertions(+)
diff --git a/TODO b/TODO
index 51bdc82..975a3ad 100644
--- a/TODO
+++ b/TODO
@@ -89,6 +89,8 @@
* Don't show splashscreen if start up is fast enough
- but if do show it, show it for at least 1.5s.
+* Use ActivityResultContract not deprecated startActivityForResult/onActivityResult.
+
* Enable crashlytics and analytics in SplashActivity if previously enabled via OnboardingActivity.
- currently, enable in MainActivity, which avoids need to persist state, but means
early crashes not visible.
From 444a03a2ef2019c4e9e0e3c2a0878e06fea54150 Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Wed, 29 May 2024 12:56:56 +0100
Subject: [PATCH 25/37] Remove unused imports from LicencesActivity.
---
.../java/uk/me/jeremygreen/merging/licences/LicencesActivity.kt | 2 --
1 file changed, 2 deletions(-)
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/licences/LicencesActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/licences/LicencesActivity.kt
index 465d749..44ee1d7 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/licences/LicencesActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/licences/LicencesActivity.kt
@@ -20,9 +20,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
-import uk.me.jeremygreen.merging.BuildConfig
import uk.me.jeremygreen.merging.R
internal class LicencesActivity: AppCompatActivity() {
From ea58ab37ac5813c5405d65dac4ece11443f01bdb Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Thu, 30 May 2024 15:09:54 +0100
Subject: [PATCH 26/37] Start to convert MainActivity to Jetpack Compose.
---
TODO | 13 +-
app/build.gradle | 1 -
.../jeremygreen/merging/main/MainActivity.kt | 207 +++++++++++-------
.../merging/main/PagerAdapterImpl.kt | 204 ++++++++---------
.../merging/main/ScreenFragmentFactory.kt | 17 --
.../merging/main/screen/AddImage.kt | 10 +
.../merging/main/screen/AddImageFragment.kt | 28 ---
.../merging/main/screen/ImageFragment.kt | 131 -----------
.../merging/main/screen/InputImage.kt | 120 ++++++++++
.../merging/main/screen/MergedImage.kt | 14 ++
.../main/screen/MergedImageFragment.kt | 28 ---
app/src/main/res/layout/add_image_screen.xml | 8 -
app/src/main/res/layout/image_screen.xml | 18 --
app/src/main/res/layout/main.xml | 45 ----
.../main/res/layout/merged_image_screen.xml | 20 --
app/src/main/res/menu/menu_main.xml | 14 --
app/src/main/res/values/dimens.xml | 1 -
17 files changed, 389 insertions(+), 490 deletions(-)
delete mode 100644 app/src/main/java/uk/me/jeremygreen/merging/main/ScreenFragmentFactory.kt
create mode 100644 app/src/main/java/uk/me/jeremygreen/merging/main/screen/AddImage.kt
delete mode 100644 app/src/main/java/uk/me/jeremygreen/merging/main/screen/AddImageFragment.kt
delete mode 100644 app/src/main/java/uk/me/jeremygreen/merging/main/screen/ImageFragment.kt
create mode 100644 app/src/main/java/uk/me/jeremygreen/merging/main/screen/InputImage.kt
create mode 100644 app/src/main/java/uk/me/jeremygreen/merging/main/screen/MergedImage.kt
delete mode 100644 app/src/main/java/uk/me/jeremygreen/merging/main/screen/MergedImageFragment.kt
delete mode 100644 app/src/main/res/layout/add_image_screen.xml
delete mode 100644 app/src/main/res/layout/image_screen.xml
delete mode 100644 app/src/main/res/layout/main.xml
delete mode 100644 app/src/main/res/layout/merged_image_screen.xml
delete mode 100644 app/src/main/res/menu/menu_main.xml
diff --git a/TODO b/TODO
index 975a3ad..d56e731 100644
--- a/TODO
+++ b/TODO
@@ -4,7 +4,18 @@
x about
X licences
- main
- - android:theme="@style/AppTheme.NoActionBar" required in manifest still?
+ X replace view binding with composables
+ x top bar
+ X menu
+ X make action button start camera app
+ - save camera image
+ - show images and faces
+ - splash
+ - Is AppTheme etc. required still?
+ - and AppCompatActivity as parent class
+ - remove old material library.
+ - remove viewBinding = true
+ - replace closeMenu function with Item Composable that also deduplicates DropdownMenuItem boilerplate
* NDK crash reporting
diff --git a/app/build.gradle b/app/build.gradle
index 758b1b4..0048936 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -136,7 +136,6 @@ dependencies {
implementation 'com.google.firebase:firebase-analytics' // versioned with BOM
implementation 'com.google.android.gms:play-services-mlkit-face-detection:16.1.4'
implementation "com.mikepenz:aboutlibraries-compose-m3:${aboutLibsVersion}"
- implementation 'com.google.android.material:material:1.10.0'
ksp "androidx.room:room-compiler:${room_version}"
testImplementation "androidx.arch.core:core-testing:2.1.0"
testImplementation "androidx.room:room-testing:${room_version}"
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
index 22a472f..787e37f 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
@@ -6,18 +6,44 @@ import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
-import android.view.Menu
-import android.view.MenuItem
+import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.outlined.MoreVert
+import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FloatingActionButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+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.Modifier
+import androidx.compose.ui.res.stringResource
import androidx.core.content.FileProvider
-import androidx.viewpager2.widget.ViewPager2
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import uk.me.jeremygreen.merging.BuildConfig
import uk.me.jeremygreen.merging.R
import uk.me.jeremygreen.merging.about.AboutActivity
-import uk.me.jeremygreen.merging.databinding.MainBinding
import uk.me.jeremygreen.merging.licences.LicencesActivity
+import uk.me.jeremygreen.merging.main.screen.AddImage
+import uk.me.jeremygreen.merging.main.screen.InputImage
+import uk.me.jeremygreen.merging.main.screen.MergedImage
import uk.me.jeremygreen.merging.model.AppViewModel
import java.io.File
import java.util.*
@@ -27,17 +53,12 @@ internal class MainActivity : AppCompatActivity() {
companion object {
private const val TAG = "MainActivity"
private const val REQUEST_TAKE_PHOTO = 1
- private const val BUNDLE_KEY__FILE = "file"
fun imagesDir(activity: Activity): File {
return File(activity.filesDir, "photos")
}
}
- private var file: File? = null
- private lateinit var binding: MainBinding
- private lateinit var pagerAdapter: PagerAdapterImpl
- private lateinit var pageChangeCallback: ViewPager2.OnPageChangeCallback
private lateinit var firebaseAnalytics: FirebaseAnalytics
private val appViewModel by lazy {
@@ -51,13 +72,6 @@ internal class MainActivity : AppCompatActivity() {
// Activity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- this.binding = MainBinding.inflate(layoutInflater)
- if (savedInstanceState != null) {
- val fileString = savedInstanceState.getString(BUNDLE_KEY__FILE)
- if (fileString != null) {
- this.file = File(fileString)
- }
- }
// Firebase Analytics and Crashlytics are only enabled after have agreed to their
// use, which is done using OnboardingActivity.
this.firebaseAnalytics = FirebaseAnalytics.getInstance(this)
@@ -66,22 +80,109 @@ internal class MainActivity : AppCompatActivity() {
val crashlytics = FirebaseCrashlytics.getInstance()
crashlytics.setCrashlyticsCollectionEnabled(true)
}
- setContentView(this.binding.root)
- setSupportActionBar(this.binding.toolbar)
- this.pagerAdapter = PagerAdapterImpl(this)
- this.binding.pager.adapter = this.pagerAdapter
- this.binding.pager.offscreenPageLimit = 2
- this.pageChangeCallback = object: ViewPager2.OnPageChangeCallback() {
- override fun onPageSelected(position: Int) {
- val screenName: String? = this@MainActivity.pagerAdapter.screenName(binding.pager)
- screenView(screenName)
+ setContent { Main() }
+// this.appViewModel.allImages().observe(this) { images ->
+// this.pagerAdapter.setImages(images)
+// }
+ }
+
+ @OptIn(
+ ExperimentalFoundationApi::class,
+ ExperimentalMaterial3Api::class
+ )
+ @Composable
+ private fun Main() {
+ val context = this
+ val pagerState = rememberPagerState(
+ pageCount = { 2 }
+ )
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ colors = TopAppBarDefaults.topAppBarColors(
+ containerColor = MaterialTheme.colorScheme.primaryContainer,
+ titleContentColor = MaterialTheme.colorScheme.primary,
+ ),
+ title = { Text(text = stringResource(R.string.appName)) },
+ actions = {
+ OverflowMenu { closeMenu ->
+ DropdownMenuItem(
+ text = { Text(stringResource(R.string.actionAbout)) },
+ onClick = {
+ closeMenu()
+ val intent = Intent(context, AboutActivity::class.java)
+ startActivity(intent)
+ }
+ )
+ DropdownMenuItem(
+ text = { Text(stringResource(R.string.actionLicences)) },
+ onClick = {
+ closeMenu()
+ val intent = Intent(context, LicencesActivity::class.java)
+ startActivity(intent)
+ }
+ )
+ }
+ }
+ )
+ },
+ floatingActionButton = {
+ FloatingActionButton(
+ onClick = ::handleTakePhoto,
+ ) {
+ Icon(
+ Icons.Default.Add,
+ contentDescription = "Add"
+ )
+ }
+ }
+
+ ) { innerPadding ->
+ Column(
+ modifier = Modifier.padding(innerPadding),
+ ) {
+ HorizontalPager(
+ state = pagerState,
+ beyondBoundsPageCount = 2
+ ) { page ->
+ Pages(page, pagerState.pageCount)
+ }
+ }
+ }
+ }
+
+ @Composable
+ private fun Pages(page: Int, pages: Int) {
+ when (page) {
+ 0 -> AddImage() // first
+ pages - 1 -> MergedImage() // last
+ else -> {
+ InputImage(page - 1)
}
}
- this.appViewModel.allImages().observe(this) { images ->
- this.pagerAdapter.setImages(images)
+ }
+
+ @Composable
+ fun OverflowMenu(content: @Composable (closeMenu: () -> Unit) -> Unit) {
+ var showMenu by remember { mutableStateOf(false) }
+ val closeMenu = { showMenu = false}
+ IconButton(onClick = {
+ showMenu = !showMenu
+ }) {
+ Icon(
+ imageVector = Icons.Outlined.MoreVert,
+ contentDescription = null
+ )
+ }
+ DropdownMenu(
+ expanded = showMenu,
+ onDismissRequest = closeMenu
+ ) {
+ content(closeMenu)
}
}
+ // TODO analytics
private fun screenView(screenName: String?) {
if (screenName == null) {
return
@@ -92,51 +193,7 @@ internal class MainActivity : AppCompatActivity() {
this.firebaseAnalytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW, params)
}
- // Activity
- override fun onResume() {
- super.onResume()
- binding.pager.registerOnPageChangeCallback(this.pageChangeCallback)
- binding.fab.setOnClickListener { handleTakePhoto() }
- }
-
- // Activity
- override fun onPause() {
- binding.fab.setOnClickListener(null)
- binding.pager.unregisterOnPageChangeCallback(this.pageChangeCallback)
- super.onPause()
- }
-
- // Activity
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- this.menuInflater.inflate(R.menu.menu_main, menu)
- return true
- }
-
- // Activity
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- return when (item.itemId) {
- R.id.actionLicences -> {
- val intent = Intent(this, LicencesActivity::class.java)
- startActivity(intent)
- true
- }
- R.id.actionAbout -> {
- val intent = Intent(this, AboutActivity::class.java)
- startActivity(intent)
- true
- }
- else -> super.onOptionsItemSelected(item)
- }
- }
-
- // Activity
- override fun onSaveInstanceState(outState: Bundle) {
- super.onSaveInstanceState(outState)
- val file = this.file
- if (file != null) {
- outState.putString(BUNDLE_KEY__FILE, file.path)
- }
- }
+ // TODO binding.fab.setOnClickListener { handleTakePhoto() }
private fun handleTakePhoto() {
val intent = createTakePhotoIntent()
@@ -152,7 +209,6 @@ internal class MainActivity : AppCompatActivity() {
BuildConfig.APPLICATION_ID + ".fileprovider",
file
)
- this.file = file
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri)
return intent
}
@@ -160,9 +216,8 @@ internal class MainActivity : AppCompatActivity() {
// Activity
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
- val file = file
- if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_TAKE_PHOTO && file != null) {
- this.appViewModel.addImage(file.path)
+ if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_TAKE_PHOTO) {
+ // TODO this.appViewModel.addImage(file.path)
}
}
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/PagerAdapterImpl.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/PagerAdapterImpl.kt
index 21e0969..6264d27 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/main/PagerAdapterImpl.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/main/PagerAdapterImpl.kt
@@ -1,105 +1,105 @@
package uk.me.jeremygreen.merging.main
-import android.util.Log
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.FragmentActivity
-import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.RecyclerView
-import androidx.viewpager2.adapter.FragmentStateAdapter
-import androidx.viewpager2.widget.ViewPager2
-import uk.me.jeremygreen.merging.model.Image
-import uk.me.jeremygreen.merging.main.screen.AddImageFragment
-import uk.me.jeremygreen.merging.main.screen.ImageFragment
-import uk.me.jeremygreen.merging.main.screen.MergedImageFragment
+//import android.util.Log
+//import androidx.fragment.app.Fragment
+//import androidx.fragment.app.FragmentActivity
+//import androidx.recyclerview.widget.DiffUtil
+//import androidx.recyclerview.widget.RecyclerView
+//import androidx.viewpager2.adapter.FragmentStateAdapter
+//import androidx.viewpager2.widget.ViewPager2
+//import uk.me.jeremygreen.merging.model.Image
+//import uk.me.jeremygreen.merging.main.screen.AddImage
+//import uk.me.jeremygreen.merging.main.screen.InputImage
+//import uk.me.jeremygreen.merging.main.screen.MergedImage
-internal class PagerAdapterImpl(
- fragmentActivity: FragmentActivity
-) : FragmentStateAdapter(fragmentActivity) {
-
- companion object {
- private const val TAG = "PagerAdapterImpl"
- }
-
- private var ids: Set = setOf()
- private var factories: List> = listOf()
-
- fun setImages(images: List) {
- Log.v(TAG, "setImages(${images.size})")
- val newFactories: MutableList> = mutableListOf()
- if (images.isEmpty()) {
- newFactories.add(AddImageFragment)
- }
- images.forEach {image ->
- newFactories.add(ImageFragment.createFactory(image))
- }
- if (images.size >= 2) {
- newFactories.add(MergedImageFragment)
- }
- val newIds = newFactories.map{ factory -> factory.id }.toSet()
- if (newIds.size != newFactories.size) {
- throw AssertionError("ids: ${this.ids} factories: ${this.factories}")
- }
- if (RecyclerView.NO_ID in newIds) {
- throw AssertionError("ids: ${this.ids} factories: ${this.factories}")
- }
- val changes = changes(this.factories, newFactories)
- this.factories = newFactories
- this.ids = newIds
- changes.dispatchUpdatesTo(this)
- }
-
- private fun changes(
- old: List>,
- new: List>
- ): DiffUtil.DiffResult {
- return DiffUtil.calculateDiff(object: DiffUtil.Callback() {
- override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
- return old[oldItemPosition].id == new[newItemPosition].id
- }
- override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
- return old[oldItemPosition].id == new[newItemPosition].id
- }
- override fun getNewListSize(): Int {
- return new.size
- }
- override fun getOldListSize(): Int {
- return old.size
- }
- })
- }
-
- /**
- * Firebase analytics "screen name".
- */
- fun screenName(viewPager2: ViewPager2): String? {
- if (this.factories.isEmpty()) {
- return null
- }
- val position = viewPager2.currentItem
- return this.factories[position].screenName()
- }
-
- // FragmentStateAdapter
- override fun getItemId(position: Int): Long {
- return this.factories[position].id // ...or RecyclerView.NO_ID?
- }
-
- // FragmentStateAdapter
- override fun containsItem(id: Long): Boolean {
- return id in this.ids
- }
-
- // FragmentStateAdapter
- override fun getItemCount(): Int {
- val count = this.ids.size
- Log.v(TAG, "getItemCount() = ${count}")
- return count
- }
-
- // FragmentStateAdapter
- override fun createFragment(position: Int): Fragment {
- Log.v(TAG, "createFragment(${position})")
- return this.factories[position].createInstance()
- }
-
-} // FragmentStateAdapter
+//internal class PagerAdapterImpl(
+// fragmentActivity: FragmentActivity
+//) : FragmentStateAdapter(fragmentActivity) {
+//
+// companion object {
+// private const val TAG = "PagerAdapterImpl"
+// }
+//
+// private var ids: Set = setOf()
+// private var factories: List> = listOf()
+//
+// fun setImages(images: List) {
+// Log.v(TAG, "setImages(${images.size})")
+// val newFactories: MutableList> = mutableListOf()
+// if (images.isEmpty()) {
+// newFactories.add(AddImage)
+// }
+// images.forEach {image ->
+// newFactories.add(InputImage.createFactory(image))
+// }
+// if (images.size >= 2) {
+// newFactories.add(MergedImage)
+// }
+// val newIds = newFactories.map{ factory -> factory.id }.toSet()
+// if (newIds.size != newFactories.size) {
+// throw AssertionError("ids: ${this.ids} factories: ${this.factories}")
+// }
+// if (RecyclerView.NO_ID in newIds) {
+// throw AssertionError("ids: ${this.ids} factories: ${this.factories}")
+// }
+// val changes = changes(this.factories, newFactories)
+// this.factories = newFactories
+// this.ids = newIds
+// changes.dispatchUpdatesTo(this)
+// }
+//
+// private fun changes(
+// old: List>,
+// new: List>
+// ): DiffUtil.DiffResult {
+// return DiffUtil.calculateDiff(object: DiffUtil.Callback() {
+// override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
+// return old[oldItemPosition].id == new[newItemPosition].id
+// }
+// override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
+// return old[oldItemPosition].id == new[newItemPosition].id
+// }
+// override fun getNewListSize(): Int {
+// return new.size
+// }
+// override fun getOldListSize(): Int {
+// return old.size
+// }
+// })
+// }
+//
+// /**
+// * Firebase analytics "screen name".
+// */
+// fun screenName(viewPager2: ViewPager2): String? {
+// if (this.factories.isEmpty()) {
+// return null
+// }
+// val position = viewPager2.currentItem
+// return this.factories[position].screenName()
+// }
+//
+// // FragmentStateAdapter
+// override fun getItemId(position: Int): Long {
+// return this.factories[position].id // ...or RecyclerView.NO_ID?
+// }
+//
+// // FragmentStateAdapter
+// override fun containsItem(id: Long): Boolean {
+// return id in this.ids
+// }
+//
+// // FragmentStateAdapter
+// override fun getItemCount(): Int {
+// val count = this.ids.size
+// Log.v(TAG, "getItemCount() = ${count}")
+// return count
+// }
+//
+// // FragmentStateAdapter
+// override fun createFragment(position: Int): Fragment {
+// Log.v(TAG, "createFragment(${position})")
+// return this.factories[position].createInstance()
+// }
+//
+//} // FragmentStateAdapter
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/ScreenFragmentFactory.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/ScreenFragmentFactory.kt
deleted file mode 100644
index 166fb9a..0000000
--- a/app/src/main/java/uk/me/jeremygreen/merging/main/ScreenFragmentFactory.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package uk.me.jeremygreen.merging.main
-
-internal interface ScreenFragmentFactory {
-
- /**
- * The ViewPager2 (RecyclerView) id. Images use non-negative Ints, and ViewPager2 uses -1.
- */
- val id: Long
-
- fun createInstance(): T
-
- /**
- * Firebase Analytics "screen name".
- */
- fun screenName(): String
-
-}
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/screen/AddImage.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/screen/AddImage.kt
new file mode 100644
index 0000000..88ac6fd
--- /dev/null
+++ b/app/src/main/java/uk/me/jeremygreen/merging/main/screen/AddImage.kt
@@ -0,0 +1,10 @@
+package uk.me.jeremygreen.merging.main.screen
+
+import androidx.compose.runtime.Composable
+
+// id: Long = -2
+// screenName(): String = "AddImage"
+@Composable
+fun AddImage() {
+ // Empty - the UI is provided by MainActivity action button.
+}
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/screen/AddImageFragment.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/screen/AddImageFragment.kt
deleted file mode 100644
index d0147b2..0000000
--- a/app/src/main/java/uk/me/jeremygreen/merging/main/screen/AddImageFragment.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package uk.me.jeremygreen.merging.main.screen
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import uk.me.jeremygreen.merging.R
-import uk.me.jeremygreen.merging.main.ScreenFragment
-import uk.me.jeremygreen.merging.main.ScreenFragmentFactory
-
-internal class AddImageFragment : ScreenFragment() {
-
- companion object: ScreenFragmentFactory {
- override val id: Long = -2
- override fun createInstance(): AddImageFragment {
- return AddImageFragment()
- }
- override fun screenName(): String = "AddImage"
- }
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?): View? {
- return inflater.inflate(R.layout.add_image_screen, container, false)
- }
-
-}
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/screen/ImageFragment.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/screen/ImageFragment.kt
deleted file mode 100644
index cb3a582..0000000
--- a/app/src/main/java/uk/me/jeremygreen/merging/main/screen/ImageFragment.kt
+++ /dev/null
@@ -1,131 +0,0 @@
-package uk.me.jeremygreen.merging.main.screen
-
-import android.app.AlertDialog
-import android.content.DialogInterface
-import android.os.Bundle
-import android.util.Log
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import com.google.firebase.crashlytics.FirebaseCrashlytics
-import com.google.mlkit.vision.face.FaceDetectorOptions
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import uk.me.jeremygreen.merging.R
-import uk.me.jeremygreen.merging.databinding.ImageScreenBinding
-import uk.me.jeremygreen.merging.main.ScreenFragment
-import uk.me.jeremygreen.merging.main.ScreenFragmentFactory
-import uk.me.jeremygreen.merging.model.Image
-import uk.me.jeremygreen.merging.model.ProcessingStage
-
-internal class ImageFragment : ScreenFragment() {
-
- companion object {
-
- private const val TAG = "ImageFragment"
- private const val BUNDLE_KEY__IMAGE_ID = "imageId"
-
- private const val BITMAP_WIDTH = 360
- private const val BITMAP_HEIGHT = 480
-
- fun createFactory(image: Image): ScreenFragmentFactory {
- require(image.id >= 0) { "might collide with non-image id: ${image.id}" }
- return object:
- ScreenFragmentFactory {
- override val id: Long = image.id
- override fun createInstance(): ImageFragment {
- return ImageFragment().apply {
- arguments = Bundle().apply {
- putLong(BUNDLE_KEY__IMAGE_ID, image.id)
- }
- }
- }
- override fun screenName(): String = "Image"
- }
- }
-
- }
-
- private var _binding: ImageScreenBinding? = null
- private val binding get() = _binding!!
-
- private val faceDetectorOptions by lazy {
- FaceDetectorOptions.Builder()
- .setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_ACCURATE)
- .setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL)
- .setContourMode(FaceDetectorOptions.CONTOUR_MODE_ALL)
- .setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_NONE)
- .build()
- }
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- _binding = ImageScreenBinding.inflate(inflater, container, false)
- return binding.root
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- val bundle = arguments
- val imageId: Long = bundle!!.getLong(BUNDLE_KEY__IMAGE_ID)
- val facesView = binding.faces
- appViewModel.faces(imageId).observe(viewLifecycleOwner) { faces ->
- facesView.faces = faces
- }
- launch(Dispatchers.IO) {
- val image = appViewModel.findById(imageId)
- launch(Dispatchers.Main) {
- facesView.setImage(image, ::handleLongClick)
- }
- val processingStage = appViewModel.getProcessingStage(imageId)
- if (processingStage == ProcessingStage.unprocessed) {
- processFaces(image, imageId)
- }
- }
- }
-
- override fun onDestroyView() {
- _binding = null
- super.onDestroyView()
- }
-
- private fun processFaces(image: Image, imageId: Long) {
- // https//firebase.google.com/docs/ml-kit/android/detect-faces suggests size to use.
- image.processBitmap(BITMAP_WIDTH, BITMAP_HEIGHT) { closeableReference ->
- Log.i(TAG, "decoded bitmap for image id: ${imageId}")
- // The IO thread has done it's work reading the Bitmap. Don't want to block this thread any more,
- // so clone the reference and hand it to Dispatcher.Default coroutine to do the CPU-intensive
- // face-detection work.
- val clonedReference = closeableReference.clone()
- launch(Dispatchers.Default) {
- Log.i(TAG, "detecting faces for image id: ${imageId}")
- image.findFaces(clonedReference, faceDetectorOptions, ::handleFaceDetectionError) { faces ->
- Log.i(TAG, "detected ${faces.size} faces for image id: ${imageId}")
- val processedImage = image.copy(processingStage = ProcessingStage.facesDetected)
- appViewModel.addAll(processedImage, faces)
- }
- }
- }
- }
-
- private fun handleFaceDetectionError(e: Exception) {
- Log.e(TAG, "face detection failed", e)
- FirebaseCrashlytics.getInstance().recordException(e)
- }
-
- private fun handleLongClick(image: Image) {
- AlertDialog.Builder(requireContext()).apply {
- setMessage(R.string.confirmDeleteImage)
- setPositiveButton(R.string.ok) { _: DialogInterface, _: Int ->
- binding.faces.setOnLongClickListener { false }
- appViewModel.delete(image)
- }
- setNegativeButton(R.string.cancel) { _: DialogInterface, _: Int -> }
- show()
- }
- }
-
-}
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/screen/InputImage.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/screen/InputImage.kt
new file mode 100644
index 0000000..c739045
--- /dev/null
+++ b/app/src/main/java/uk/me/jeremygreen/merging/main/screen/InputImage.kt
@@ -0,0 +1,120 @@
+package uk.me.jeremygreen.merging.main.screen
+
+import android.app.AlertDialog
+import android.content.DialogInterface
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import com.google.firebase.crashlytics.FirebaseCrashlytics
+import com.google.mlkit.vision.face.FaceDetectorOptions
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import uk.me.jeremygreen.merging.R
+import uk.me.jeremygreen.merging.model.Image
+import uk.me.jeremygreen.merging.model.ProcessingStage
+
+private const val TAG = "ImageFragment"
+private const val BUNDLE_KEY__IMAGE_ID = "imageId"
+
+private const val BITMAP_WIDTH = 360
+private const val BITMAP_HEIGHT = 480
+
+private val faceDetectorOptions =
+ FaceDetectorOptions.Builder()
+ .setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_ACCURATE)
+ .setLandmarkMode(FaceDetectorOptions.LANDMARK_MODE_ALL)
+ .setContourMode(FaceDetectorOptions.CONTOUR_MODE_ALL)
+ .setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_NONE)
+ .build()
+
+@Composable
+fun InputImage(page: Int) {
+ Text("image: $page")
+
+//
+
+
+}
+
+// fun createFactory(image: Image): ScreenFragmentFactory {
+// require(image.id >= 0) { "might collide with non-image id: ${image.id}" }
+// return object:
+// ScreenFragmentFactory {
+// override val id: Long = image.id
+// override fun createInstance(): InputImage {
+// return InputImage().apply {
+// arguments = Bundle().apply {
+// putLong(BUNDLE_KEY__IMAGE_ID, image.id)
+// }
+// }
+// }
+// override fun screenName(): String = "Image"
+// }
+// }
+//
+// }
+
+// private fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+// val bundle = arguments
+// val imageId: Long = bundle!!.getLong(BUNDLE_KEY__IMAGE_ID)
+// val facesView = binding.faces
+// appViewModel.faces(imageId).observe(viewLifecycleOwner) { faces ->
+// facesView.faces = faces
+// }
+// launch(Dispatchers.IO) {
+// val image = appViewModel.findById(imageId)
+// launch(Dispatchers.Main) {
+// facesView.setImage(image, ::handleLongClick)
+// }
+// val processingStage = appViewModel.getProcessingStage(imageId)
+// if (processingStage == ProcessingStage.unprocessed) {
+// processFaces(image, imageId)
+// }
+// }
+// }
+//
+// private fun processFaces(image: Image, imageId: Long) {
+// // https//firebase.google.com/docs/ml-kit/android/detect-faces suggests size to use.
+// image.processBitmap(BITMAP_WIDTH, BITMAP_HEIGHT) { closeableReference ->
+// Log.i(TAG, "decoded bitmap for image id: ${imageId}")
+// // The IO thread has done it's work reading the Bitmap. Don't want to block this thread any more,
+// // so clone the reference and hand it to Dispatcher.Default coroutine to do the CPU-intensive
+// // face-detection work.
+// val clonedReference = closeableReference.clone()
+// launch(Dispatchers.Default) {
+// Log.i(TAG, "detecting faces for image id: ${imageId}")
+// image.findFaces(clonedReference, faceDetectorOptions, ::handleFaceDetectionError) { faces ->
+// Log.i(TAG, "detected ${faces.size} faces for image id: ${imageId}")
+// val processedImage = image.copy(processingStage = ProcessingStage.facesDetected)
+// appViewModel.addAll(processedImage, faces)
+// }
+// }
+// }
+// }
+//
+// private fun handleFaceDetectionError(e: Exception) {
+// Log.e(TAG, "face detection failed", e)
+// FirebaseCrashlytics.getInstance().recordException(e)
+// }
+//
+// private fun handleLongClick(image: Image) {
+// AlertDialog.Builder(requireContext()).apply {
+// setMessage(R.string.confirmDeleteImage)
+// setPositiveButton(R.string.ok) { _: DialogInterface, _: Int ->
+// binding.faces.setOnLongClickListener { false }
+// appViewModel.delete(image)
+// }
+// setNegativeButton(R.string.cancel) { _: DialogInterface, _: Int -> }
+// show()
+// }
+// }
+
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/screen/MergedImage.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/screen/MergedImage.kt
new file mode 100644
index 0000000..5cc5ca3
--- /dev/null
+++ b/app/src/main/java/uk/me/jeremygreen/merging/main/screen/MergedImage.kt
@@ -0,0 +1,14 @@
+package uk.me.jeremygreen.merging.main.screen
+
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import uk.me.jeremygreen.merging.R
+
+// id: Long = -3
+// screenName(): String = "MergedImage"
+
+@Composable
+fun MergedImage() {
+ Text(text = stringResource(R.string.merged_image))
+}
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/screen/MergedImageFragment.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/screen/MergedImageFragment.kt
deleted file mode 100644
index 6827d2f..0000000
--- a/app/src/main/java/uk/me/jeremygreen/merging/main/screen/MergedImageFragment.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package uk.me.jeremygreen.merging.main.screen
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import uk.me.jeremygreen.merging.R
-import uk.me.jeremygreen.merging.main.ScreenFragment
-import uk.me.jeremygreen.merging.main.ScreenFragmentFactory
-
-internal class MergedImageFragment : ScreenFragment() {
-
- companion object: ScreenFragmentFactory {
- override val id: Long = -3
- override fun createInstance(): MergedImageFragment {
- return MergedImageFragment()
- }
- override fun screenName(): String = "MergedImage"
- }
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?): View? {
- return inflater.inflate(R.layout.merged_image_screen, container, false)
- }
-
-}
diff --git a/app/src/main/res/layout/add_image_screen.xml b/app/src/main/res/layout/add_image_screen.xml
deleted file mode 100644
index eeb13dd..0000000
--- a/app/src/main/res/layout/add_image_screen.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/image_screen.xml b/app/src/main/res/layout/image_screen.xml
deleted file mode 100644
index 6afddb1..0000000
--- a/app/src/main/res/layout/image_screen.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/main.xml b/app/src/main/res/layout/main.xml
deleted file mode 100644
index fb6e7c3..0000000
--- a/app/src/main/res/layout/main.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/merged_image_screen.xml b/app/src/main/res/layout/merged_image_screen.xml
deleted file mode 100644
index e6834c8..0000000
--- a/app/src/main/res/layout/merged_image_screen.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml
deleted file mode 100644
index 858a75e..0000000
--- a/app/src/main/res/menu/menu_main.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index ecdb70c..8542005 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -1,3 +1,2 @@
- 16dp
From ebb6bda5873e69abedb5153da00fe52c59b9ce1c Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Fri, 31 May 2024 17:40:35 +0100
Subject: [PATCH 27/37] Show HorizontalPager pages based on images LiveData. No
images or faces yet.
---
.idea/detekt.xml | 7 +++
TODO | 6 +++
app/build.gradle | 1 +
.../jeremygreen/merging/main/MainActivity.kt | 46 +++++++++++++++----
.../merging/main/screen/InputImage.kt | 12 +++--
5 files changed, 57 insertions(+), 15 deletions(-)
create mode 100644 .idea/detekt.xml
diff --git a/.idea/detekt.xml b/.idea/detekt.xml
new file mode 100644
index 0000000..ee7289c
--- /dev/null
+++ b/.idea/detekt.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/TODO b/TODO
index d56e731..b151071 100644
--- a/TODO
+++ b/TODO
@@ -8,9 +8,15 @@
x top bar
X menu
X make action button start camera app
+ - only show FAB if on add image page
- save camera image
- show images and faces
- splash
+ - fresco is quite an old library and doesn't support jetpack compose directly
+ - coil?
+ - https://coil-kt.github.io/coil/migrating/#non-view-targets
+ - glide?
+ - picasso?
- Is AppTheme etc. required still?
- and AppCompatActivity as parent class
- remove old material library.
diff --git a/app/build.gradle b/app/build.gradle
index 0048936..ea5c914 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -112,6 +112,7 @@ dependencies {
// JetPack Compose
implementation composeBom
implementation 'androidx.compose.material3:material3'
+ implementation("androidx.compose.runtime:runtime-livedata:1.6.7")
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.activity:activity-compose'
debugImplementation 'androidx.compose.ui:ui-tooling'
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
index 787e37f..3827978 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
@@ -10,6 +10,7 @@ import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
@@ -29,6 +30,7 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
@@ -45,6 +47,7 @@ import uk.me.jeremygreen.merging.main.screen.AddImage
import uk.me.jeremygreen.merging.main.screen.InputImage
import uk.me.jeremygreen.merging.main.screen.MergedImage
import uk.me.jeremygreen.merging.model.AppViewModel
+import uk.me.jeremygreen.merging.model.Image
import java.io.File
import java.util.*
@@ -93,8 +96,12 @@ internal class MainActivity : AppCompatActivity() {
@Composable
private fun Main() {
val context = this
+ val images = this.appViewModel.allImages().observeAsState().value
+ //Log.i(TAG, "images: ${images?.size}")
val pagerState = rememberPagerState(
- pageCount = { 2 }
+ pageCount = {
+ pagerPageCount(images)
+ }
)
Scaffold(
topBar = {
@@ -143,23 +150,42 @@ internal class MainActivity : AppCompatActivity() {
) {
HorizontalPager(
state = pagerState,
- beyondBoundsPageCount = 2
+ beyondBoundsPageCount = 2,
+ modifier = Modifier.fillMaxHeight()
) { page ->
- Pages(page, pagerState.pageCount)
+ Pages(images, page)
}
}
}
}
+ private fun isMergedImageShown(images: List?) = !images.isNullOrEmpty() && images.size > 1
+
+ private fun pagerPageCount(images: List?): Int {
+ var pageCount = 1 // add image page
+ if (!images.isNullOrEmpty()) {
+ pageCount += images.size
+ }
+ if (isMergedImageShown(images)) {
+ pageCount += 1
+ }
+ //Log.i(TAG, "pagerPageCount: ${pageCount}")
+ return pageCount
+ }
+
@Composable
- private fun Pages(page: Int, pages: Int) {
- when (page) {
- 0 -> AddImage() // first
- pages - 1 -> MergedImage() // last
- else -> {
- InputImage(page - 1)
- }
+ private fun Pages(images: List?, page: Int) {
+ if (page == 0) {
+ AddImage()
+ return
+ }
+ val isLastPage = page == pagerPageCount(images) - 1
+ if (isLastPage && isMergedImageShown(images)) {
+ MergedImage()
+ return
}
+ val image = images?.get(page - 1)
+ InputImage(image)
}
@Composable
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/screen/InputImage.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/screen/InputImage.kt
index c739045..cb725b9 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/main/screen/InputImage.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/main/screen/InputImage.kt
@@ -5,8 +5,11 @@ import android.content.DialogInterface
import android.os.Bundle
import android.util.Log
import android.view.View
+import androidx.compose.foundation.Image
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.painter.BitmapPainter
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.mlkit.vision.face.FaceDetectorOptions
import kotlinx.coroutines.Dispatchers
@@ -30,8 +33,10 @@ private val faceDetectorOptions =
.build()
@Composable
-fun InputImage(page: Int) {
- Text("image: $page")
+internal fun InputImage(image: Image?) {
+ Text(image?.uri.toString())
+ // TODO show FacesView
+}
//
-
-}
-
// fun createFactory(image: Image): ScreenFragmentFactory {
// require(image.id >= 0) { "might collide with non-image id: ${image.id}" }
// return object:
From 17a08682a0edaaaadb4c5189c0ac3fef42bbae37 Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Fri, 31 May 2024 17:56:14 +0100
Subject: [PATCH 28/37] Refactor MainActivity Floating Action Button code.
---
.../jeremygreen/merging/main/MainActivity.kt | 22 +++++++++----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
index 3827978..1bdf5df 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
@@ -103,6 +103,16 @@ internal class MainActivity : AppCompatActivity() {
pagerPageCount(images)
}
)
+ val floatingActionButton = @Composable {
+ FloatingActionButton(
+ onClick = ::handleTakePhoto,
+ ) {
+ Icon(
+ Icons.Default.Add,
+ contentDescription = "Add"
+ )
+ }
+ }
Scaffold(
topBar = {
TopAppBar(
@@ -133,17 +143,7 @@ internal class MainActivity : AppCompatActivity() {
}
)
},
- floatingActionButton = {
- FloatingActionButton(
- onClick = ::handleTakePhoto,
- ) {
- Icon(
- Icons.Default.Add,
- contentDescription = "Add"
- )
- }
- }
-
+ floatingActionButton = floatingActionButton
) { innerPadding ->
Column(
modifier = Modifier.padding(innerPadding),
From 97a9652f8fddd45e8ac3bae3ed2649325c757bd9 Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Fri, 31 May 2024 18:04:55 +0100
Subject: [PATCH 29/37] Only show AddImage page if there are no images.
---
TODO | 1 -
.../me/jeremygreen/merging/main/MainActivity.kt | 17 ++++++++---------
2 files changed, 8 insertions(+), 10 deletions(-)
diff --git a/TODO b/TODO
index b151071..6026c1c 100644
--- a/TODO
+++ b/TODO
@@ -8,7 +8,6 @@
x top bar
X menu
X make action button start camera app
- - only show FAB if on add image page
- save camera image
- show images and faces
- splash
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
index 1bdf5df..4d549fa 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
@@ -162,20 +162,19 @@ internal class MainActivity : AppCompatActivity() {
private fun isMergedImageShown(images: List?) = !images.isNullOrEmpty() && images.size > 1
private fun pagerPageCount(images: List?): Int {
- var pageCount = 1 // add image page
- if (!images.isNullOrEmpty()) {
- pageCount += images.size
+ if (images.isNullOrEmpty()) {
+ return 1 // add image page
}
- if (isMergedImageShown(images)) {
- pageCount += 1
+ return if (isMergedImageShown(images)) {
+ images.size + 1
+ } else {
+ images.size
}
- //Log.i(TAG, "pagerPageCount: ${pageCount}")
- return pageCount
}
@Composable
private fun Pages(images: List?, page: Int) {
- if (page == 0) {
+ if (images.isNullOrEmpty()) {
AddImage()
return
}
@@ -184,7 +183,7 @@ internal class MainActivity : AppCompatActivity() {
MergedImage()
return
}
- val image = images?.get(page - 1)
+ val image = images?.get(page)
InputImage(image)
}
From 23955367fd6e94bc3501ab81aa53819a2a57515a Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Wed, 5 Jun 2024 10:44:33 +0100
Subject: [PATCH 30/37] Reimplement photo taking.
---
.../jeremygreen/merging/main/MainActivity.kt | 56 +++++++++----------
1 file changed, 26 insertions(+), 30 deletions(-)
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
index 4d549fa..12f8563 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
@@ -4,9 +4,10 @@ import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
-import android.provider.MediaStore
import android.util.Log
+import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Column
@@ -33,6 +34,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
@@ -55,7 +57,6 @@ internal class MainActivity : AppCompatActivity() {
companion object {
private const val TAG = "MainActivity"
- private const val REQUEST_TAKE_PHOTO = 1
fun imagesDir(activity: Activity): File {
return File(activity.filesDir, "photos")
@@ -97,15 +98,27 @@ internal class MainActivity : AppCompatActivity() {
private fun Main() {
val context = this
val images = this.appViewModel.allImages().observeAsState().value
- //Log.i(TAG, "images: ${images?.size}")
val pagerState = rememberPagerState(
pageCount = {
pagerPageCount(images)
}
)
+ var imageUri: Uri? by rememberSaveable { mutableStateOf(null) }
+ val cameraLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.TakePicture(),
+ onResult = { success ->
+ if (success) {
+ appViewModel.addImage(imageUri.toString())
+ }
+ // TODO else analytics
+ }
+ )
val floatingActionButton = @Composable {
FloatingActionButton(
- onClick = ::handleTakePhoto,
+ onClick = {
+ imageUri = createTakeImageUri()
+ cameraLauncher.launch(imageUri)
+ },
) {
Icon(
Icons.Default.Add,
@@ -183,7 +196,7 @@ internal class MainActivity : AppCompatActivity() {
MergedImage()
return
}
- val image = images?.get(page)
+ val image = images[page]
InputImage(image)
}
@@ -218,32 +231,15 @@ internal class MainActivity : AppCompatActivity() {
this.firebaseAnalytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW, params)
}
- // TODO binding.fab.setOnClickListener { handleTakePhoto() }
- private fun handleTakePhoto() {
- val intent = createTakePhotoIntent()
- startActivityForResult(intent, REQUEST_TAKE_PHOTO)
- }
-
- private fun createTakePhotoIntent(): Intent {
- val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
- val uuid = UUID.randomUUID()
- val file = File(imagesDir, "${uuid}.jpg")
- val imageUri: Uri = FileProvider.getUriForFile(
- baseContext,
- BuildConfig.APPLICATION_ID + ".fileprovider",
- file
- )
- intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri)
- return intent
- }
-
- // Activity
- override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
- super.onActivityResult(requestCode, resultCode, data)
- if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_TAKE_PHOTO) {
- // TODO this.appViewModel.addImage(file.path)
- }
+ private fun createTakeImageUri(): Uri {
+ val uuid = UUID.randomUUID()
+ val file = File(imagesDir, "${uuid}.jpg")
+ return FileProvider.getUriForFile(
+ baseContext,
+ BuildConfig.APPLICATION_ID + ".fileprovider",
+ file
+ )
}
} // MainActivity
From 45ef5621f96f25e606cd62526717a8a120a8e80c Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Wed, 5 Jun 2024 10:58:38 +0100
Subject: [PATCH 31/37] Refactor photo taking code into own Composable.
---
.../jeremygreen/merging/main/MainActivity.kt | 34 +++++++++++--------
1 file changed, 20 insertions(+), 14 deletions(-)
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
index 12f8563..0ef72a1 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
@@ -90,6 +90,24 @@ internal class MainActivity : AppCompatActivity() {
// }
}
+ @Composable
+ fun takePicture(): () -> Unit {
+ var imageUri: Uri? by rememberSaveable { mutableStateOf(null) }
+ val cameraLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.TakePicture(),
+ onResult = { success ->
+ if (success) {
+ appViewModel.addImage(imageUri.toString())
+ }
+ // TODO else analytics
+ }
+ )
+ return {
+ imageUri = createTakeImageUri()
+ cameraLauncher.launch(imageUri)
+ }
+ }
+
@OptIn(
ExperimentalFoundationApi::class,
ExperimentalMaterial3Api::class
@@ -103,22 +121,10 @@ internal class MainActivity : AppCompatActivity() {
pagerPageCount(images)
}
)
- var imageUri: Uri? by rememberSaveable { mutableStateOf(null) }
- val cameraLauncher = rememberLauncherForActivityResult(
- contract = ActivityResultContracts.TakePicture(),
- onResult = { success ->
- if (success) {
- appViewModel.addImage(imageUri.toString())
- }
- // TODO else analytics
- }
- )
+ val takePicture = takePicture()
val floatingActionButton = @Composable {
FloatingActionButton(
- onClick = {
- imageUri = createTakeImageUri()
- cameraLauncher.launch(imageUri)
- },
+ onClick = { takePicture() },
) {
Icon(
Icons.Default.Add,
From d19243105bd1b787ceac6ce968c2e718b8bb0b2b Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Wed, 5 Jun 2024 11:12:57 +0100
Subject: [PATCH 32/37] Need to create imagesDir.
---
.../main/java/uk/me/jeremygreen/merging/main/MainActivity.kt | 3 +++
1 file changed, 3 insertions(+)
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
index 0ef72a1..a895051 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
@@ -39,6 +39,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.core.content.FileProvider
+import com.facebook.common.file.FileUtils
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import uk.me.jeremygreen.merging.BuildConfig
@@ -96,6 +97,7 @@ internal class MainActivity : AppCompatActivity() {
val cameraLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.TakePicture(),
onResult = { success ->
+ //Log.i(TAG, "takePicture: uri: $imageUri success: $success")
if (success) {
appViewModel.addImage(imageUri.toString())
}
@@ -103,6 +105,7 @@ internal class MainActivity : AppCompatActivity() {
}
)
return {
+ FileUtils.mkdirs(imagesDir)
imageUri = createTakeImageUri()
cameraLauncher.launch(imageUri)
}
From 7d2550f12551f312a444f0e7ec58b59de118480c Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Wed, 5 Jun 2024 11:13:23 +0100
Subject: [PATCH 33/37] Remove obsolete commented out code.
---
.../main/java/uk/me/jeremygreen/merging/main/MainActivity.kt | 3 ---
1 file changed, 3 deletions(-)
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
index a895051..dead982 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
@@ -86,9 +86,6 @@ internal class MainActivity : AppCompatActivity() {
crashlytics.setCrashlyticsCollectionEnabled(true)
}
setContent { Main() }
-// this.appViewModel.allImages().observe(this) { images ->
-// this.pagerAdapter.setImages(images)
-// }
}
@Composable
From d4c5725afa0c82b525d4b537e141f94a038837a7 Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Wed, 5 Jun 2024 11:21:36 +0100
Subject: [PATCH 34/37] Centre align merged image text.
---
.../merging/main/screen/MergedImage.kt | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/screen/MergedImage.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/screen/MergedImage.kt
index 5cc5ca3..ea1322e 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/main/screen/MergedImage.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/main/screen/MergedImage.kt
@@ -1,7 +1,12 @@
package uk.me.jeremygreen.merging.main.screen
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import uk.me.jeremygreen.merging.R
@@ -10,5 +15,13 @@ import uk.me.jeremygreen.merging.R
@Composable
fun MergedImage() {
- Text(text = stringResource(R.string.merged_image))
+ Column (
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ modifier = Modifier.fillMaxSize()
+ ) {
+ Text(
+ text = stringResource(R.string.merged_image)
+ )
+ }
}
From d53c08bc8aca0020b0834d107dc0d23ae3df4c26 Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Wed, 5 Jun 2024 15:26:26 +0100
Subject: [PATCH 35/37] Workaround for out-of-range page if add image when on
merged image page if beyondBoundsPageCount set to 2 not 0.
---
.../main/java/uk/me/jeremygreen/merging/main/MainActivity.kt | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
index dead982..199089c 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
@@ -193,6 +193,7 @@ internal class MainActivity : AppCompatActivity() {
@Composable
private fun Pages(images: List?, page: Int) {
+ Log.i(TAG, "Pages: page=$page images=${images?.size}")
if (images.isNullOrEmpty()) {
AddImage()
return
@@ -202,6 +203,10 @@ internal class MainActivity : AppCompatActivity() {
MergedImage()
return
}
+ if (page < 0 || page >= images.size) {
+ // Non-zero beyondBoundsPageCount causes out-of-range page to be provided.
+ return
+ }
val image = images[page]
InputImage(image)
}
From 2f1d078a996e405f8e9d79d9586caf9911470f9b Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Wed, 5 Jun 2024 15:35:01 +0100
Subject: [PATCH 36/37] Implement long-press image deletion (without
confirmation dialog).
---
.../me/jeremygreen/merging/main/MainActivity.kt | 2 +-
.../jeremygreen/merging/main/screen/InputImage.kt | 15 +++++++++++++--
2 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
index 199089c..fb70480 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
@@ -208,7 +208,7 @@ internal class MainActivity : AppCompatActivity() {
return
}
val image = images[page]
- InputImage(image)
+ InputImage(image, onLongClick = { appViewModel.delete(image) })
}
@Composable
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/screen/InputImage.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/screen/InputImage.kt
index cb725b9..5785156 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/main/screen/InputImage.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/main/screen/InputImage.kt
@@ -5,9 +5,12 @@ import android.content.DialogInterface
import android.os.Bundle
import android.util.Log
import android.view.View
+import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
+import androidx.compose.foundation.combinedClickable
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import com.google.firebase.crashlytics.FirebaseCrashlytics
@@ -32,9 +35,17 @@ private val faceDetectorOptions =
.setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_NONE)
.build()
+@OptIn(ExperimentalFoundationApi::class)
@Composable
-internal fun InputImage(image: Image?) {
- Text(image?.uri.toString())
+internal fun InputImage(image: Image?, onLongClick : () -> Unit = {}) {
+ Text(
+ text = image?.uri.toString(),
+ modifier = Modifier.combinedClickable(
+ enabled = true,
+ onLongClick = { onLongClick() },
+ onClick = {}
+ )
+ )
// TODO show FacesView
}
From 38fc459248f4469840b6a253b854ffca5021b031 Mon Sep 17 00:00:00 2001
From: Jeremy Green <20728342+jg210@users.noreply.github.com>
Date: Wed, 21 Aug 2024 16:31:02 +0100
Subject: [PATCH 37/37] Try out coil for image loading. Get weird UI glitches
in combination with left-right swiping component.
---
.idea/deploymentTargetDropDown.xml | 15 ++++++-
README.md | 2 +-
app/build.gradle | 2 +
.../jeremygreen/merging/main/MainActivity.kt | 4 ++
.../merging/main/screen/InputImage.kt | 39 +++++++++----------
5 files changed, 40 insertions(+), 22 deletions(-)
diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
index 0c0c338..59e2001 100644
--- a/.idea/deploymentTargetDropDown.xml
+++ b/.idea/deploymentTargetDropDown.xml
@@ -3,7 +3,20 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
index 5c5186b..06dc4a7 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ It's using:
* [ML Kit](https://developers.google.com/ml-kit/vision/face-detection) for face feature detection.
* [Firebase Crashlytics](https://firebase.google.com/docs/crashlytics/) for crash reporting.
* [Firebase Analytics](https://firebase.google.com/docs/analytics).
-* [Fresco](https://developers.google.com/ml-kit/) for android Bitmap management.
+* [Coil](https://coil-kt.github.io/coil/) for android Bitmap management.
* [Circle CI](https://circleci.com/gh/jg210/merging) for automated build, test and continuous delivery [![CircleCI](https://circleci.com/gh/jg210/merging/tree/develop.svg?style=svg)](https://circleci.com/gh/jg210/merging)
* [Fastlane](https://fastlane.tools/) to publish the app as a beta (early access) release for every commit on the release branch. [](https://play.google.com/store/apps/details?id=uk.me.jeremygreen.merging)
* [Material Design](https://material.io/design/).
diff --git a/app/build.gradle b/app/build.gradle
index ea5c914..5467a5e 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -129,6 +129,8 @@ dependencies {
implementation "androidx.room:room-ktx:${room_version}"
implementation "androidx.room:room-runtime:${room_version}"
implementation 'androidx.viewpager2:viewpager2:1.0.0'
+ implementation("io.coil-kt:coil:2.6.0")
+ implementation("io.coil-kt:coil-compose:2.6.0")
implementation 'com.facebook.fresco:fresco:3.1.3'
// https://github.com/facebook/fresco/issues/2598#issuecomment-1110860661
compileOnly 'com.facebook.infer.annotation:infer-annotation:0.18.0'
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
index fb70480..9d0f40c 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/main/MainActivity.kt
@@ -10,8 +10,10 @@ import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
@@ -39,7 +41,9 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.core.content.FileProvider
+import coil.compose.AsyncImage
import com.facebook.common.file.FileUtils
+import com.facebook.imagepipeline.request.ImageRequest
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import uk.me.jeremygreen.merging.BuildConfig
diff --git a/app/src/main/java/uk/me/jeremygreen/merging/main/screen/InputImage.kt b/app/src/main/java/uk/me/jeremygreen/merging/main/screen/InputImage.kt
index 5785156..d7e8e70 100644
--- a/app/src/main/java/uk/me/jeremygreen/merging/main/screen/InputImage.kt
+++ b/app/src/main/java/uk/me/jeremygreen/merging/main/screen/InputImage.kt
@@ -1,25 +1,16 @@
package uk.me.jeremygreen.merging.main.screen
-import android.app.AlertDialog
-import android.content.DialogInterface
-import android.os.Bundle
-import android.util.Log
-import android.view.View
import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.Image
import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.ImageBitmap
-import androidx.compose.ui.graphics.painter.BitmapPainter
-import com.google.firebase.crashlytics.FirebaseCrashlytics
+import coil.compose.AsyncImage
+import coil.request.ImageRequest
import com.google.mlkit.vision.face.FaceDetectorOptions
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import uk.me.jeremygreen.merging.R
import uk.me.jeremygreen.merging.model.Image
-import uk.me.jeremygreen.merging.model.ProcessingStage
+import java.io.File
private const val TAG = "ImageFragment"
private const val BUNDLE_KEY__IMAGE_ID = "imageId"
@@ -38,13 +29,21 @@ private val faceDetectorOptions =
@OptIn(ExperimentalFoundationApi::class)
@Composable
internal fun InputImage(image: Image?, onLongClick : () -> Unit = {}) {
- Text(
- text = image?.uri.toString(),
- modifier = Modifier.combinedClickable(
- enabled = true,
- onLongClick = { onLongClick() },
- onClick = {}
- )
+ if (image == null) {
+ return
+ }
+ Text("${image.uri} exists: ${File(image.uri.path).exists()}")
+ AsyncImage(
+// model = image.uri,
+ model = "https://upload.wikimedia.org/wikipedia/commons/thumb/8/8d/Tabby_Kitten_on_Blue_Throw.jpg/1280px-Tabby_Kitten_on_Blue_Throw.jpg",
+ contentDescription = null,
+ modifier = Modifier.
+ fillMaxSize().
+ combinedClickable(
+ enabled = true,
+ onLongClick = { onLongClick() },
+ onClick = {}
+ )
)
// TODO show FacesView
}