diff --git a/.gitignore b/.gitignore
index c0850d2..455a8bc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -20,8 +20,6 @@ app/build/
app/debug/
app/release/
captures/
-.externalNativeBuild
-.cxx
local.properties
keystore.properties
key.jks
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 5cc6f01..a7198d0 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -2,20 +2,23 @@ plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'org.jetbrains.kotlin.plugin.serialization' version '2.2.20'
+ id 'org.jetbrains.kotlin.plugin.compose' version '2.2.20'
id 'com.mikepenz.aboutlibraries.plugin' version '12.2.4'
}
android {
namespace 'net.helcel.beans'
- compileSdk 34
+ compileSdk 36
defaultConfig {
+ buildConfigField("String", "APP_NAME", "\"Beans\"")
+ manifestPlaceholders["APP_NAME"] = "Beans"
applicationId 'net.helcel.beans'
minSdk 28
- targetSdk 34
- versionCode 2
- versionName "1.0b"
+ targetSdk 36
+ versionCode 3
+ versionName "1.0c"
}
signingConfigs {
create("release") {
@@ -54,17 +57,15 @@ android {
compileOptions {
coreLibraryDesugaringEnabled true
- sourceCompatibility JavaVersion.VERSION_17
- targetCompatibility JavaVersion.VERSION_17
+ sourceCompatibility JavaVersion.VERSION_21
+ targetCompatibility JavaVersion.VERSION_21
encoding 'utf-8'
}
- kotlinOptions {
- jvmTarget = JavaVersion.VERSION_17
- }
-
buildFeatures {
viewBinding true
+ compose true
+ buildConfig true
}
dependenciesInfo {
@@ -73,21 +74,43 @@ android {
// Disables dependency metadata when building Android App Bundles.
includeInBundle = false
}
+ composeOptions {
+ kotlinCompilerExtensionVersion = "2.2.20"
+ }
+
+ lint {
+ disable 'UsingMaterialAndMaterial3Libraries'
+ }
}
aboutLibraries {
- exclusionPatterns = [~"androidx.*", ~"com.google.android.*", ~"org.jetbrains.*"]
- configPath = "config"
+ library {
+ exclusionPatterns = [~"androidx.*", ~"com.google.android.*", ~"org.jetbrains.*"]
+ }
excludeFields = ["generated"]
}
dependencies {
+ implementation 'androidx.compose.material3:material3:1.3.2'
+ implementation 'androidx.navigation:navigation-compose:2.9.4'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.1.5'
implementation 'androidx.preference:preference-ktx:1.2.1'
+ implementation 'androidx.compose.ui:ui'
+ implementation "androidx.compose.material:material:1.9.1"
+ implementation "androidx.activity:activity-ktx:1.11.0"
+
+ implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'com.google.android.material:material:1.13.0'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0'
implementation 'com.caverock:androidsvg-aar:1.4'
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
+
implementation 'com.mikepenz:aboutlibraries:12.2.4'
+ implementation 'com.mikepenz:aboutlibraries-compose-m3:12.2.4'
+ implementation 'com.mikepenz:aboutlibraries-core:12.2.4'
+
+
+ implementation platform('androidx.compose:compose-bom:2025.09.00')
+ debugImplementation 'androidx.compose.ui:ui-tooling:1.9.1'
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c859234..e3b6df2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,52 +1,22 @@
-
+ xmlns:tools="http://schemas.android.com/tools">
-
-
+ tools:replace="android:allowBackup">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/java/net/helcel/beans/activity/EditActivity.kt b/app/src/main/java/net/helcel/beans/activity/EditActivity.kt
deleted file mode 100644
index b51c8f3..0000000
--- a/app/src/main/java/net/helcel/beans/activity/EditActivity.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-package net.helcel.beans.activity
-
-import android.os.Bundle
-import android.view.Menu
-import android.view.MenuItem
-import androidx.activity.addCallback
-import androidx.appcompat.app.AppCompatActivity
-import com.google.android.material.tabs.TabLayoutMediator
-import net.helcel.beans.R
-import net.helcel.beans.activity.adapter.ViewPagerAdapter
-import net.helcel.beans.activity.fragment.EditGroupAddFragment
-import net.helcel.beans.activity.fragment.EditPlaceFragment
-import net.helcel.beans.countries.World
-import net.helcel.beans.databinding.ActivityEditBinding
-import net.helcel.beans.helper.Data
-import net.helcel.beans.helper.Settings
-import net.helcel.beans.helper.Theme.createActionBar
-
-
-class EditActivity : AppCompatActivity() {
-
- private lateinit var _binding: ActivityEditBinding
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- _binding = ActivityEditBinding.inflate(layoutInflater)
-
- setContentView(_binding.root)
- createActionBar(this, getString(R.string.action_edit))
-
- val adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, _binding.pager)
- _binding.pager.adapter = adapter
- adapter.addFragment(null, EditPlaceFragment(World.WWW, adapter))
-
- TabLayoutMediator(_binding.tab, _binding.pager) { tab, position ->
- tab.text = adapter.getLabel(position)
- }.attach()
-
- onBackPressedDispatcher.addCallback {
- if (!adapter.backPressed()) {
- finish()
- }
- }
- }
-
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- if (Settings.isSingleGroup(this)) {
- menuInflater.inflate(R.menu.menu_edit, menu)
- }
- return true
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- when (item.itemId) {
- R.id.action_color -> {
- Data.groups.getUniqueEntry()?.let { group ->
- EditGroupAddFragment(group.key, {
- (_binding.pager.adapter as ViewPagerAdapter?)?.refreshColors(group.color)
- }, {}, false).show(supportFragmentManager, "AddColorDialogFragment")
- }
- }
-
- else -> finish()
- }
- return super.onOptionsItemSelected(item)
- }
-
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/helcel/beans/activity/EditScreen.kt b/app/src/main/java/net/helcel/beans/activity/EditScreen.kt
new file mode 100644
index 0000000..628e2f1
--- /dev/null
+++ b/app/src/main/java/net/helcel/beans/activity/EditScreen.kt
@@ -0,0 +1,64 @@
+package net.helcel.beans.activity
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Scaffold
+import androidx.compose.material.Text
+import androidx.compose.material.TopAppBar
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import net.helcel.beans.R
+import net.helcel.beans.activity.sub.EditPlaceScreen
+import net.helcel.beans.countries.World
+
+@Composable
+fun EditScreen(onExit:()->Unit) {
+ SysTheme {
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text(stringResource(R.string.action_edit)) },
+ navigationIcon = {
+ IconButton(onClick = onExit) {
+ Icon(
+ Icons.AutoMirrored.Filled.ArrowBack,
+ contentDescription = null
+ )
+ }
+ }
+ )
+ },
+
+ ) { innerPadding ->
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(MaterialTheme.colors.background)
+ .padding(innerPadding) // ensures content is below the app bar
+ ) {
+ Column {
+ Spacer(
+ modifier = Modifier.height(2.dp).fillMaxWidth()
+ .background(MaterialTheme.colors.background)
+ )
+ EditPlaceScreen(World.WWW, onExit)
+ }
+ }
+ }
+ }
+}
+
+
diff --git a/app/src/main/java/net/helcel/beans/activity/MainActivity.kt b/app/src/main/java/net/helcel/beans/activity/MainActivity.kt
deleted file mode 100644
index 5608420..0000000
--- a/app/src/main/java/net/helcel/beans/activity/MainActivity.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-package net.helcel.beans.activity
-
-import android.content.Intent
-import android.graphics.drawable.PictureDrawable
-import android.os.Bundle
-import android.view.Menu
-import android.view.MenuItem
-import androidx.appcompat.app.AppCompatActivity
-import com.caverock.androidsvg.RenderOptions
-import net.helcel.beans.R
-import net.helcel.beans.countries.GeoLocImporter
-import net.helcel.beans.databinding.ActivityMainBinding
-import net.helcel.beans.helper.Data
-import net.helcel.beans.helper.Settings
-import net.helcel.beans.svg.CSSWrapper
-import net.helcel.beans.svg.SVGWrapper
-
-
-class MainActivity : AppCompatActivity() {
- private lateinit var _binding: ActivityMainBinding
-
- private lateinit var psvg: SVGWrapper
- private lateinit var css: CSSWrapper
-
- override fun onRestart() {
- refreshProjection()
- refreshMap()
- super.onRestart()
- }
-
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- menuInflater.inflate(R.menu.menu_main, menu)
- return true
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- val d = when (item.itemId) {
- R.id.action_edit -> EditActivity::class.java
- R.id.action_stats -> StatsActivity::class.java
- R.id.action_settings -> SettingsActivity::class.java
- else -> throw Exception("Non Existent Menu Item")
- }
- startActivity(Intent(this@MainActivity, d))
- return true
- }
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- _binding = ActivityMainBinding.inflate(layoutInflater)
- Settings.start(this)
-
- setContentView(_binding.root)
-
- _binding.photoView.minimumScale = 1f
- _binding.photoView.maximumScale = 40f
-
- GeoLocImporter.importStates(this)
- Data.loadData(this, Int.MIN_VALUE)
-
- refreshProjection()
- refreshMap()
- }
-
- private fun refreshMap() {
- val opt: RenderOptions = RenderOptions.create()
- opt.css(css.get())
- _binding.photoView.setImageDrawable(PictureDrawable(psvg.get()?.renderToPicture(opt)))
- }
-
- fun refreshProjection() {
- psvg = SVGWrapper(this)
- css = CSSWrapper(this)
- }
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/helcel/beans/activity/MainScreen.kt b/app/src/main/java/net/helcel/beans/activity/MainScreen.kt
new file mode 100644
index 0000000..3381aa0
--- /dev/null
+++ b/app/src/main/java/net/helcel/beans/activity/MainScreen.kt
@@ -0,0 +1,123 @@
+package net.helcel.beans.activity
+
+import android.graphics.drawable.PictureDrawable
+import android.os.Bundle
+import android.widget.ImageView
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Scaffold
+import androidx.compose.material.Text
+import androidx.compose.material.TopAppBar
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.DateRange
+import androidx.compose.material.icons.filled.Edit
+import androidx.compose.material.icons.filled.Settings
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import com.caverock.androidsvg.RenderOptions
+import com.github.chrisbanes.photoview.PhotoView
+import net.helcel.beans.BuildConfig
+import net.helcel.beans.countries.GeoLocImporter
+import net.helcel.beans.helper.Data
+import net.helcel.beans.helper.Settings
+import net.helcel.beans.svg.CSSWrapper
+import net.helcel.beans.svg.SVGWrapper
+
+
+class MainScreen : ComponentActivity() {
+
+ private lateinit var psvg: SVGWrapper
+ private lateinit var css: CSSWrapper
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ actionBar?.hide()
+ Settings.start(this)
+ GeoLocImporter.importStates(this)
+ Data.loadData(this, Int.MIN_VALUE)
+
+ setContent {
+ SysTheme {
+ Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background)) {
+ AppNavHost(psvg, css)
+ }
+ }
+ }
+ refreshProjection()
+ }
+
+ @Composable
+ fun AppNavHost(psvg: SVGWrapper, css: CSSWrapper) {
+ val navController = rememberNavController()
+ NavHost(navController, startDestination = "main") {
+ composable("main") { MainScreenC(psvg,css, navController) }
+ composable("settings") { SettingsMainScreen { navController.navigate("main")} }
+ composable("edit") { EditScreen { navController.navigate("main") } }
+ composable("stats") { StatsScreen { navController.navigate("main") } }
+ }
+ }
+
+ @Composable
+ fun MainScreenC(psvg: SVGWrapper,css: CSSWrapper, nav: NavHostController){
+ SysTheme {
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text(BuildConfig.APP_NAME) },
+ actions = {
+ IconButton(onClick = { nav.navigate("edit") }) {
+ Icon(Icons.Default.Edit, contentDescription = "Edit")
+ }
+ IconButton(onClick = { nav.navigate("stats") }){
+ Icon(Icons.Default.DateRange, contentDescription = "Stats")
+ }
+ IconButton(onClick = { nav.navigate("settings") }) {
+ Icon(Icons.Default.Settings, contentDescription = "Settings")
+ }
+ }
+ )
+ }
+ ) { innerPadding ->
+ Box(modifier = Modifier.padding(innerPadding)) {
+ MapScreen(psvg, css)
+ }
+ }
+ }
+ }
+
+ @Composable
+ fun MapScreen(psvg: SVGWrapper, css: CSSWrapper) {
+ Box {
+ val opt: RenderOptions = RenderOptions.create()
+ opt.css(css.get())
+ val drawable = remember(psvg, css) {
+ PictureDrawable(psvg.get()?.renderToPicture(opt))
+ }
+ AndroidView(factory = { ctx ->
+ PhotoView(ctx).apply {
+ setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null)
+ setImageDrawable(drawable)
+ scaleType = ImageView.ScaleType.FIT_CENTER
+ }
+ }, modifier = Modifier.fillMaxSize())
+ }
+ }
+
+ fun refreshProjection() {
+ psvg = SVGWrapper(this)
+ css = CSSWrapper(this)
+ }
+}
diff --git a/app/src/main/java/net/helcel/beans/activity/SettingsActivity.kt b/app/src/main/java/net/helcel/beans/activity/SettingsActivity.kt
deleted file mode 100644
index 89ea76d..0000000
--- a/app/src/main/java/net/helcel/beans/activity/SettingsActivity.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-package net.helcel.beans.activity
-
-import android.os.Bundle
-import android.view.MenuItem
-import androidx.appcompat.app.AppCompatActivity
-import net.helcel.beans.R
-import net.helcel.beans.activity.fragment.AboutFragment
-import net.helcel.beans.activity.fragment.LicenseFragment
-import net.helcel.beans.activity.fragment.SettingsFragment
-import net.helcel.beans.helper.Theme.createActionBar
-
-class SettingsActivity : AppCompatActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- setContentView(R.layout.activity_settings)
- createActionBar(this, getString(R.string.action_settings))
-
- // Populate activity with settings fragment
- supportFragmentManager.beginTransaction()
- .replace(R.id.fragment_view, SettingsFragment(), getString(R.string.action_settings))
- .commit()
-
- // Change title in action bar according to current fragment
- supportFragmentManager.addFragmentOnAttachListener { _, _ ->
- supportActionBar?.title =
- supportFragmentManager.findFragmentById(R.id.fragment_view).let {
- when (it) {
- is LicenseFragment -> getString(R.string.licenses)
- is AboutFragment -> getString(R.string.about)
- else -> getString(R.string.action_settings)
- }
- }
- }
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- // Configure on back pressed
- supportFragmentManager.findFragmentById(R.id.fragment_view).let {
- when (it) {
- is LicenseFragment, is AboutFragment -> {
- supportFragmentManager.beginTransaction()
- .remove(it)
- .commit()
- supportFragmentManager.beginTransaction()
- .replace(
- R.id.fragment_view,
- SettingsFragment(),
- getString(R.string.action_settings)
- )
- .commit()
- supportActionBar?.title = getString(R.string.action_settings)
- }
-
- else -> {
- finish()
- }
- }
- }
- return super.onOptionsItemSelected(item)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/helcel/beans/activity/SettingsScreen.kt b/app/src/main/java/net/helcel/beans/activity/SettingsScreen.kt
new file mode 100644
index 0000000..13743d4
--- /dev/null
+++ b/app/src/main/java/net/helcel/beans/activity/SettingsScreen.kt
@@ -0,0 +1,381 @@
+package net.helcel.beans.activity
+
+import android.os.Build
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Button
+import androidx.compose.material.CircularProgressIndicator
+import androidx.compose.material.Colors
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.RadioButton
+import androidx.compose.material.Scaffold
+import androidx.compose.material.Text
+import androidx.compose.material.TopAppBar
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.preference.PreferenceManager
+import net.helcel.beans.R
+import net.helcel.beans.countries.GeoLocImporter
+import net.helcel.beans.helper.Settings
+import androidx.core.content.edit
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import net.helcel.beans.activity.sub.AboutScreen
+import net.helcel.beans.activity.sub.EditPlaceColorDialog
+import net.helcel.beans.activity.sub.EditPlaceDialog
+import net.helcel.beans.activity.sub.LicenseScreen
+import net.helcel.beans.helper.Data
+
+@Composable
+fun SysTheme(
+ content: @Composable () -> Unit
+) {
+ val context = LocalContext.current
+ val prefs = PreferenceManager.getDefaultSharedPreferences(context)
+ val themeKey = prefs.getString(stringResource(R.string.key_theme), stringResource(R.string.system))
+ val darkTheme = when (themeKey) {
+ stringResource(R.string.system) -> isSystemInDarkTheme()
+ stringResource(R.string.light) -> false
+ stringResource(R.string.dark) -> true
+ else -> isSystemInDarkTheme()
+ }
+ val colorScheme = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ if(darkTheme) dynamicDarkColorScheme(LocalContext.current ) else dynamicLightColorScheme(LocalContext.current )
+ } else {
+ if(darkTheme) darkColorScheme() else lightColorScheme()
+ }
+ val m2colors = Colors(
+ primary = colorScheme.primary,
+ primaryVariant = colorScheme.primaryContainer,
+ secondary = colorScheme.secondary,
+ background = colorScheme.background,
+ surface = colorScheme.surface,
+ onPrimary = colorScheme.onPrimary,
+ onSecondary = colorScheme.onSecondary,
+ onBackground = colorScheme.onBackground,
+ onSurface = colorScheme.onSurface,
+ secondaryVariant = colorScheme.secondary,
+ error = colorScheme.error,
+ onError = colorScheme.onError,
+ isLight = !darkTheme,
+ )
+
+ MaterialTheme(
+ colors = m2colors,
+ content = content
+ )
+}
+
+
+@Composable
+fun settingsNav(): NavHostController {
+ val navController = rememberNavController()
+ NavHost(navController, startDestination= "settings"){
+ composable("settings"){SettingsScreen(navController)}
+ composable("licenses"){ LicenseScreen() }
+ composable("about"){ AboutScreen() }
+ }
+ return navController
+}
+
+@Composable
+fun SettingsMainScreen(onExit: ()->Unit = {}) {
+ var nav: NavHostController? = null
+ SysTheme {
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text(stringResource(R.string.action_settings)) },
+ navigationIcon = {
+ IconButton(onClick = {
+ if(nav!=null && !nav!!.popBackStack())
+ onExit()
+ }) {
+ Icon(
+ Icons.AutoMirrored.Filled.ArrowBack,
+ contentDescription = null
+ )
+ }
+ }
+ )
+ }
+ ) { innerPadding ->
+ Box(modifier = Modifier.padding(innerPadding)) {
+ nav = settingsNav()
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+fun SettingsScreen(navController: NavHostController = settingsNav()) {
+ val context = LocalContext.current
+ val prefs = PreferenceManager.getDefaultSharedPreferences(context)
+ var showEdit by remember { mutableStateOf(false) }
+
+ var theme by remember { mutableStateOf(prefs.getString(context.getString(R.string.key_theme), context.getString(R.string.system))!!) }
+ var projection by remember { mutableStateOf(prefs.getString(context.getString(R.string.key_projection), "default")!!) }
+ var groups by remember { mutableStateOf(prefs.getString(context.getString(R.string.key_group), context.getString(R.string.off))!!) }
+
+ if(showEdit)
+ EditPlaceDialog(true) {
+ showEdit = false
+ val g = Data.selected_group
+ if (it && g != null)
+ Data.visits.reassignAllVisitedToGroup(g.key)
+ }
+
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp)
+ .background(MaterialTheme.colors.background)
+ ) {
+ item {
+ Text(
+ "Theme", style = MaterialTheme.typography.h6,
+ color = MaterialTheme.colors.onBackground,
+ )
+ MultiPreference(arrayOf(stringResource(R.string.system),stringResource(R.string.light),stringResource(R.string.dark)), theme) { newTheme ->
+ theme = newTheme
+ prefs.edit { putString(context.getString(R.string.key_theme), newTheme) }
+ }
+ HorizontalDivider()
+ }
+ item {
+ Text(
+ "Map Projection",
+ style = MaterialTheme.typography.h6,
+ color = MaterialTheme.colors.onBackground,
+ modifier = Modifier.padding(top = 16.dp)
+ )
+ MultiPreference(arrayOf(stringResource(R.string.mercator), stringResource(R.string.azimuthalequidistant)), projection) { newProj ->
+ projection = newProj
+ prefs.edit { putString(context.getString(R.string.key_projection), newProj) }
+ Settings.refreshProjection()
+ }
+ HorizontalDivider()
+ }
+ item {
+ Text(
+ "Groups",
+ style = MaterialTheme.typography.h6,
+ color = MaterialTheme.colors.onBackground,
+ modifier = Modifier.padding(top = 16.dp)
+ )
+ var showDialog by remember{mutableStateOf(false)}
+ if(showDialog){
+ EditPlaceColorDialog(
+ deleteMode = true,
+ onDismiss = {
+ val g = Data.selected_group
+ if (g != null)
+ Data.visits.reassignAllVisitedToGroup(g.key)
+ showDialog = false
+ })
+ }
+ MultiPreference(
+ arrayOf(stringResource(R.string.on), stringResource(R.string.off)),
+ groups
+ ) { key ->
+ if (key == context.getString(R.string.off)) {
+ showDialog=true
+ }
+ groups = key
+ prefs.edit { putString(context.getString(R.string.key_group), key) }
+ }
+ HorizontalDivider()
+ }
+ item {
+ Text(
+ text = "Regional",
+ style = MaterialTheme.typography.h6,
+ color = MaterialTheme.colors.onBackground,
+ modifier = Modifier
+ .padding(top = 16.dp)
+ .clickable(onClick = {}),
+ )
+ RegionalScreen()
+ HorizontalDivider()
+ }
+ item{
+ val launcher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.OpenDocument(),
+ onResult = { uri -> Data.doImport(context, uri) }
+ )
+ Row(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Button(onClick = {
+ launcher.launch(arrayOf("*/*"))
+ }, modifier = Modifier
+ .fillMaxWidth(fraction = 0.4f)
+ .padding(vertical = 8.dp)) {
+ Text("Import")
+ }
+ Spacer(
+ modifier = Modifier.fillMaxWidth(0.4f)
+ )
+ Button(onClick = {
+ Data.doExport(context)
+ }, modifier = Modifier
+ .fillMaxWidth(fraction = 1f)
+ .padding(vertical = 8.dp)) {
+ Text("Export")
+ }
+ }
+ HorizontalDivider()
+ }
+ item {
+ PreferenceButton("Licenses") {
+ if (navController.currentDestination?.route != "licenses")
+ navController.navigate("licenses")
+ }
+ PreferenceButton("About") {
+ if (navController.currentDestination?.route != "about")
+ navController.navigate("about")
+ }
+ }
+ }
+ }
+
+@Composable
+fun RegionalScreen() {
+ val context = LocalContext.current
+ val prefs = PreferenceManager.getDefaultSharedPreferences(context)
+ var selected by remember { mutableStateOf(prefs.getString(context.getString(R.string.key_regional),context.getString(R.string.off))!!)}
+ var regional by remember{ mutableStateOf(prefs.getString(context.getString(R.string.key_regional), context.getString(R.string.off))!!)}
+ var showDialog by remember{mutableStateOf(false)}
+ var showLoad by remember{mutableStateOf(false)}
+
+ if(showDialog)
+ Dialog(
+ content = {
+ Column(
+ modifier = Modifier
+ .background(
+ MaterialTheme.colors.background,
+ RoundedCornerShape(corner = CornerSize(16.dp))
+ )
+ .padding(16.dp),){
+ Text(style=MaterialTheme.typography.caption, text= stringResource(R.string.delete_regions))
+ Button(onClick = {
+ GeoLocImporter.clearStates()
+ regional= selected
+ prefs.edit {
+ putString(
+ context.getString(R.string.key_regional),
+ regional
+ )
+ }
+ showDialog=false
+ }){
+ Text(stringResource(R.string.ok))
+ }
+ }
+ },
+ onDismissRequest = { showDialog=false }
+ )
+ if(showLoad){
+ Dialog(
+ content = {
+ CircularProgressIndicator(
+ color = MaterialTheme.colors.primary,
+ strokeWidth = 4.dp,
+ modifier = Modifier.size(50.dp)
+ )
+ },
+ onDismissRequest = {}
+ )
+ }
+ val scope = rememberCoroutineScope()
+ MultiPreference(arrayOf(stringResource(R.string.on),stringResource(R.string.off)),regional) { key ->
+ when (key) {
+ context.getString(R.string.off) -> { showDialog=true
+ selected=key
+ }
+ context.getString(R.string.on) -> {
+ regional = key
+ prefs.edit { putString(context.getString(R.string.key_regional), key) }
+ showLoad=true
+ scope.launch {
+ withContext(Dispatchers.IO) {
+ GeoLocImporter.importStates(context, true)
+ }
+ showLoad = false
+ }
+ }
+ }
+ }
+}
+
+
+@Composable
+fun MultiPreference(list: Array, selected: String, onSelected: (String) -> Unit) {
+ Column(Modifier.padding(2.dp)) {
+ list.map { value ->
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(36.dp)
+ .clickable { onSelected(value) }) {
+ RadioButton(selected = selected == value, onClick = { onSelected(value) })
+ Text(
+ value, modifier = Modifier.padding(start = 8.dp),
+ color = MaterialTheme.colors.onBackground,
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun PreferenceButton(text: String, onClick: () -> Unit) {
+ Button(onClick = onClick, modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 8.dp)) {
+ Text(text)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/helcel/beans/activity/StatsActivity.kt b/app/src/main/java/net/helcel/beans/activity/StatsActivity.kt
index b13c776..0f01c2d 100644
--- a/app/src/main/java/net/helcel/beans/activity/StatsActivity.kt
+++ b/app/src/main/java/net/helcel/beans/activity/StatsActivity.kt
@@ -1,57 +1,159 @@
package net.helcel.beans.activity
-import android.os.Bundle
-import android.view.MenuItem
-import androidx.appcompat.app.AppCompatActivity
-import androidx.fragment.app.Fragment
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import androidx.viewpager2.adapter.FragmentStateAdapter
-import androidx.viewpager2.widget.ViewPager2
-import com.google.android.material.tabs.TabLayoutMediator
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.Button
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.Scaffold
+import androidx.compose.material.Tab
+import androidx.compose.material.TabRow
+import androidx.compose.material.Text
+import androidx.compose.material.TopAppBar
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
import net.helcel.beans.R
-import net.helcel.beans.activity.adapter.StatsListAdapter
import net.helcel.beans.countries.GeoLoc.LocType
-import net.helcel.beans.databinding.ActivityStatBinding
-import net.helcel.beans.helper.Settings
-import net.helcel.beans.helper.Theme.createActionBar
+import net.helcel.beans.countries.World
+import net.helcel.beans.helper.AUTO_GROUP
+import net.helcel.beans.helper.Data
+import net.helcel.beans.helper.Groups
+import net.helcel.beans.helper.Settings.isRegional
+import net.helcel.beans.helper.Theme.getContrastColor
private val MODE_LIST = listOf(LocType.WORLD, LocType.COUNTRY, LocType.STATE)
-class StatsActivity : AppCompatActivity() {
- private lateinit var _binding: ActivityStatBinding
- private var activeMode = LocType.WORLD
+@Composable
+fun StatsScreen(
+ onExit: ()-> Unit
+) {
+ val modes = if (isRegional(LocalContext.current)) MODE_LIST else MODE_LIST.take(2)
+ var selectedTab by remember { mutableIntStateOf(0) }
+ var countMode by remember { mutableStateOf(true) }
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- _binding = ActivityStatBinding.inflate(layoutInflater)
- setContentView(_binding.root)
- createActionBar(this, getString(R.string.action_stat))
- _binding.stats.layoutManager =
- LinearLayoutManager(this, RecyclerView.VERTICAL, false)
- val adapter = StatsListAdapter(_binding.stats, _binding.name)
- _binding.groupColor.setOnClickListener { adapter.invertCountMode() }
- _binding.stats.adapter = adapter
+ SysTheme {
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = {
+ Row(verticalAlignment = Alignment.CenterVertically){
+ Text(text=stringResource(R.string.action_edit), modifier = Modifier.weight(1f))
+ Button(onClick = { countMode = !countMode }) {
+ Text(if (countMode) "Count" else "Area")
+ }
+ }
+ },
+ navigationIcon = {
+ IconButton(onClick = onExit) {
+ Icon(
+ Icons.AutoMirrored.Filled.ArrowBack,
+ contentDescription = null
+ )
+ }
+ },
- _binding.pager.adapter = object : FragmentStateAdapter(supportFragmentManager, lifecycle) {
- override fun getItemCount(): Int = if (Settings.isRegional(applicationContext)) 3 else 2
- override fun createFragment(position: Int): Fragment = Fragment()
- }
- TabLayoutMediator(_binding.tab, _binding.pager) { tab, position ->
- tab.text = MODE_LIST[position].txt
- }.attach()
+ )
+ },
+ ) { padding ->
+ Column(Modifier.padding(padding)) {
+ TabRow(selectedTabIndex = selectedTab) {
+ modes.forEachIndexed { index, mode ->
+ Tab(
+ selected = selectedTab == index,
+ onClick = { selectedTab = index },
+ text = { Text(mode.txt) }
+ )
+ }
+ }
- _binding.pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
- override fun onPageSelected(position: Int) {
- activeMode = MODE_LIST[position]
- adapter.refreshMode(activeMode)
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(8.dp),
+ horizontalArrangement = Arrangement.End
+ ) {
+ }
+
+ val activeMode = modes.getOrNull(selectedTab) ?: LocType.WORLD
+ StatsList(activeMode, countMode)
}
- })
+ }
+ }
+}
+
+@Composable
+fun StatsList(activeMode: LocType, countMode: Boolean) {
+ val groups = remember { Data.groups.groupsFlow }
+ val unCat = stringResource(R.string.uncategorized)
+
+ LazyColumn(modifier = Modifier.fillMaxSize()) {
+ items(groups.value + listOf(Groups.Group(AUTO_GROUP, unCat))) { group ->
+ StatsRow(group, activeMode, countMode)
+ }
+ }
+}
+
+@Composable
+fun StatsRow(group: Groups.Group, activeMode: LocType, countMode: Boolean) {
+ val context = LocalContext.current
+
+ val visited = remember(group, activeMode) {
+ Data.visits.getVisitedByValue(group.key)
}
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- finish()
- return super.onOptionsItemSelected(item)
+ val count = when (activeMode) {
+ LocType.WORLD -> World.WWW.children.filter { it.code in visited }.size
+ LocType.COUNTRY -> World.WWW.children.flatMap { it.children.filter { c -> c.code in visited } }.size
+ LocType.STATE -> World.WWW.children.flatMap { itc->itc.children.flatMap { it.children.filter { it.code in visited } } }.size
+ else -> 0
+ }
+
+ val area = when (activeMode) {
+ LocType.WORLD -> World.WWW.children.filter { it.code in visited }.sumOf { it.area }
+ LocType.COUNTRY -> World.WWW.children.flatMap { it.children.filter { c -> c.code in visited } }.sumOf { it.area }
+ LocType.STATE -> World.WWW.children.flatMap { it.children.flatMap { it.children.filter { it.code in visited } } }.sumOf { it.area }
+ else -> 0
+ }
+
+ val displayValue = if (countMode) count.toString() else context.getString(R.string.number_with_unit, area, "km²")
+
+ val backgroundColor = group.color.color
+ val textColor = getContrastColor(backgroundColor)
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(Color(backgroundColor))
+ .padding(16.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text=group.name,
+ modifier= Modifier.weight(1f),
+ color = Color(textColor)
+ )
+ Text(text=displayValue,
+ color = Color(textColor)
+ )
}
}
\ No newline at end of file
diff --git a/app/src/main/java/net/helcel/beans/activity/adapter/GeolocListAdapter.kt b/app/src/main/java/net/helcel/beans/activity/adapter/GeolocListAdapter.kt
deleted file mode 100644
index af7db05..0000000
--- a/app/src/main/java/net/helcel/beans/activity/adapter/GeolocListAdapter.kt
+++ /dev/null
@@ -1,193 +0,0 @@
-package net.helcel.beans.activity.adapter
-
-import android.content.res.ColorStateList
-import android.graphics.Color
-import android.graphics.Typeface
-import android.graphics.drawable.ColorDrawable
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import androidx.fragment.app.FragmentActivity
-import androidx.recyclerview.widget.RecyclerView
-import com.google.android.material.checkbox.MaterialCheckBox
-import net.helcel.beans.activity.fragment.EditPlaceColorFragment
-import net.helcel.beans.activity.fragment.EditPlaceFragment
-import net.helcel.beans.countries.GeoLoc
-import net.helcel.beans.databinding.ItemListGeolocBinding
-import net.helcel.beans.helper.*
-import net.helcel.beans.helper.Theme.colorWrapper
-
-class GeolocListAdapter(
- private val ctx: EditPlaceFragment, private val l: GeoLoc, private val pager: ViewPagerAdapter,
- private val parentHolder: FoldingListViewHolder?
-) : RecyclerView.Adapter() {
-
- private val sortedList = l.children.toList().sortedBy { it.fullName }
- private val holders: MutableSet = mutableSetOf()
-
- override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): FoldingListViewHolder {
- val binding = ItemListGeolocBinding.inflate(
- LayoutInflater.from(viewGroup.context),
- viewGroup,
- false
- )
- val holder = FoldingListViewHolder(ctx.requireActivity(), binding, parentHolder, l)
- holders.add(holder)
- return holder
- }
-
- override fun onBindViewHolder(holder: FoldingListViewHolder, position: Int) {
- val el = sortedList[position]
- holder.bind(el)
- holder.addListeners(el) {
- if (el.children.isNotEmpty())
- pager.addFragment(ctx, EditPlaceFragment(el, pager, holder))
- true
- }
- }
-
- override fun getItemCount(): Int {
- return l.children.size
- }
-
- fun refreshColors(colorDrawable: ColorDrawable) {
- holders.forEach { it.refreshColor(colorDrawable) }
- }
-
- class FoldingListViewHolder(
- private val ctx: FragmentActivity,
- private val _binding: ItemListGeolocBinding,
- private val _parentHolder: FoldingListViewHolder? = null,
- private val _parentGeoLoc: GeoLoc,
- ) : RecyclerView.ViewHolder(_binding.root), DialogCloser {
- private lateinit var el: GeoLoc
-
- private fun bindGroup(el: GeoLoc) {
- refreshCount(el)
- _binding.textView.setTypeface(null, Typeface.BOLD)
- _binding.textView.backgroundTintList = ColorStateList.valueOf(
- colorWrapper(
- ctx,
- android.R.attr.panelColorBackground
- ).color
- ).withAlpha(64)
- }
-
- fun bind(el: GeoLoc) {
- this.el = el
- _binding.textView.text = el.fullName
- _binding.textView.backgroundTintList =
- ColorStateList.valueOf(colorWrapper(ctx, android.R.attr.colorBackground).color)
-
- if (el.children.isNotEmpty())
- bindGroup(el)
-
- refreshCheck(el)
- }
-
- fun refreshColor(colorDrawable: ColorDrawable) {
- if (Data.visits.getVisited(el) !in listOf(NO_GROUP, AUTO_GROUP)) {
- _binding.checkBox.buttonTintList =
- ColorStateList.valueOf(colorDrawable.color)
- refreshCheck(el)
- }
- }
-
- fun addListeners(el: GeoLoc, expandLambda: () -> Boolean) {
- if (el.children.isNotEmpty()) {
- _binding.textView.setOnClickListener { expandLambda() }
- }
- _binding.checkBox.setOnClickListener {
- Data.selected_geoloc = el
- if (Data.groups.size() == 1 && Settings.isSingleGroup(ctx)) {
- if (_binding.checkBox.isChecked) {
- // If one has just checked the box (assign unique group)
- Data.selected_group = Data.groups.getUniqueEntry()
- onDialogDismiss(false)
- } else {
- // If one has just unchecked the box (unassign unique group)
- Data.selected_group = null
- onDialogDismiss(true)
- }
- } else {
- Data.selected_group = null
- EditPlaceColorFragment(this).show(
- ctx.supportFragmentManager,
- "AddColorDialogFragment"
- )
- }
- _parentHolder?.refresh(_parentGeoLoc)
- }
- }
-
- override fun onDialogDismiss(clear: Boolean) {
- if (clear) {
- Data.visits.setVisited(Data.selected_geoloc, NO_GROUP)
- Data.saveData()
-
- if (_parentGeoLoc.children.all { Data.visits.getVisited(it) == NO_GROUP }) {
- Data.clearing_geoloc = _parentGeoLoc
- }
- }
- if (Data.selected_group != null && Data.selected_geoloc != null) {
- Data.visits.setVisited(Data.selected_geoloc, Data.selected_group?.key ?: NO_GROUP)
- Data.saveData()
- }
- Data.selected_geoloc?.let { refreshCheck(it) }
- Data.selected_geoloc = null
- Data.selected_group = null
- _parentHolder?.refresh(_parentGeoLoc)
- }
-
- private fun refreshCheck(geoLoc: GeoLoc) {
- _binding.checkBox.checkedState =
- if (Data.visits.getVisited(geoLoc) !in listOf(NO_GROUP, AUTO_GROUP)) {
- MaterialCheckBox.STATE_CHECKED
- } else if (geoLoc.children.isNotEmpty() &&
- geoLoc.children.all {
- Data.visits.getVisited(it) !in listOf(NO_GROUP, AUTO_GROUP)
- }
- ) {
- Data.visits.setVisited(geoLoc, AUTO_GROUP)
- MaterialCheckBox.STATE_CHECKED
- } else if (geoLoc.children.isEmpty() && Data.visits.getVisited(geoLoc) == AUTO_GROUP) {
- MaterialCheckBox.STATE_CHECKED
- } else if (geoLoc.children.any { Data.visits.getVisited(it) != NO_GROUP }) {
- Data.visits.setVisited(geoLoc, AUTO_GROUP)
- MaterialCheckBox.STATE_INDETERMINATE
- } else {
- Data.visits.setVisited(geoLoc, NO_GROUP)
- if (Data.clearing_geoloc == geoLoc) {
- Data.clearing_geoloc = null
- }
- MaterialCheckBox.STATE_UNCHECKED
- }
- Data.saveData()
-
- var col = Data.groups.getGroupFromKey(Data.visits.getVisited(geoLoc)).color
- if (Data.visits.getVisited(geoLoc) == AUTO_GROUP) {
- col = colorWrapper(ctx, android.R.attr.colorPrimary)
- } else if (col.color == Color.TRANSPARENT) {
- col = colorWrapper(ctx, android.R.attr.panelColorBackground)
- col.alpha = 64
- }
- _binding.checkBox.buttonTintList = ColorStateList.valueOf(col.color)
- }
-
- private fun refreshCount(geoLoc: GeoLoc) {
- val numerator =
- geoLoc.children.map { Data.visits.getVisited(it) != NO_GROUP }.count { it }
- val denominator = geoLoc.children.size
- _binding.count.text = Settings.getStats(ctx, numerator, denominator)
- }
-
- private fun refresh(geoLoc: GeoLoc) {
- // Refresh
- refreshCheck(geoLoc)
- refreshCount(geoLoc)
-
- // Recursively refresh parent
- _parentHolder?.refresh(_parentGeoLoc)
- }
-
- }
-}
diff --git a/app/src/main/java/net/helcel/beans/activity/adapter/GroupListAdapter.kt b/app/src/main/java/net/helcel/beans/activity/adapter/GroupListAdapter.kt
deleted file mode 100644
index 1dae265..0000000
--- a/app/src/main/java/net/helcel/beans/activity/adapter/GroupListAdapter.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-package net.helcel.beans.activity.adapter
-
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import androidx.fragment.app.DialogFragment
-import androidx.fragment.app.FragmentActivity
-import androidx.recyclerview.widget.RecyclerView
-import net.helcel.beans.activity.fragment.EditGroupAddFragment
-import net.helcel.beans.databinding.ItemListGroupBinding
-import net.helcel.beans.helper.Data
-import net.helcel.beans.helper.Groups
-import net.helcel.beans.helper.Theme.getContrastColor
-
-class GroupListAdapter(
- private val activity: FragmentActivity,
- private val selectDialog: DialogFragment,
- private val delete: Boolean = false
-) : RecyclerView.Adapter() {
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GroupViewHolder {
- val binding =
- ItemListGroupBinding.inflate(LayoutInflater.from(parent.context), parent, false)
- return GroupViewHolder(binding, activity, selectDialog)
- }
-
- override fun onBindViewHolder(holder: GroupViewHolder, pos: Int) {
- holder.bind(Data.groups.getGroupFromPos(pos))
- }
-
- override fun getItemCount(): Int {
- return Data.groups.size()
- }
-
- inner class GroupViewHolder(
- private val _binding: ItemListGroupBinding,
- private val activity: FragmentActivity,
- private val selectDialog: DialogFragment
- ) : RecyclerView.ViewHolder(_binding.root) {
- private lateinit var dialogFragment: EditGroupAddFragment
- fun bind(entry: Pair) {
- _binding.groupColor.text = entry.second.name
- dialogFragment = EditGroupAddFragment(entry.first, {
- val newEntry = Data.groups.getGroupFromKey(entry.first)
- _binding.groupColor.text = newEntry.name
- val newEntryColor = newEntry.color.color
- val contrastNewEntryColor =
- getContrastColor(newEntryColor)
- _binding.groupColor.setBackgroundColor(newEntryColor)
- _binding.groupColor.setTextColor(contrastNewEntryColor)
- _binding.name.setTextColor(contrastNewEntryColor)
- _binding.name.text = "0"
- }, {
- notifyItemRemoved(it)
- })
-
- val entryColor = entry.second.color.color
- val contrastEntryColor = getContrastColor(entryColor)
- _binding.groupColor.setBackgroundColor(entryColor)
- _binding.groupColor.setTextColor(contrastEntryColor)
- _binding.name.setTextColor(contrastEntryColor)
- _binding.name.text = Data.visits.countVisited(entry.first).toString()
-
- _binding.groupColor.setOnClickListener {
- Data.selected_group = entry.second
- selectDialog.dismiss()
- }
- if (!delete) {
- _binding.groupColor.setOnLongClickListener {
- dialogFragment.show(
- activity.supportFragmentManager,
- "AddColorDialogFragment"
- )
- true
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/helcel/beans/activity/adapter/StatsListAdapter.kt b/app/src/main/java/net/helcel/beans/activity/adapter/StatsListAdapter.kt
deleted file mode 100644
index f36efef..0000000
--- a/app/src/main/java/net/helcel/beans/activity/adapter/StatsListAdapter.kt
+++ /dev/null
@@ -1,152 +0,0 @@
-package net.helcel.beans.activity.adapter
-
-import android.content.Context
-import android.view.LayoutInflater
-import android.view.ViewGroup
-import androidx.recyclerview.widget.RecyclerView
-import com.google.android.material.textview.MaterialTextView
-import net.helcel.beans.R
-import net.helcel.beans.countries.GeoLoc
-import net.helcel.beans.countries.GeoLoc.LocType
-import net.helcel.beans.countries.World
-import net.helcel.beans.databinding.ItemListGroupBinding
-import net.helcel.beans.helper.AUTO_GROUP
-import net.helcel.beans.helper.Data
-import net.helcel.beans.helper.Groups
-import net.helcel.beans.helper.Settings
-import net.helcel.beans.helper.Theme.getContrastColor
-
-class StatsListAdapter(private val stats: RecyclerView, private val total: MaterialTextView) :
- RecyclerView.Adapter() {
- private val unit = "km²"
-
- private var locMode = LocType.WORLD
- private lateinit var ctx: Context
- private var countMode: Boolean = true
- private var initialSum: Int = 0
-
- private val wwwTotal: List = World.WWW.children.toList()
- private val countryTotal: List = World.WWW.children.flatMap { it.children }
- private val stateTotal: List =
- World.WWW.children.flatMap { it.children.flatMap { itt -> itt.children } }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StatsViewHolder {
- ctx = parent.context
- val binding =
- ItemListGroupBinding.inflate(LayoutInflater.from(ctx), parent, false)
-
- return StatsViewHolder(binding)
- }
-
- override fun onBindViewHolder(holder: StatsViewHolder, pos: Int) {
- initialSum += if (pos == itemCount - 1) {
- holder.bind(
- Pair(
- AUTO_GROUP,
- Groups.Group(AUTO_GROUP, ctx.getString(R.string.uncategorized))
- )
- )
- } else {
- holder.bind(Data.groups.getGroupFromPos(pos))
- }
- val unitNow = if (!countMode) unit else ""
- total.text = Settings.getStats(ctx, initialSum, getTotal(), unitNow)
- }
-
- override fun getItemCount(): Int {
- return Data.groups.size() + 1
- }
-
- private fun getTotal(): Int {
- return if (countMode) {
- when (locMode) {
- LocType.WORLD -> wwwTotal.size
- LocType.COUNTRY -> countryTotal.size
- LocType.STATE -> stateTotal.size
- else -> 0
- }
- } else {
- when (locMode) {
- LocType.WORLD -> wwwTotal.sumOf { it.area }
- LocType.COUNTRY -> countryTotal.sumOf { it.area }
- LocType.STATE -> stateTotal.sumOf { it.area }
- else -> 0
- }
- }
- }
-
- fun refreshMode(mode: LocType) {
- val sum = (0 until itemCount).map {
- val viewHolder = stats.findViewHolderForAdapterPosition(it) as? StatsViewHolder
- viewHolder?.refresh(mode)
- }.reduce { acc, i -> acc?.plus((i ?: 0)) }
- val unitNow = if (!countMode) unit else ""
- total.text = Settings.getStats(ctx, sum, getTotal(), unitNow)
- }
-
- fun invertCountMode() {
- countMode = !countMode
- refreshMode(locMode)
- }
-
- inner class StatsViewHolder(
- private val _binding: ItemListGroupBinding
- ) : RecyclerView.ViewHolder(_binding.root) {
-
- private lateinit var data: Pair
-
- private lateinit var wwwCount: List
- private lateinit var countryCount: List
- private lateinit var stateCount: List
-
- fun bind(entry: Pair): Int {
- data = entry
- _binding.groupColor.text = entry.second.name
-
- val entryColor = data.second.color.color
- val contrastEntryColor = getContrastColor(entryColor)
- _binding.groupColor.setBackgroundColor(entryColor)
- _binding.groupColor.setTextColor(contrastEntryColor)
- _binding.name.setTextColor(contrastEntryColor)
-
- _binding.groupColor.setOnClickListener { invertCountMode() }
- compute()
- return refresh(locMode)
- }
-
- private fun compute() {
- val visited = Data.visits.getVisitedByValue(data.first)
- wwwCount = World.WWW.children.filter { it.code in visited }
- countryCount =
- World.WWW.children.map { it.children.filter { itt -> itt.code in visited } }
- .flatten()
- stateCount =
- World.WWW.children.map { it.children.map { itt -> itt.children.filter { ittt -> ittt.code in visited } } }
- .flatten().flatten()
- }
-
- fun refresh(mode: LocType): Int {
- locMode = mode
- return if (countMode) {
- val count = when (locMode) {
- LocType.WORLD -> wwwCount.size
- LocType.COUNTRY -> countryCount.size
- LocType.STATE -> stateCount.size
- else -> -1
- }
- _binding.name.text = count.toString()
- count
- } else {
- val area = when (locMode) {
- LocType.WORLD -> wwwCount.sumOf { it.area }
- LocType.COUNTRY -> countryCount.sumOf { it.area }
- LocType.STATE -> stateCount.sumOf { it.area }
- else -> -1
- }
- _binding.name.text = ctx.getString(R.string.number_with_unit, area, unit)
- area
- }
- }
-
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/helcel/beans/activity/adapter/ViewPagerAdapter.kt b/app/src/main/java/net/helcel/beans/activity/adapter/ViewPagerAdapter.kt
deleted file mode 100644
index d5a7697..0000000
--- a/app/src/main/java/net/helcel/beans/activity/adapter/ViewPagerAdapter.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-package net.helcel.beans.activity.adapter
-
-import android.graphics.drawable.ColorDrawable
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.FragmentManager
-import androidx.lifecycle.Lifecycle
-import androidx.viewpager2.adapter.FragmentStateAdapter
-import androidx.viewpager2.widget.ViewPager2
-import net.helcel.beans.activity.fragment.EditPlaceFragment
-import kotlin.math.max
-
-class ViewPagerAdapter(
- fragmentManager: FragmentManager,
- lifecycle: Lifecycle,
- private val viewPager: ViewPager2
-) :
- FragmentStateAdapter(fragmentManager, lifecycle) {
-
- private val fragmentList: MutableList = ArrayList()
-
- fun addFragment(src: EditPlaceFragment?, target: EditPlaceFragment) {
- val idx = fragmentList.indexOf(src)
- viewPager.currentItem = max(0, idx)
- if (src != null && idx >= 0) {
- fragmentList.subList(idx + 1, fragmentList.size).clear()
- }
- fragmentList.add(target)
- notifyItemRangeChanged(max(0, idx), fragmentList.size)
- viewPager.currentItem = fragmentList.size - 1
- }
-
- override fun getItemCount(): Int {
- return fragmentList.size
- }
-
- fun backPressed(): Boolean {
- if (viewPager.currentItem == 0) {
- return false
- }
- val target = viewPager.currentItem
- while (fragmentList.size > target) {
- fragmentList.removeLast()
- notifyItemRemoved(fragmentList.size)
- }
- return true
- }
-
- fun getLabel(pos: Int): String {
- return fragmentList[pos].loc.fullName
- }
-
- override fun createFragment(position: Int): Fragment {
- return fragmentList[position]
- }
-
- fun refreshColors(colorDrawable: ColorDrawable) {
- fragmentList.forEach{ it.refreshColors(colorDrawable)}
- }
-}
-
-
diff --git a/app/src/main/java/net/helcel/beans/activity/fragment/AboutFragment.kt b/app/src/main/java/net/helcel/beans/activity/fragment/AboutFragment.kt
deleted file mode 100644
index b82b86e..0000000
--- a/app/src/main/java/net/helcel/beans/activity/fragment/AboutFragment.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package net.helcel.beans.activity.fragment
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import net.helcel.beans.databinding.FragmentAboutBinding
-
-class AboutFragment : Fragment() {
- private lateinit var _binding: FragmentAboutBinding
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- _binding = FragmentAboutBinding.inflate(inflater, container, false)
- return _binding.root
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/helcel/beans/activity/fragment/EditGroupAddFragment.kt b/app/src/main/java/net/helcel/beans/activity/fragment/EditGroupAddFragment.kt
deleted file mode 100644
index 80038db..0000000
--- a/app/src/main/java/net/helcel/beans/activity/fragment/EditGroupAddFragment.kt
+++ /dev/null
@@ -1,155 +0,0 @@
-package net.helcel.beans.activity.fragment
-
-import android.app.Dialog
-import android.graphics.Color
-import android.graphics.drawable.ColorDrawable
-import android.os.Bundle
-import android.text.Editable
-import android.text.TextWatcher
-import android.view.View
-import androidx.core.graphics.blue
-import androidx.core.graphics.green
-import androidx.core.graphics.red
-import androidx.fragment.app.DialogFragment
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import com.google.android.material.slider.Slider
-import com.google.android.material.textfield.TextInputEditText
-import net.helcel.beans.R
-import net.helcel.beans.databinding.FragmentEditGroupsAddBinding
-import net.helcel.beans.helper.Data
-import net.helcel.beans.helper.Groups
-import net.helcel.beans.helper.Theme.colorToHex6
-
-
-class EditGroupAddFragment(
- private val key: Int = 0,
- val onAddCb: (Int) -> Unit,
- val onDelCb: (Int) -> Unit,
- private val deleteEnabled: Boolean = true
-) : DialogFragment() {
-
- private lateinit var _binding: FragmentEditGroupsAddBinding
- private val grp = Data.groups.getGroupFromKey(key)
-
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val builder = MaterialAlertDialogBuilder(requireContext())
- _binding = FragmentEditGroupsAddBinding.inflate(layoutInflater)
-
- setupSlider(_binding.colorR, grp.color.color.red / 255F)
- setupSlider(_binding.colorG, grp.color.color.green / 255F)
- setupSlider(_binding.colorB, grp.color.color.blue / 255F)
- setupText(_binding.groupColor, grp)
-
- _binding.colorView.background = ColorDrawable(grp.color.color)
-
-
- if (key == 0 || !deleteEnabled) {
- _binding.btnDelete.visibility = View.INVISIBLE
- _binding.btnDelete.isEnabled = false
- }
- _binding.btnDelete.setOnClickListener {
- MaterialAlertDialogBuilder(requireActivity())
- .setMessage(R.string.delete_group)
- .setPositiveButton(android.R.string.ok) { _, _ ->
- val pos = Data.groups.findGroupPos(key)
- // Remove all countries belonging to that group
- Data.visits.deleteVisited(key)
- // Delete the group
- Data.groups.deleteGroup(key)
- Data.saveData()
- onDelCb(pos)
- dialog?.dismiss()
- }
- .setNegativeButton(android.R.string.cancel) { _, _ -> }
- .show()
- }
-
- _binding.btnOk.setOnClickListener {
- val name = _binding.groupName.text.toString()
- val color = _binding.groupColor.text.toString()
- val key = (if (key != 0) key else Data.groups.genKey())
- Data.groups.setGroup(key, name, ColorDrawable(Color.parseColor("#$color")))
- Data.saveData()
- onAddCb(key)
- dialog?.dismiss()
- }
-
- _binding.btnCancel.setOnClickListener {
- dialog?.cancel()
- }
-
- _binding.groupName.setText(grp.name)
- builder.setView(_binding.root)
- return builder.create()
- }
-
- private fun setupText(s: TextInputEditText, grp: Groups.Group?) {
- s.setText(colorToHex6(ColorDrawable(grp?.color?.color ?: 0)).substring(1))
- s.addTextChangedListener(
- EditTextListener(
- _binding.colorR,
- _binding.colorG,
- _binding.colorB,
- _binding.groupColor,
- _binding.colorView
- )
- )
- }
-
- private fun setupSlider(s: Slider, v: Float) {
- s.valueFrom = 0F
- s.valueTo = 1F
- s.value = v
- s.addOnChangeListener(
- SliderOnChangeListener(
- _binding.colorR,
- _binding.colorG,
- _binding.colorB,
- _binding.groupColor,
- _binding.colorView
- )
- )
- }
-
-}
-
-
-private class EditTextListener(
- private val colorEditR: Slider,
- private val colorEditG: Slider,
- private val colorEditB: Slider,
- private val colorEditText: TextInputEditText,
- private val colorView: View
-) : TextWatcher {
-
- override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
- override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
- override fun afterTextChanged(s: Editable?) {
- val col: Color
- try {
- col = Color.valueOf(Color.parseColor("#${colorEditText.text}"))
- } catch (e: Exception) {
- return
- }
-
- colorEditR.value = col.red()
- colorEditG.value = col.green()
- colorEditB.value = col.blue()
- colorView.background = ColorDrawable(col.toArgb())
- }
-}
-
-private class SliderOnChangeListener(
- private val colorEditR: Slider,
- private val colorEditG: Slider,
- private val colorEditB: Slider,
- private val colorEditText: TextInputEditText,
- private val colorView: View
-) : Slider.OnChangeListener {
- override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {
- val rgb =
- ColorDrawable(Color.argb(1F, colorEditR.value, colorEditG.value, colorEditB.value))
- colorEditText.setText(colorToHex6(rgb).substring(1))
- colorView.background = rgb
- }
-}
diff --git a/app/src/main/java/net/helcel/beans/activity/fragment/EditPlaceColorFragment.kt b/app/src/main/java/net/helcel/beans/activity/fragment/EditPlaceColorFragment.kt
deleted file mode 100644
index 58bb328..0000000
--- a/app/src/main/java/net/helcel/beans/activity/fragment/EditPlaceColorFragment.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-package net.helcel.beans.activity.fragment
-
-import android.app.Dialog
-import android.content.DialogInterface
-import android.os.Bundle
-import android.view.View
-import androidx.fragment.app.DialogFragment
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import net.helcel.beans.R
-import net.helcel.beans.activity.adapter.GroupListAdapter
-import net.helcel.beans.databinding.FragmentEditPlacesColorsBinding
-import net.helcel.beans.helper.Data
-import net.helcel.beans.helper.DialogCloser
-
-
-class EditPlaceColorFragment(private val parent: DialogCloser, private val delete: Boolean = false) :
- DialogFragment() {
-
- private lateinit var _binding: FragmentEditPlacesColorsBinding
- private lateinit var listAdapt: GroupListAdapter
- private var clear: Boolean = false
-
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val ctx = requireContext()
- val builder = MaterialAlertDialogBuilder(ctx)
- _binding = FragmentEditPlacesColorsBinding.inflate(layoutInflater)
- _binding.btnAdd.setOnClickListener {
- EditGroupAddFragment(0, {
- listAdapt.notifyItemInserted(Data.groups.findGroupPos(it))
- }, {}).show(requireActivity().supportFragmentManager, "AddColorDialogFragment")
- }
- _binding.btnClear.setOnClickListener {
- clear = true
- dialog?.dismiss()
- }
-
- val dialog = builder.setView(_binding.root).create()
- listAdapt = GroupListAdapter(requireActivity(), this, delete)
- _binding.groupsColor.layoutManager =
- LinearLayoutManager(ctx, RecyclerView.VERTICAL, false)
- _binding.groupsColor.adapter = listAdapt
-
- if (delete) {
- _binding.btnAdd.visibility = View.GONE
- _binding.btnClear.text = ctx.getString(R.string.cancel)
- _binding.warningText.text = ctx.getString(R.string.select_group)
- } else {
- _binding.warningText.text = ctx.getString(R.string.edit_group)
- }
-
- return dialog
- }
-
- override fun onDismiss(dialog: DialogInterface) {
- super.onDismiss(dialog)
- parent.onDialogDismiss(clear)
- }
-}
diff --git a/app/src/main/java/net/helcel/beans/activity/fragment/EditPlaceFragment.kt b/app/src/main/java/net/helcel/beans/activity/fragment/EditPlaceFragment.kt
deleted file mode 100644
index 82855ac..0000000
--- a/app/src/main/java/net/helcel/beans/activity/fragment/EditPlaceFragment.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-package net.helcel.beans.activity.fragment
-
-import android.graphics.drawable.ColorDrawable
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import net.helcel.beans.activity.adapter.GeolocListAdapter
-import net.helcel.beans.activity.adapter.GeolocListAdapter.FoldingListViewHolder
-import net.helcel.beans.activity.adapter.ViewPagerAdapter
-import net.helcel.beans.countries.GeoLoc
-import net.helcel.beans.databinding.FragmentEditPlacesBinding
-
-class EditPlaceFragment(val loc: GeoLoc, private val pager: ViewPagerAdapter, private val holder: FoldingListViewHolder? = null) : Fragment() {
- private lateinit var _binding: FragmentEditPlacesBinding
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- _binding = FragmentEditPlacesBinding.inflate(inflater, container, false)
-
- _binding.list.setItemViewCacheSize(5)
- _binding.list.setHasFixedSize(true)
- _binding.list.layoutManager =
- LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
- _binding.list.adapter = GeolocListAdapter(this, loc, pager, holder)
- return _binding.root
- }
-
- fun refreshColors(colorDrawable: ColorDrawable) {
- (_binding.list.adapter as GeolocListAdapter?)?.refreshColors(colorDrawable)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/helcel/beans/activity/fragment/LicenseFragment.kt b/app/src/main/java/net/helcel/beans/activity/fragment/LicenseFragment.kt
deleted file mode 100644
index d7d64fe..0000000
--- a/app/src/main/java/net/helcel/beans/activity/fragment/LicenseFragment.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package net.helcel.beans.activity.fragment
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import com.mikepenz.aboutlibraries.LibsBuilder
-import net.helcel.beans.R
-import net.helcel.beans.databinding.FragmentLicenseBinding
-
-class LicenseFragment : Fragment() {
- private lateinit var _binding: FragmentLicenseBinding
-
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- _binding = FragmentLicenseBinding.inflate(inflater, container, false)
-
- val librariesFragment = LibsBuilder()
- .withLicenseShown(true)
- .supportFragment()
-
- requireActivity().supportFragmentManager.beginTransaction()
- .replace(R.id.license_fragment_view, librariesFragment)
- .commit()
-
- return _binding.root
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/helcel/beans/activity/fragment/SettingsFragment.kt b/app/src/main/java/net/helcel/beans/activity/fragment/SettingsFragment.kt
deleted file mode 100644
index 3606407..0000000
--- a/app/src/main/java/net/helcel/beans/activity/fragment/SettingsFragment.kt
+++ /dev/null
@@ -1,142 +0,0 @@
-package net.helcel.beans.activity.fragment
-
-import android.content.Context
-import android.os.Bundle
-import androidx.appcompat.app.AppCompatDelegate
-import androidx.preference.Preference
-import androidx.preference.PreferenceFragmentCompat
-import androidx.preference.PreferenceManager
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import net.helcel.beans.R
-import net.helcel.beans.countries.GeoLocImporter
-import net.helcel.beans.helper.Data
-import net.helcel.beans.helper.DialogCloser
-import net.helcel.beans.helper.Settings
-
-
-class SettingsFragment : PreferenceFragmentCompat(), DialogCloser {
- private var savedInstanceState: Bundle? = null
- private var rootKey: String? = null
-
- override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
- this.savedInstanceState = savedInstanceState
- this.rootKey = rootKey
-
- setPreferencesFromResource(R.xml.fragment_settings, rootKey)
- val ctx = requireContext()
-
- // Select Light/Dark/System Mode
- findPreference(getString(R.string.key_theme))?.setOnPreferenceChangeListener { _, key ->
- setTheme(ctx, key as String)
- }
-
- // Select map projection
- findPreference(getString(R.string.key_projection))?.setOnPreferenceChangeListener { _, key ->
- Settings.refreshProjection()
- }
-
- // Toggle groups
- findPreference(getString(R.string.key_group))?.setOnPreferenceChangeListener { _, key ->
- if (key as String == ctx.getString(R.string.off)) {
- val fragment = EditPlaceColorFragment(this, true)
- fragment.show(
- this.parentFragmentManager,
- "AddColorDialogFragment"
- )
- false
- } else {
- true
- }
- }
-
- // Toggle regional geolocs
- findPreference(getString(R.string.key_regional))?.setOnPreferenceChangeListener { _, key ->
- when (key as String) {
- ctx.getString(R.string.off) -> {
- MaterialAlertDialogBuilder(requireActivity())
- .setMessage(R.string.delete_regions)
- .setPositiveButton(android.R.string.ok) { _, _ ->
- GeoLocImporter.clearStates()
- PreferenceManager.getDefaultSharedPreferences(ctx).edit().putString(
- ctx.getString(R.string.key_regional),
- ctx.getString(R.string.off)
- ).apply()
- refreshPreferences()
- }
- .setNegativeButton(android.R.string.cancel) { _, _ -> }
- .show()
- false
- }
-
- ctx.getString(R.string.on) -> {
- GeoLocImporter.importStates(ctx, true)
- true
- }
-
- else -> false
- }
- }
-
-
- // Open license fragment
- findPreference(getString(R.string.licenses))?.setOnPreferenceClickListener {
- requireActivity().supportFragmentManager.beginTransaction()
- .replace(R.id.fragment_view, LicenseFragment(), getString(R.string.licenses))
- .commit()
- true
- }
-
- // Open about fragment
- findPreference(getString(R.string.about))?.setOnPreferenceClickListener {
- requireActivity().supportFragmentManager.beginTransaction()
- .replace(R.id.fragment_view, AboutFragment(), getString(R.string.about))
- .commit()
- true
- }
-
- }
-
- companion object {
- fun setTheme(ctx: Context, key: String?): Boolean {
- AppCompatDelegate.setDefaultNightMode(
- when (key) {
- ctx.getString(R.string.system) -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
- ctx.getString(R.string.light) -> AppCompatDelegate.MODE_NIGHT_NO
- ctx.getString(R.string.dark) -> AppCompatDelegate.MODE_NIGHT_YES
- else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
- }
- )
- return true
- }
- }
-
- override fun onDialogDismiss(clear: Boolean) {
- // When turning groups off, select one group to keep and reassign everything
- Data.selected_group?.let { selectedGroup ->
- // Reassign all visited that are not to selectedGroup to selectedGroup
- Data.visits.reassignAllVisitedToGroup(selectedGroup.key)
-
- // Delete all groups that are not selectedGroup
- Data.groups.deleteAllExcept(selectedGroup.key)
-
- // Save and clear global variables
- Data.saveData()
- Data.selected_geoloc = null
- Data.selected_group = null
-
- // Actually change preference
- val ctx = requireContext()
- val sp = PreferenceManager.getDefaultSharedPreferences(ctx)
- sp.edit().putString(ctx.getString(R.string.key_group), ctx.getString(R.string.off))
- .apply()
-
- // Refresh entire preference fragment to reflect changes
- refreshPreferences()
- }
- }
-
- private fun refreshPreferences() {
- preferenceScreen.removeAll()
- onCreatePreferences(savedInstanceState, rootKey)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/helcel/beans/activity/sub/AboutScreen.kt b/app/src/main/java/net/helcel/beans/activity/sub/AboutScreen.kt
new file mode 100644
index 0000000..9c030f7
--- /dev/null
+++ b/app/src/main/java/net/helcel/beans/activity/sub/AboutScreen.kt
@@ -0,0 +1,78 @@
+package net.helcel.beans.activity.sub
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalUriHandler
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import net.helcel.beans.R
+import net.helcel.beans.BuildConfig
+
+@Preview
+@Composable
+fun AboutScreen(
+ modifier: Modifier = Modifier
+) {
+ Column(
+ modifier = modifier
+ .fillMaxSize()
+ .padding(top = 20.dp).background(MaterialTheme.colors.background),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.ic_launcher_foreground),
+ contentDescription = "Logo",
+ modifier = Modifier
+ .size(300.dp)
+ )
+
+ Text(
+ text = BuildConfig.APP_NAME,
+ fontSize = 30.sp,
+ color = MaterialTheme.colors.onBackground,
+ fontWeight = FontWeight.Bold,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.padding(vertical = 15.dp, horizontal = 10.dp)
+ )
+
+ Text(
+ text = BuildConfig.VERSION_NAME,
+ fontSize = 25.sp,
+ color = MaterialTheme.colors.onBackground,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.padding(vertical = 15.dp, horizontal = 10.dp)
+ )
+
+ Text(
+ text = stringResource(R.string.beans_is_foss),
+ textAlign = TextAlign.Center,
+ color = MaterialTheme.colors.onBackground,
+ modifier = Modifier.padding(vertical = 15.dp, horizontal = 10.dp)
+ )
+
+ val uriHandler = LocalUriHandler.current
+ val uri = stringResource(R.string.beans_repo_uri)
+ Text(
+ text = stringResource(id = R.string.beans_repo,uri),
+ textAlign = TextAlign.Center,
+ color = MaterialTheme.colors.onBackground,
+ modifier = Modifier
+ .clickable {
+ uriHandler.openUri(uri)
+ }
+ .padding(vertical = 15.dp, horizontal = 10.dp)
+ )
+ }
+}
diff --git a/app/src/main/java/net/helcel/beans/activity/sub/EditGroupAddDialog.kt b/app/src/main/java/net/helcel/beans/activity/sub/EditGroupAddDialog.kt
new file mode 100644
index 0000000..78b5c68
--- /dev/null
+++ b/app/src/main/java/net/helcel/beans/activity/sub/EditGroupAddDialog.kt
@@ -0,0 +1,222 @@
+package net.helcel.beans.activity.sub
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Button
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Slider
+import androidx.compose.material.SliderDefaults
+import androidx.compose.material.Text
+import androidx.compose.material.TextButton
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.OutlinedTextFieldDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.window.Dialog
+import net.helcel.beans.helper.Data
+import net.helcel.beans.helper.Theme.colorToHex6
+import androidx.core.graphics.drawable.toDrawable
+import androidx.core.graphics.toColorInt
+import net.helcel.beans.R
+
+
+@Preview
+@Composable
+fun EditGroupPreview(){
+ EditGroupDialog(0,true,{},{},{})
+}
+@Composable
+fun EditGroupDialog(
+ key: Int = 0,
+ deleteEnabled: Boolean = true,
+ onAddCb: (Int) -> Unit,
+ onDelCb: (Int) -> Unit,
+ onDismiss: () -> Unit
+) {
+ val group by remember { mutableStateOf(Data.groups.getGroupFromKey(key)) }
+ var name by remember { mutableStateOf(group.name) }
+ var colorHex by remember {
+ mutableStateOf(colorToHex6(group.color.color.toDrawable()).substring(1))
+ }
+
+ // Convert hex to Color safely
+ var color = remember {try {
+ Color("#$colorHex".toColorInt())
+ } catch (_: Exception) {
+ Color.Gray
+ }}
+ var r by remember { mutableIntStateOf((color.red *255).toInt()) }
+ var g by remember { mutableIntStateOf((color.green*255).toInt()) }
+ var b by remember { mutableIntStateOf((color.blue*255).toInt()) }
+
+ fun updateHexFromSliders() {
+ val newColor = Color(r, g, b)
+ colorHex = colorToHex6(newColor.toArgb().toDrawable()).substring(1)
+ color = newColor
+ }
+ Dialog(
+ onDismissRequest = onDismiss,
+ content = {
+ Column(
+ modifier = Modifier
+ .background(
+ MaterialTheme.colors.background,
+ RoundedCornerShape(corner = CornerSize(16.dp))
+ )
+ .padding(16.dp),
+
+ ) {
+ Text(
+ color = MaterialTheme.colors.onBackground,
+ style = MaterialTheme.typography.h6,
+ text = if (key == 0) stringResource(R.string.action_add)
+ else stringResource(R.string.action_edit),
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+ Column(
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ // Group name
+ OutlinedTextField(
+ value = name,
+ onValueChange = { it: String -> name = it },
+ modifier = Modifier.fillMaxWidth(),
+ placeholder = { Text(stringResource(R.string.name)) },
+ colors = OutlinedTextFieldDefaults.colors(
+ unfocusedTextColor = MaterialTheme.colors.onBackground,
+ focusedTextColor = MaterialTheme.colors.onBackground,
+ ),
+ )
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+
+ // Color preview
+ Box(
+ modifier = Modifier
+ .size(96.dp, (96).dp)
+ .clip(RoundedCornerShape(8.dp))
+ .background(color),
+ propagateMinConstraints = true,
+
+ content = {}
+ )
+ Column {
+ ColorSlider(
+ r.toFloat(),
+ { r = it.toInt(); updateHexFromSliders() },
+ Color(255, 0, 0)
+ )
+ ColorSlider(
+ g.toFloat(),
+ { g = it.toInt(); updateHexFromSliders() },
+ Color(0, 255, 0)
+ )
+ ColorSlider(
+ b.toFloat(),
+ { b = it.toInt(); updateHexFromSliders() },
+ Color(0, 0, 255)
+ )
+ }
+ }
+ // Hex input
+ OutlinedTextField(
+ value = colorHex,
+ onValueChange = { n:String->
+ colorHex = n.filter { it.isLetterOrDigit() }
+ },
+ label = { Text(text="Color (hex)", color=MaterialTheme.colors.onBackground) },
+ singleLine = true,
+ textStyle = TextStyle(
+ fontSize = 12.sp
+ ),
+ modifier = Modifier.fillMaxWidth(),
+ colors = OutlinedTextFieldDefaults.colors(
+ unfocusedTextColor =MaterialTheme.colors.onBackground,
+ focusedTextColor = MaterialTheme.colors.onBackground,
+ ),
+
+ )
+ }
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.End
+ ) {
+ Button(onClick = {
+ val newKey = if (key != 0) key else Data.groups.genKey()
+ Data.groups.setGroup(
+ newKey,
+ name,
+ "#$colorHex".toColorInt().toDrawable()
+ )
+ Data.saveData()
+ onAddCb(newKey)
+ onDismiss()
+ }, ) {
+ Text("OK")
+ }
+ if (key != 0 && deleteEnabled) {
+ TextButton(onClick = {
+ val pos = Data.groups.findGroupPos(key)
+ Data.visits.deleteVisited(key)
+ Data.groups.deleteGroup(key)
+ Data.saveData()
+ onDelCb(pos)
+ onDismiss()
+ }) {
+ Text("Delete")
+ }
+ }
+ TextButton(onClick = { onDismiss() }) {
+ Text("Cancel")
+ }
+
+ }
+ }
+ },
+ )
+}
+
+@Composable
+fun ColorSlider(v: Float, onChange:(Float)->Unit, c:Color ){
+ Slider(
+ value = v,
+ onValueChange = onChange,
+ valueRange = 0f..255f,
+ steps = 255,
+ modifier = Modifier.height(32.dp),
+ colors = SliderDefaults.colors(
+ thumbColor = c,
+ activeTickColor = c,
+ inactiveTickColor = MaterialTheme.colors.onBackground,
+ )
+ )
+}
diff --git a/app/src/main/java/net/helcel/beans/activity/sub/EditPlaceColorDialog.kt b/app/src/main/java/net/helcel/beans/activity/sub/EditPlaceColorDialog.kt
new file mode 100644
index 0000000..2546515
--- /dev/null
+++ b/app/src/main/java/net/helcel/beans/activity/sub/EditPlaceColorDialog.kt
@@ -0,0 +1,193 @@
+package net.helcel.beans.activity.sub
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.combinedClickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Button
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.material.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import net.helcel.beans.R
+import net.helcel.beans.helper.Data
+import net.helcel.beans.helper.Groups
+import androidx.core.graphics.drawable.toDrawable
+import net.helcel.beans.activity.SysTheme
+
+@Composable
+fun EditPlaceDialog(delete: Boolean, onDialogDismiss: (Boolean)->Unit){
+ SysTheme {
+ var showEditGroupDialog by remember { mutableStateOf(false) }
+ var showEditPlaceColorDialog by remember { mutableStateOf(true) }
+ var showSelectedKey by remember { mutableIntStateOf(-1) }
+ var showDelete by remember { mutableStateOf(false) }
+ if (showEditGroupDialog)
+ EditGroupDialog(
+ key = showSelectedKey,
+ deleteEnabled = showDelete,
+ onAddCb = { },
+ onDelCb = {
+
+ },
+ onDismiss = {
+ showEditGroupDialog = false
+ },
+ )
+ if (showEditPlaceColorDialog)
+ EditPlaceColorDialog(
+ delete,
+ onAdd = {
+ showSelectedKey = it
+ showDelete = false
+ showEditGroupDialog = true
+ },
+ onDelete = {
+ showSelectedKey = it
+ showDelete = true
+ showEditGroupDialog = true
+ },
+ onClear = {
+ showEditPlaceColorDialog = false
+ onDialogDismiss(true)
+ },
+ onDismiss = {
+ showEditPlaceColorDialog = false
+ onDialogDismiss(false)
+ }
+ )
+ }
+}
+
+@Preview
+@Composable
+fun GroupListPreview() {
+ Data.groups = Groups(0, HashMap())
+ Data.groups.setGroup(0, "Testing", Color.Red.toArgb().toDrawable())
+ Data.groups.setGroup(1, "Testing", Color.Blue.toArgb().toDrawable())
+ EditPlaceColorDialog(false,{},{},{},{})
+}
+
+@Composable
+fun EditPlaceColorDialog(
+ deleteMode: Boolean = false,
+ onAdd: (Int) -> Unit = {},
+ onDelete: (Int) -> Unit= {},
+ onClear: () -> Unit= {},
+ onDismiss: () -> Unit= {},
+) {
+ val groups by Data.groups.groupsFlow.collectAsState()
+
+ Dialog(
+ onDismissRequest = onDismiss,
+ content = {
+ Column(
+ modifier = Modifier
+ .background(
+ MaterialTheme.colors.background,
+ RoundedCornerShape(corner = CornerSize(16.dp)))
+ .padding(16.dp)
+ ,
+
+ ) {
+ Text(
+ style = MaterialTheme.typography.h6,
+ color=MaterialTheme.colors.onBackground,
+ text = if (deleteMode) stringResource(R.string.select_group)
+ else stringResource(R.string.edit_group)
+ )
+ Text(
+ style = MaterialTheme.typography.caption,
+ color=MaterialTheme.colors.onBackground,
+ text = if (deleteMode) stringResource(R.string.select_group_sub)
+ else stringResource(R.string.edit_group_sub)
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .heightIn(max = 300.dp) // cap dialog growth
+ ) {
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxWidth()
+ //.weight(1f)
+ ) {
+ items(groups, key = { it.key }) { group ->
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .combinedClickable(
+ onClick = { Data.selected_group = group; onDismiss() },
+ onLongClick = { onDelete(group.key) })
+ .background(
+ Color(88, 88, 88, 88),
+ RoundedCornerShape(corner = CornerSize(16.dp))
+ )
+ .padding(8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+
+ ) {
+ Box(
+ modifier = Modifier
+ .size(24.dp)
+ .background(Color(group.color.color), CircleShape)
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ Text(color=MaterialTheme.colors.onBackground,text=group.name)
+ }
+ Spacer(modifier = Modifier.height(8.dp))
+ }
+ }
+ }
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.End) {
+ if (!deleteMode) {
+ Button(onClick = { onAdd(0) }) {
+ Text(stringResource(R.string.action_add))
+ }
+ }
+ TextButton(onClick = {
+ if (deleteMode) onDismiss() else onClear()
+ }) {
+ Text(
+ text = if (deleteMode) stringResource(R.string.cancel)
+ else stringResource(R.string.action_clear)
+ )
+ }
+ }
+ }
+ },
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/helcel/beans/activity/sub/EditPlaceScreen.kt b/app/src/main/java/net/helcel/beans/activity/sub/EditPlaceScreen.kt
new file mode 100644
index 0000000..8f16cc1
--- /dev/null
+++ b/app/src/main/java/net/helcel/beans/activity/sub/EditPlaceScreen.kt
@@ -0,0 +1,218 @@
+package net.helcel.beans.activity.sub
+
+
+import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.CheckboxDefaults
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.ScrollableTabRow
+import androidx.compose.material.Tab
+import androidx.compose.material.Text
+import androidx.compose.material.TriStateCheckbox
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.SnapshotStateList
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.state.ToggleableState
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import net.helcel.beans.countries.GeoLoc
+import net.helcel.beans.countries.Group
+import net.helcel.beans.countries.World
+import net.helcel.beans.helper.AUTO_GROUP
+import net.helcel.beans.helper.Data
+import net.helcel.beans.helper.NO_GROUP
+import net.helcel.beans.helper.Settings
+import kotlin.math.min
+
+@Preview
+@Composable
+fun EditPlaceScreenPreview(){
+ EditPlaceScreen(Group.EEE)
+}
+
+fun syncVisited(loc: GeoLoc?=World.WWW){
+ loc?.children?.forEach { tt ->
+ tt.children.forEach {itc->
+ if(Data.visits.getVisited(itc) in listOf(AUTO_GROUP,NO_GROUP)) {
+ if(itc.children.any { itcc -> Data.visits.getVisited(itcc) != NO_GROUP })
+ Data.visits.setVisited(itc, AUTO_GROUP)
+ else
+ Data.visits.setVisited(itc, NO_GROUP)
+ }
+ }
+ if(Data.visits.getVisited(tt) in listOf(AUTO_GROUP,NO_GROUP)) {
+ if(tt.children.any { itc -> Data.visits.getVisited(itc) != NO_GROUP })
+ Data.visits.setVisited(tt, AUTO_GROUP)
+ else
+ Data.visits.setVisited(tt, NO_GROUP)
+ }
+ }
+}
+
+@Composable
+fun EditPlaceScreen(loc: GeoLoc, onExit:()->Unit={}) {
+ var showEdit by remember { mutableStateOf(false) }
+ val tabs : SnapshotStateList = remember { mutableStateListOf(loc) }
+ val ctx = LocalContext.current
+ var selectedTab by remember { mutableIntStateOf(0) }
+
+ LaunchedEffect(tabs.size) {
+ selectedTab = tabs.lastIndex
+ }
+ SideEffect {
+ syncVisited()
+ }
+ BackHandler {
+ if (tabs.size > 1) tabs.removeAt(tabs.lastIndex)
+ else onExit()
+ }
+ if(showEdit)
+ EditPlaceDialog(false) {
+ showEdit = false
+ if (it) {
+ Data.visits.setVisited(Data.selected_geoloc, NO_GROUP)
+ Data.saveData()
+
+ if (Data.selected_geoloc!=null && Data.selected_geoloc!!.children.any { itc-> Data.visits.getVisited(itc) != NO_GROUP }) {
+ Data.clearing_geoloc = Data.selected_geoloc
+ }
+ }
+ if (Data.selected_group != null && Data.selected_geoloc != null) {
+ Data.visits.setVisited(Data.selected_geoloc, Data.selected_group!!.key)
+ Data.saveData()
+ }
+ Data.selected_geoloc = null
+ Data.selected_group = null
+ }
+
+ Column {
+ val currentTab = tabs.getOrNull(selectedTab) ?: return@Column
+
+ ScrollableTabRow(
+ selectedTabIndex = min(tabs.lastIndex,selectedTab)
+ ) {
+ tabs.forEachIndexed { index, tab ->
+ Tab(
+ selected = selectedTab == index,
+ onClick = {
+ while (tabs.size > index + 1)
+ tabs.removeAt(tabs.lastIndex)
+ },
+ text = { Text(tab.fullName) }
+ )
+ }
+ }
+
+ LazyColumn(
+ modifier = Modifier.fillMaxSize()
+ ) {
+ items(currentTab.children.toList(), key= {it.code}) { loc ->
+
+ GeoLocRow(loc, {
+ if (loc.children.isNotEmpty()){
+ tabs.add(loc)
+ }
+ }, {
+ Data.selected_geoloc = loc
+ if (Data.groups.size() == 1 && Settings.isSingleGroup(ctx)) {
+ Data.visits.setVisited(Data.selected_geoloc,
+ if (it != ToggleableState.On) Data.groups.getUniqueEntry()!!.key
+ else if(Data.selected_geoloc?.children?.any{ itc->
+ Data.visits.getVisited(itc)!= NO_GROUP } == true) AUTO_GROUP
+ else NO_GROUP
+ )
+ Data.selected_group = null
+ } else {
+ Data.selected_group = null
+ showEdit=true
+ }
+
+ })
+
+ }
+ }
+
+ }
+}
+
+
+@Composable
+fun GeoLocRow(
+ loc: GeoLoc,
+ onClick: () -> Unit,
+ onCheckedChange: (ToggleableState) -> Unit
+) {
+ val visits by Data.visits.visitsFlow.collectAsState()
+ val checked by remember(visits, loc) {
+ derivedStateOf {
+ when (visits.getOrElse(loc.code) { NO_GROUP }) {
+ NO_GROUP -> ToggleableState.Off
+ AUTO_GROUP -> ToggleableState.Indeterminate
+ else -> ToggleableState.On
+ }
+ }
+ }
+
+ val color = if (Data.visits.getVisited(loc) !in listOf(NO_GROUP, AUTO_GROUP))
+ Color(Data.groups.getGroupFromKey(Data.visits.getVisited(loc)).color.color)
+ else MaterialTheme.colors.onBackground
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(50.dp)
+ .clickable(onClick = onClick) // whole row clickable
+ .padding(horizontal = 20.dp, vertical = 4.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = loc.fullName,
+ style = MaterialTheme.typography.body2,
+ color = MaterialTheme.colors.onBackground,
+ modifier = Modifier.weight(1f)
+ )
+
+ Text(
+ text = "",//loc.children.size.toString(),
+ style = MaterialTheme.typography.body2,
+ modifier = Modifier.padding(end = 16.dp)
+ )
+
+ TriStateCheckbox(
+ state = checked,
+ onClick= { onCheckedChange(checked) },
+ colors = CheckboxDefaults.colors(
+ checkedColor = color,
+ ),
+
+ modifier = Modifier.size(24.dp)
+ )
+ }
+ Spacer(modifier = Modifier
+ .height(2.dp)
+ .fillMaxWidth()
+ .background(MaterialTheme.colors.onBackground))
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/helcel/beans/activity/sub/LicenseScreen.kt b/app/src/main/java/net/helcel/beans/activity/sub/LicenseScreen.kt
new file mode 100644
index 0000000..36b1b71
--- /dev/null
+++ b/app/src/main/java/net/helcel/beans/activity/sub/LicenseScreen.kt
@@ -0,0 +1,43 @@
+package net.helcel.beans.activity.sub
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.mikepenz.aboutlibraries.ui.compose.DefaultChipColors
+import com.mikepenz.aboutlibraries.ui.compose.DefaultLibraryColors
+import com.mikepenz.aboutlibraries.ui.compose.android.rememberLibraries
+import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
+import net.helcel.beans.R
+import net.helcel.beans.activity.SysTheme
+
+
+@Preview
+@Composable
+fun LicenseScreen() {
+ val libraries = rememberLibraries(R.raw.aboutlibraries)
+ SysTheme {
+ LibrariesContainer(
+ libraries = libraries.value,
+ modifier = Modifier.fillMaxSize(),
+ colors = DefaultLibraryColors(
+ backgroundColor = MaterialTheme.colors.background,
+ contentColor = MaterialTheme.colors.onBackground,
+ licenseChipColors = DefaultChipColors(
+ containerColor = MaterialTheme.colors.primary,
+ contentColor = MaterialTheme.colors.onPrimary,
+ ),
+ versionChipColors = DefaultChipColors(
+ containerColor = MaterialTheme.colors.secondary,
+ contentColor = MaterialTheme.colors.onSecondary,
+ ),
+ fundingChipColors = DefaultChipColors(
+ containerColor = MaterialTheme.colors.secondary,
+ contentColor = MaterialTheme.colors.onSecondary,
+ ),
+ dialogConfirmButtonColor = MaterialTheme.colors.primary,
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/helcel/beans/countries/Country.kt b/app/src/main/java/net/helcel/beans/countries/Country.kt
index 0b39838..39a39f7 100644
--- a/app/src/main/java/net/helcel/beans/countries/Country.kt
+++ b/app/src/main/java/net/helcel/beans/countries/Country.kt
@@ -7,8 +7,8 @@ enum class Country(
ATA("Antarctica", 14000000),
// HKG("Hong Kong", 1104),
-// MAC("Macao", 32),
-// ANT("Netherlands Antilles", 800),
+ // MAC("Macao", 32),
+ // ANT("Netherlands Antilles", 800),
AFG("Afghanistan", 645487),
XAD("Akrotiri and Dhekelia", 234),
ALA("Åland", 1483),
diff --git a/app/src/main/java/net/helcel/beans/countries/GeoLoc.kt b/app/src/main/java/net/helcel/beans/countries/GeoLoc.kt
index 941c96d..eddb270 100644
--- a/app/src/main/java/net/helcel/beans/countries/GeoLoc.kt
+++ b/app/src/main/java/net/helcel/beans/countries/GeoLoc.kt
@@ -12,4 +12,4 @@ interface GeoLoc {
val type: LocType
val children: Set
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/helcel/beans/helper/Data.kt b/app/src/main/java/net/helcel/beans/helper/Data.kt
index bd59bbc..38415a1 100644
--- a/app/src/main/java/net/helcel/beans/helper/Data.kt
+++ b/app/src/main/java/net/helcel/beans/helper/Data.kt
@@ -1,13 +1,21 @@
package net.helcel.beans.helper
+import android.content.ContentValues
import android.content.Context
import android.content.SharedPreferences
-import android.graphics.drawable.ColorDrawable
+import android.net.Uri
+import android.os.Build
+import android.os.Environment
+import android.provider.MediaStore
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
import net.helcel.beans.R
import net.helcel.beans.countries.GeoLoc
import java.util.HashMap
+import androidx.core.graphics.drawable.toDrawable
+import androidx.core.content.edit
+import android.content.Intent
+import java.io.File
object Data {
var visits : Visits = Visits(0, HashMap())
@@ -33,7 +41,8 @@ object Data {
// Add default group "Visited" with app's color if there is no group already
if (groups.size() == 0) {
- groups.setGroup(DEFAULT_GROUP, "Visited", ColorDrawable(ContextCompat.getColor(ctx, R.color.blue)))
+ groups.setGroup(DEFAULT_GROUP, "Visited",
+ ContextCompat.getColor(ctx, R.color.blue).toDrawable())
saveData()
}
}
@@ -41,9 +50,73 @@ object Data {
fun saveData() {
if(groups.id != visits.id) return
val id = groups.id
- val editor = sharedPreferences.edit()
- editor.putString("groups_$id", groupsSerial.writeTo(groups))
- editor.putString("visits_$id", visitsSerial.writeTo(visits))
- editor.apply()
+ sharedPreferences.edit {
+ putString("groups_$id", groupsSerial.writeTo(groups))
+ putString("visits_$id", visitsSerial.writeTo(visits))
+ }
+ }
+
+ fun exportData(ctx: Context, filepath: Uri){
+ val groupsJson = groupsSerial.writeTo(groups)
+ val visitsJson = visitsSerial.writeTo(visits)
+ val outputStream = ctx.contentResolver.openOutputStream(filepath)
+ outputStream?.write(
+ buildString {
+ append(groupsJson)
+ append("\n---\n") // optional separator
+ append(visitsJson)
+ }.toByteArray())
+ outputStream?.flush()
+ outputStream?.close()
+ }
+
+
+ fun importData(ctx: Context, filePath: Uri) {
+ val inputStream = ctx.contentResolver.openInputStream(filePath)
+ val data = inputStream?.bufferedReader().use { it?.readText() }
+ if(data==null) return
+ val lines = data.split("\n---\n")
+ val groupsJson = lines[0]
+ val visitsJson = lines[1]
+
+ groups = if(groupsJson.isNotEmpty()) groupsSerial.readFrom(groupsJson.byteInputStream()) else groupsSerial.defaultValue
+ visits = if(visitsJson.isNotEmpty()) visitsSerial.readFrom(visitsJson.byteInputStream()) else visitsSerial.defaultValue
+
+ // Add default group "Visited" with app's color if there is no group already
+ if (groups.size() == 0) {
+ groups.setGroup(DEFAULT_GROUP, "Visited",
+ ContextCompat.getColor(ctx, R.color.blue).toDrawable())
+ }
+ saveData()
+ }
+
+ fun doImport(ctx: Context, file: Uri?){
+ if(file!=null) {
+ importData(ctx, file)
+ val intent = ctx.packageManager
+ .getLaunchIntentForPackage(ctx.packageName)?.apply {
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+ ctx.startActivity(intent)
+ }
+ }
+
+ fun doExport(ctx: Context){
+ val fileName = "beans_backup.json"
+ val resolver = ctx.contentResolver
+ val contentValues = ContentValues().apply {
+ put(MediaStore.Downloads.DISPLAY_NAME, fileName) // "backup.json"
+ put(MediaStore.Downloads.MIME_TYPE, "text/*")
+ put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
+ }
+ val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
+ } else {
+ val downloadsDir =
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
+ val file = File(downloadsDir, fileName)
+ Uri.fromFile(file)
+ }
+ if(uri!=null) exportData(ctx, uri)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/net/helcel/beans/helper/DialogCloser.kt b/app/src/main/java/net/helcel/beans/helper/DialogCloser.kt
deleted file mode 100644
index eba0822..0000000
--- a/app/src/main/java/net/helcel/beans/helper/DialogCloser.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package net.helcel.beans.helper
-
-interface DialogCloser {
- fun onDialogDismiss(clear: Boolean)
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/helcel/beans/helper/Groups.kt b/app/src/main/java/net/helcel/beans/helper/Groups.kt
index 60e88c6..8f11d7c 100644
--- a/app/src/main/java/net/helcel/beans/helper/Groups.kt
+++ b/app/src/main/java/net/helcel/beans/helper/Groups.kt
@@ -2,15 +2,16 @@ package net.helcel.beans.helper
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
-import androidx.core.content.ContextCompat
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.Serializer
import kotlinx.serialization.json.Json
-import net.helcel.beans.R
import java.io.InputStream
-import kotlin.coroutines.coroutineContext
import kotlin.random.Random
+import androidx.core.graphics.drawable.toDrawable
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
private const val randSeed = 0
@@ -20,20 +21,23 @@ const val NO_GROUP = 0
const val DEFAULT_GROUP = 1
const val AUTO_GROUP = -1
+
+
@Serializable
class Groups(val id: Int, private val grps: HashMap) {
+ @kotlinx.serialization.Transient
+ private val _groupsFlow = MutableStateFlow>(grps.values.toList())
+ @kotlinx.serialization.Transient
+ val groupsFlow: StateFlow> = _groupsFlow.asStateFlow()
fun setGroup(key: Int, name: String, col: ColorDrawable) {
grps[key] = Group(key, name, col)
+ _groupsFlow.value = grps.values.toList()
}
fun deleteGroup(key: Int) {
grps.remove(key)
- }
-
- fun deleteAllExcept(grp: Int) {
- val keysToDelete = grps.keys.filter { it != grp }
- keysToDelete.forEach { grps.remove(it) }
+ _groupsFlow.value = grps.values.toList()
}
fun getGroupFromKey(key: Int): Group {
@@ -60,6 +64,7 @@ class Groups(val id: Int, private val grps: HashMap) {
}
fun getGroupFromPos(pos: Int): Pair {
+ if(grps.keys.isEmpty()) return Pair(NO_GROUP,Group(NO_GROUP,"-"))
val key = grps.keys.toList()[pos]
return Pair(key, getGroupFromKey(key))
}
@@ -74,9 +79,7 @@ class Groups(val id: Int, private val grps: HashMap) {
open class Group(
val key: Int,
val name: String,
- @Serializable(with = Theme.ColorDrawableSerializer::class) val color: ColorDrawable = ColorDrawable(
- Color.GRAY
- )
+ @Serializable(with = Theme.ColorDrawableSerializer::class) val color: ColorDrawable = Color.GRAY.toDrawable()
)
@OptIn(ExperimentalSerializationApi::class)
diff --git a/app/src/main/java/net/helcel/beans/helper/Settings.kt b/app/src/main/java/net/helcel/beans/helper/Settings.kt
index af64328..cb92bf4 100644
--- a/app/src/main/java/net/helcel/beans/helper/Settings.kt
+++ b/app/src/main/java/net/helcel/beans/helper/Settings.kt
@@ -4,19 +4,15 @@ import android.content.Context
import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import net.helcel.beans.R
-import net.helcel.beans.activity.MainActivity
-import net.helcel.beans.activity.fragment.SettingsFragment
+import net.helcel.beans.activity.MainScreen
object Settings {
private lateinit var sp: SharedPreferences
- private lateinit var mainActivity: MainActivity
- fun start(ctx: MainActivity) {
+ private lateinit var mainActivity: MainScreen
+ fun start(ctx: MainScreen) {
mainActivity = ctx
sp = PreferenceManager.getDefaultSharedPreferences(ctx)
- SettingsFragment.setTheme(
- ctx, sp.getString(ctx.getString(R.string.key_theme), ctx.getString(R.string.system))
- )
}
fun isSingleGroup(ctx: Context): Boolean {
@@ -41,7 +37,7 @@ object Settings {
}
fun refreshProjection(): Boolean {
- mainActivity.refreshProjection()
+ (mainActivity).refreshProjection()
return true
}
diff --git a/app/src/main/java/net/helcel/beans/helper/Theme.kt b/app/src/main/java/net/helcel/beans/helper/Theme.kt
index f3897f6..6858310 100644
--- a/app/src/main/java/net/helcel/beans/helper/Theme.kt
+++ b/app/src/main/java/net/helcel/beans/helper/Theme.kt
@@ -1,23 +1,16 @@
package net.helcel.beans.helper
-import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
-import android.util.TypedValue
-import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.ColorUtils
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
+import androidx.core.graphics.drawable.toDrawable
object Theme {
- fun colorWrapper(ctx: Context, res: Int): ColorDrawable {
- val colorPrimaryTyped = TypedValue()
- ctx.theme.resolveAttribute(res, colorPrimaryTyped, true)
- return ColorDrawable(colorPrimaryTyped.data)
- }
fun colorToHex6(c: ColorDrawable): String {
return '#' + colorToHex8(c).substring(3)
@@ -28,11 +21,6 @@ object Theme {
return '#' + c.color.toHexString()
}
- fun createActionBar(ctx: AppCompatActivity, title: String) {
- ctx.supportActionBar?.title = title
- ctx.supportActionBar?.setDisplayHomeAsUpEnabled(true)
- }
-
fun getContrastColor(color: Int): Int {
val whiteContrast = ColorUtils.calculateContrast(Color.WHITE, color)
val blackContrast = ColorUtils.calculateContrast(Color.BLACK, color)
@@ -43,7 +31,7 @@ object Theme {
override val descriptor = PrimitiveSerialDescriptor("ColorDrawable", PrimitiveKind.INT)
override fun deserialize(decoder: Decoder): ColorDrawable {
- return ColorDrawable(decoder.decodeInt())
+ return decoder.decodeInt().toDrawable()
}
override fun serialize(encoder: Encoder, value: ColorDrawable) {
diff --git a/app/src/main/java/net/helcel/beans/helper/Visits.kt b/app/src/main/java/net/helcel/beans/helper/Visits.kt
index f472ef7..a9254bb 100644
--- a/app/src/main/java/net/helcel/beans/helper/Visits.kt
+++ b/app/src/main/java/net/helcel/beans/helper/Visits.kt
@@ -1,5 +1,7 @@
package net.helcel.beans.helper
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.Serializer
@@ -11,9 +13,17 @@ import java.io.InputStream
@Serializable
class Visits(val id: Int, private val locs: HashMap) {
+ @kotlinx.serialization.Transient
+ private val _visitsFlow = MutableStateFlow