Compare commits
	
		
			1 Commits
		
	
	
		
			1.1a
			...
			8113be9e96
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 8113be9e96 | 
							
								
								
									
										4
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -23,7 +23,7 @@ jobs: | |||||||
|       contents: write |       contents: write | ||||||
|  |  | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v5 |       - uses: actions/checkout@v4 | ||||||
|  |  | ||||||
|       - name: set up secrets |       - name: set up secrets | ||||||
|         run: | |         run: | | ||||||
| @@ -41,7 +41,7 @@ jobs: | |||||||
|         run: git checkout -B "$BRANCH" |         run: git checkout -B "$BRANCH" | ||||||
|  |  | ||||||
|       - name: set up JDK |       - name: set up JDK | ||||||
|         uses: actions/setup-java@v5 |         uses: actions/setup-java@v4 | ||||||
|         with: |         with: | ||||||
|           java-version: 17 |           java-version: 17 | ||||||
|           distribution: "temurin" |           distribution: "temurin" | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -20,6 +20,8 @@ app/build/ | |||||||
| app/debug/ | app/debug/ | ||||||
| app/release/ | app/release/ | ||||||
| captures/ | captures/ | ||||||
|  | .externalNativeBuild | ||||||
|  | .cxx | ||||||
| local.properties | local.properties | ||||||
| keystore.properties | keystore.properties | ||||||
| key.jks | key.jks | ||||||
| @@ -39,11 +39,8 @@ | |||||||
| ## 📳 Installation | ## 📳 Installation | ||||||
|  |  | ||||||
| <div style="display: flex; justify-content: center; align-items: center; flex-direction: row;"> | <div style="display: flex; justify-content: center; align-items: center; flex-direction: row;"> | ||||||
|     <!--<a href="https://f-droid.org/packages/net.helcel.beans/"> |     <a href="https://f-droid.org/packages/net.helcel.beans/"> | ||||||
|         <img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" width="206"> |         <img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" width="206"> | ||||||
|     </a>--> |  | ||||||
|     <a href="https://apt.izzysoft.de/fdroid/index/apk/net.helcel.beans"> |  | ||||||
|         <img width="200" height="80" alt="Izzy Download" src=".github/images/izzy.png"> |  | ||||||
|     </a> |     </a> | ||||||
|     <a href="https://github.com/helcel-net/beans/releases/latest"> |     <a href="https://github.com/helcel-net/beans/releases/latest"> | ||||||
|         <img width="200" height="84" alt="APK Download" src=".github/images/apk.png"> |         <img width="200" height="84" alt="APK Download" src=".github/images/apk.png"> | ||||||
|   | |||||||
| @@ -1,24 +1,21 @@ | |||||||
| plugins { | plugins { | ||||||
|     id 'com.android.application' |     id 'com.android.application' | ||||||
|     id 'org.jetbrains.kotlin.android' |     id 'org.jetbrains.kotlin.android' | ||||||
|     id 'org.jetbrains.kotlin.plugin.serialization' version '2.2.20' |     id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.23' | ||||||
|     id 'org.jetbrains.kotlin.plugin.compose' version '2.2.20' |     id 'com.mikepenz.aboutlibraries.plugin' version '11.1.4' | ||||||
|     id 'com.mikepenz.aboutlibraries.plugin' version '12.2.4' |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| android { | android { | ||||||
|     namespace 'net.helcel.beans' |     namespace 'net.helcel.beans' | ||||||
|     compileSdk 36 |     compileSdk 34 | ||||||
|  |  | ||||||
|     defaultConfig { |     defaultConfig { | ||||||
|         buildConfigField("String", "APP_NAME", "\"Beans\"") |  | ||||||
|         manifestPlaceholders["APP_NAME"] = "Beans" |  | ||||||
|         applicationId 'net.helcel.beans' |         applicationId 'net.helcel.beans' | ||||||
|         minSdk 28 |         minSdk 28 | ||||||
|         targetSdk 36 |         targetSdk 34 | ||||||
|         versionCode 4 |         versionCode 1 | ||||||
|         versionName "1.1a" |         versionName "1.0" | ||||||
|     } |     } | ||||||
|     signingConfigs { |     signingConfigs { | ||||||
|         create("release") { |         create("release") { | ||||||
| @@ -57,15 +54,17 @@ android { | |||||||
|     compileOptions { |     compileOptions { | ||||||
|         coreLibraryDesugaringEnabled true |         coreLibraryDesugaringEnabled true | ||||||
|  |  | ||||||
|         sourceCompatibility JavaVersion.VERSION_21 |         sourceCompatibility JavaVersion.VERSION_17 | ||||||
|         targetCompatibility JavaVersion.VERSION_21 |         targetCompatibility JavaVersion.VERSION_17 | ||||||
|         encoding 'utf-8' |         encoding 'utf-8' | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     kotlinOptions { | ||||||
|  |         jvmTarget = JavaVersion.VERSION_17 | ||||||
|  |     } | ||||||
|  |  | ||||||
|     buildFeatures { |     buildFeatures { | ||||||
|         viewBinding true |         viewBinding true | ||||||
|         compose true |  | ||||||
|         buildConfig true |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     dependenciesInfo { |     dependenciesInfo { | ||||||
| @@ -74,47 +73,20 @@ android { | |||||||
|         // Disables dependency metadata when building Android App Bundles. |         // Disables dependency metadata when building Android App Bundles. | ||||||
|         includeInBundle = false |         includeInBundle = false | ||||||
|     } |     } | ||||||
|     composeOptions { |  | ||||||
|         kotlinCompilerExtensionVersion = "2.2.20" |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     kotlin { |  | ||||||
|         jvmToolchain(21) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     lint { |  | ||||||
|         disable 'UsingMaterialAndMaterial3Libraries' |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } | } | ||||||
| aboutLibraries { | aboutLibraries { | ||||||
|     library { |     exclusionPatterns = [~"androidx.*", ~"com.google.android.*", ~"org.jetbrains.*"] | ||||||
|         exclusionPatterns = [~"androidx.*", ~"com.google.android.*", ~"org.jetbrains.*"] |     configPath = "config" | ||||||
|     } |  | ||||||
|     excludeFields = ["generated"] |  | ||||||
| } | } | ||||||
|  |  | ||||||
| dependencies { | dependencies { | ||||||
|     implementation 'androidx.compose.material3:material3:1.3.2' |     coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.0.4' | ||||||
|     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.preference:preference-ktx:1.2.1' | ||||||
|     implementation 'androidx.compose.ui:ui' |     implementation 'com.google.android.material:material:1.12.0' | ||||||
|     implementation "androidx.compose.material:material:1.9.1" |     implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3' | ||||||
|     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.caverock:androidsvg-aar:1.4' | ||||||
|     implementation 'com.github.chrisbanes:PhotoView:2.3.0' |     implementation 'com.github.chrisbanes:PhotoView:2.3.0' | ||||||
|  |     implementation 'com.mikepenz:aboutlibraries:11.1.4' | ||||||
|     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' |  | ||||||
| } | } | ||||||
| @@ -1,22 +1,50 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|     xmlns:tools="http://schemas.android.com/tools"> |     xmlns:tools="http://schemas.android.com/tools"> | ||||||
|  |  | ||||||
|     <application |     <application | ||||||
|         android:allowBackup="true" |         android:allowBackup="true" | ||||||
|         android:dataExtractionRules="@xml/data_extraction_rules" |         android:dataExtractionRules="@xml/data_extraction_rules" | ||||||
|         android:fullBackupContent="@xml/backup_rules" |         android:fullBackupContent="@xml/backup_rules" | ||||||
|         android:hardwareAccelerated="false" |         android:hardwareAccelerated="false" | ||||||
|         android:icon="@mipmap/ic_launcher_round" |         android:icon="@mipmap/ic_launcher_round" | ||||||
|         android:label="${APP_NAME}" |         android:label="@string/app_name" | ||||||
|         android:supportsRtl="true" |         android:supportsRtl="true" | ||||||
|         tools:replace="android:allowBackup"> |         android:theme="@style/Theme.Beans" | ||||||
|  |         tools:replace="android:allowBackup" | ||||||
|  |         tools:targetApi="31"> | ||||||
|  |         <profileable android:shell="true" /> | ||||||
|  |  | ||||||
|         <activity |         <activity | ||||||
|             android:name=".activity.MainScreen" |             android:name=".activity.MainActivity" | ||||||
|             android:exported="true"> |             android:exported="true"> | ||||||
|             <intent-filter> |             <intent-filter> | ||||||
|                 <action android:name="android.intent.action.MAIN" /> |                 <action android:name="android.intent.action.MAIN" /> | ||||||
|                 <category android:name="android.intent.category.LAUNCHER" /> |                 <category android:name="android.intent.category.LAUNCHER" /> | ||||||
|             </intent-filter> |             </intent-filter> | ||||||
|         </activity> |         </activity> | ||||||
|  |         <activity | ||||||
|  |             android:name=".activity.EditActivity" | ||||||
|  |             android:exported="true"> | ||||||
|  |             <intent-filter> | ||||||
|  |                 <action android:name="android.intent.action.MAIN" /> | ||||||
|  |             </intent-filter> | ||||||
|  |         </activity> | ||||||
|  |         <activity | ||||||
|  |             android:name=".activity.StatsActivity" | ||||||
|  |             android:exported="true"> | ||||||
|  |             <intent-filter> | ||||||
|  |                 <action android:name="android.intent.action.MAIN" /> | ||||||
|  |             </intent-filter> | ||||||
|  |         </activity> | ||||||
|  |         <activity | ||||||
|  |             android:name=".activity.SettingsActivity" | ||||||
|  |             android:exported="true"> | ||||||
|  |             <intent-filter> | ||||||
|  |                 <action android:name="android.intent.action.MAIN" /> | ||||||
|  |             </intent-filter> | ||||||
|  |         </activity> | ||||||
|  |  | ||||||
|     </application> |     </application> | ||||||
|  |  | ||||||
| </manifest> | </manifest> | ||||||
							
								
								
									
										69
									
								
								app/src/main/java/net/helcel/beans/activity/EditActivity.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								app/src/main/java/net/helcel/beans/activity/EditActivity.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | |||||||
|  | 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) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -1,64 +0,0 @@ | |||||||
| 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) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										75
									
								
								app/src/main/java/net/helcel/beans/activity/MainActivity.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								app/src/main/java/net/helcel/beans/activity/MainActivity.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | |||||||
|  | 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) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -1,123 +0,0 @@ | |||||||
| 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) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -0,0 +1,63 @@ | |||||||
|  | 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) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,381 +0,0 @@ | |||||||
| 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<String>, 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) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,159 +1,57 @@ | |||||||
| package net.helcel.beans.activity | package net.helcel.beans.activity | ||||||
|  |  | ||||||
| import androidx.compose.foundation.background | import android.os.Bundle | ||||||
| import androidx.compose.foundation.layout.Arrangement | import android.view.MenuItem | ||||||
| import androidx.compose.foundation.layout.Column | import androidx.appcompat.app.AppCompatActivity | ||||||
| import androidx.compose.foundation.layout.Row | import androidx.fragment.app.Fragment | ||||||
| import androidx.compose.foundation.layout.fillMaxSize | import androidx.recyclerview.widget.LinearLayoutManager | ||||||
| import androidx.compose.foundation.layout.fillMaxWidth | import androidx.recyclerview.widget.RecyclerView | ||||||
| import androidx.compose.foundation.layout.padding | import androidx.viewpager2.adapter.FragmentStateAdapter | ||||||
| import androidx.compose.foundation.lazy.LazyColumn | import androidx.viewpager2.widget.ViewPager2 | ||||||
| import androidx.compose.foundation.lazy.items | import com.google.android.material.tabs.TabLayoutMediator | ||||||
| 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.R | ||||||
|  | import net.helcel.beans.activity.adapter.StatsListAdapter | ||||||
| import net.helcel.beans.countries.GeoLoc.LocType | import net.helcel.beans.countries.GeoLoc.LocType | ||||||
| import net.helcel.beans.countries.World | import net.helcel.beans.databinding.ActivityStatBinding | ||||||
| import net.helcel.beans.helper.AUTO_GROUP | import net.helcel.beans.helper.Settings | ||||||
| import net.helcel.beans.helper.Data | import net.helcel.beans.helper.Theme.createActionBar | ||||||
| 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) | private val MODE_LIST = listOf(LocType.WORLD, LocType.COUNTRY, LocType.STATE) | ||||||
|  |  | ||||||
| @Composable | class StatsActivity : AppCompatActivity() { | ||||||
| fun StatsScreen( |     private lateinit var _binding: ActivityStatBinding | ||||||
|     onExit: ()-> Unit |     private var activeMode = LocType.WORLD | ||||||
| ) { |  | ||||||
|     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)) | ||||||
|  |  | ||||||
|     SysTheme { |         _binding.stats.layoutManager = | ||||||
|         Scaffold( |             LinearLayoutManager(this, RecyclerView.VERTICAL, false) | ||||||
|             topBar = { |         val adapter = StatsListAdapter(_binding.stats, _binding.name) | ||||||
|                 TopAppBar( |         _binding.groupColor.setOnClickListener { adapter.invertCountMode() } | ||||||
|                     title = { |         _binding.stats.adapter = adapter | ||||||
|                         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 | ||||||
|         ) { padding -> |             override fun createFragment(position: Int): Fragment = Fragment() | ||||||
|             Column(Modifier.padding(padding)) { |         } | ||||||
|                 TabRow(selectedTabIndex = selectedTab) { |         TabLayoutMediator(_binding.tab, _binding.pager) { tab, position -> | ||||||
|                     modes.forEachIndexed { index, mode -> |             tab.text = MODE_LIST[position].txt | ||||||
|                         Tab( |         }.attach() | ||||||
|                             selected = selectedTab == index, |  | ||||||
|                             onClick = { selectedTab = index }, |  | ||||||
|                             text = { Text(mode.txt) } |  | ||||||
|                         ) |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 Row( |         _binding.pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { | ||||||
|                     modifier = Modifier |             override fun onPageSelected(position: Int) { | ||||||
|                         .fillMaxWidth() |                 activeMode = MODE_LIST[position] | ||||||
|                         .padding(8.dp), |                 adapter.refreshMode(activeMode) | ||||||
|                     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) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     val count = when (activeMode) { |     override fun onOptionsItemSelected(item: MenuItem): Boolean { | ||||||
|         LocType.WORLD -> World.WWW.children.filter { it.code in visited }.size |         finish() | ||||||
|         LocType.COUNTRY -> World.WWW.children.flatMap { it.children.filter { c -> c.code in visited } }.size |         return super.onOptionsItemSelected(item) | ||||||
|         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) |  | ||||||
|         ) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -0,0 +1,193 @@ | |||||||
|  | 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<GeolocListAdapter.FoldingListViewHolder>() { | ||||||
|  |  | ||||||
|  |     private val sortedList = l.children.toList().sortedBy { it.fullName } | ||||||
|  |     private val holders: MutableSet<FoldingListViewHolder> = 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) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,78 @@ | |||||||
|  | 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<GroupListAdapter.GroupViewHolder>() { | ||||||
|  |  | ||||||
|  |     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<Int, Groups.Group>) { | ||||||
|  |             _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 | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,152 @@ | |||||||
|  | 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<StatsListAdapter.StatsViewHolder>() { | ||||||
|  |     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<GeoLoc> = World.WWW.children.toList() | ||||||
|  |     private val countryTotal: List<GeoLoc> = World.WWW.children.flatMap { it.children } | ||||||
|  |     private val stateTotal: List<GeoLoc> = | ||||||
|  |         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<Int, Groups.Group> | ||||||
|  |  | ||||||
|  |         private lateinit var wwwCount: List<GeoLoc> | ||||||
|  |         private lateinit var countryCount: List<GeoLoc> | ||||||
|  |         private lateinit var stateCount: List<GeoLoc> | ||||||
|  |  | ||||||
|  |         fun bind(entry: Pair<Int, Groups.Group>): 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 | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,61 @@ | |||||||
|  | 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<EditPlaceFragment> = 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)} | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,21 @@ | |||||||
|  | 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 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,155 @@ | |||||||
|  | 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 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,60 @@ | |||||||
|  | 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) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,38 @@ | |||||||
|  | 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) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,33 @@ | |||||||
|  | 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 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,142 @@ | |||||||
|  | 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<Preference>(getString(R.string.key_theme))?.setOnPreferenceChangeListener { _, key -> | ||||||
|  |             setTheme(ctx, key as String) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Select map projection | ||||||
|  |         findPreference<Preference>(getString(R.string.key_projection))?.setOnPreferenceChangeListener { _, key -> | ||||||
|  |             Settings.refreshProjection() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Toggle groups | ||||||
|  |         findPreference<Preference>(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<Preference>(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<Preference>(getString(R.string.licenses))?.setOnPreferenceClickListener { | ||||||
|  |             requireActivity().supportFragmentManager.beginTransaction() | ||||||
|  |                 .replace(R.id.fragment_view, LicenseFragment(), getString(R.string.licenses)) | ||||||
|  |                 .commit() | ||||||
|  |             true | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Open about fragment | ||||||
|  |         findPreference<Preference>(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) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,78 +0,0 @@ | |||||||
| 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) |  | ||||||
|         ) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,222 +0,0 @@ | |||||||
| 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, |  | ||||||
|         ) |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
| @@ -1,193 +0,0 @@ | |||||||
| 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) |  | ||||||
|                         ) |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }, |  | ||||||
|     ) |  | ||||||
| } |  | ||||||
| @@ -1,218 +0,0 @@ | |||||||
| 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.Tab |  | ||||||
| import androidx.compose.material.TabRow |  | ||||||
| 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<GeoLoc> = 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 |  | ||||||
|             TabRow( |  | ||||||
|                 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)) |  | ||||||
| } |  | ||||||
| @@ -1,43 +0,0 @@ | |||||||
| 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, |  | ||||||
|             ) |  | ||||||
|         ) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -7,8 +7,8 @@ enum class Country( | |||||||
|     ATA("Antarctica", 14000000), |     ATA("Antarctica", 14000000), | ||||||
|  |  | ||||||
|     //    HKG("Hong Kong", 1104), |     //    HKG("Hong Kong", 1104), | ||||||
|     //    MAC("Macao", 32), | //    MAC("Macao", 32), | ||||||
|     //    ANT("Netherlands Antilles", 800), | //    ANT("Netherlands Antilles", 800), | ||||||
|     AFG("Afghanistan", 645487), |     AFG("Afghanistan", 645487), | ||||||
|     XAD("Akrotiri and Dhekelia", 234), |     XAD("Akrotiri and Dhekelia", 234), | ||||||
|     ALA("Åland", 1483), |     ALA("Åland", 1483), | ||||||
|   | |||||||
| @@ -12,4 +12,4 @@ interface GeoLoc { | |||||||
|  |  | ||||||
|     val type: LocType |     val type: LocType | ||||||
|     val children: Set<GeoLoc> |     val children: Set<GeoLoc> | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,21 +1,13 @@ | |||||||
| package net.helcel.beans.helper | package net.helcel.beans.helper | ||||||
|  |  | ||||||
| import android.content.ContentValues |  | ||||||
| import android.content.Context | import android.content.Context | ||||||
| import android.content.SharedPreferences | import android.content.SharedPreferences | ||||||
| import android.net.Uri | import android.graphics.drawable.ColorDrawable | ||||||
| import android.os.Build |  | ||||||
| import android.os.Environment |  | ||||||
| import android.provider.MediaStore |  | ||||||
| import androidx.core.content.ContextCompat | import androidx.core.content.ContextCompat | ||||||
| import androidx.preference.PreferenceManager | import androidx.preference.PreferenceManager | ||||||
| import net.helcel.beans.R | import net.helcel.beans.R | ||||||
| import net.helcel.beans.countries.GeoLoc | import net.helcel.beans.countries.GeoLoc | ||||||
| import java.util.HashMap | 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 { | object Data { | ||||||
|     var visits : Visits = Visits(0, HashMap()) |     var visits : Visits = Visits(0, HashMap()) | ||||||
| @@ -41,8 +33,7 @@ object Data { | |||||||
|  |  | ||||||
|         // Add default group "Visited" with app's color if there is no group already |         // Add default group "Visited" with app's color if there is no group already | ||||||
|         if (groups.size() == 0) { |         if (groups.size() == 0) { | ||||||
|             groups.setGroup(DEFAULT_GROUP, "Visited", |             groups.setGroup(DEFAULT_GROUP, "Visited", ColorDrawable(ContextCompat.getColor(ctx, R.color.blue))) | ||||||
|                 ContextCompat.getColor(ctx, R.color.blue).toDrawable()) |  | ||||||
|             saveData() |             saveData() | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -50,73 +41,9 @@ object Data { | |||||||
|     fun saveData() { |     fun saveData() { | ||||||
|         if(groups.id != visits.id) return |         if(groups.id != visits.id) return | ||||||
|         val id = groups.id |         val id = groups.id | ||||||
|         sharedPreferences.edit { |         val editor = sharedPreferences.edit() | ||||||
|             putString("groups_$id", groupsSerial.writeTo(groups)) |         editor.putString("groups_$id", groupsSerial.writeTo(groups)) | ||||||
|             putString("visits_$id", visitsSerial.writeTo(visits)) |         editor.putString("visits_$id", visitsSerial.writeTo(visits)) | ||||||
|         } |         editor.apply() | ||||||
|     } |  | ||||||
|  |  | ||||||
|     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) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -0,0 +1,5 @@ | |||||||
|  | package net.helcel.beans.helper | ||||||
|  |  | ||||||
|  | interface DialogCloser { | ||||||
|  |     fun onDialogDismiss(clear: Boolean) | ||||||
|  | } | ||||||
| @@ -2,16 +2,15 @@ package net.helcel.beans.helper | |||||||
|  |  | ||||||
| import android.graphics.Color | import android.graphics.Color | ||||||
| import android.graphics.drawable.ColorDrawable | import android.graphics.drawable.ColorDrawable | ||||||
|  | import androidx.core.content.ContextCompat | ||||||
| import kotlinx.serialization.ExperimentalSerializationApi | import kotlinx.serialization.ExperimentalSerializationApi | ||||||
| import kotlinx.serialization.Serializable | import kotlinx.serialization.Serializable | ||||||
| import kotlinx.serialization.Serializer | import kotlinx.serialization.Serializer | ||||||
| import kotlinx.serialization.json.Json | import kotlinx.serialization.json.Json | ||||||
|  | import net.helcel.beans.R | ||||||
| import java.io.InputStream | import java.io.InputStream | ||||||
|  | import kotlin.coroutines.coroutineContext | ||||||
| import kotlin.random.Random | 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 | private const val randSeed = 0 | ||||||
| @@ -21,23 +20,20 @@ const val NO_GROUP = 0 | |||||||
| const val DEFAULT_GROUP = 1 | const val DEFAULT_GROUP = 1 | ||||||
| const val AUTO_GROUP = -1 | const val AUTO_GROUP = -1 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @Serializable | @Serializable | ||||||
| class Groups(val id: Int, private val grps: HashMap<Int, Group>) { | class Groups(val id: Int, private val grps: HashMap<Int, Group>) { | ||||||
|     @kotlinx.serialization.Transient |  | ||||||
|     private val _groupsFlow = MutableStateFlow<List<Group>>(grps.values.toList()) |  | ||||||
|     @kotlinx.serialization.Transient |  | ||||||
|     val groupsFlow: StateFlow<List<Group>> = _groupsFlow.asStateFlow() |  | ||||||
|  |  | ||||||
|     fun setGroup(key: Int, name: String, col: ColorDrawable) { |     fun setGroup(key: Int, name: String, col: ColorDrawable) { | ||||||
|         grps[key] = Group(key, name, col) |         grps[key] = Group(key, name, col) | ||||||
|         _groupsFlow.value = grps.values.toList() |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun deleteGroup(key: Int) { |     fun deleteGroup(key: Int) { | ||||||
|         grps.remove(key) |         grps.remove(key) | ||||||
|         _groupsFlow.value = grps.values.toList() |     } | ||||||
|  |  | ||||||
|  |     fun deleteAllExcept(grp: Int) { | ||||||
|  |         val keysToDelete = grps.keys.filter { it != grp } | ||||||
|  |         keysToDelete.forEach { grps.remove(it) } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun getGroupFromKey(key: Int): Group { |     fun getGroupFromKey(key: Int): Group { | ||||||
| @@ -64,7 +60,6 @@ class Groups(val id: Int, private val grps: HashMap<Int, Group>) { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun getGroupFromPos(pos: Int): Pair<Int, Group> { |     fun getGroupFromPos(pos: Int): Pair<Int, Group> { | ||||||
|         if(grps.keys.isEmpty()) return Pair(NO_GROUP,Group(NO_GROUP,"-")) |  | ||||||
|         val key = grps.keys.toList()[pos] |         val key = grps.keys.toList()[pos] | ||||||
|         return Pair(key, getGroupFromKey(key)) |         return Pair(key, getGroupFromKey(key)) | ||||||
|     } |     } | ||||||
| @@ -79,7 +74,9 @@ class Groups(val id: Int, private val grps: HashMap<Int, Group>) { | |||||||
|     open class Group( |     open class Group( | ||||||
|         val key: Int, |         val key: Int, | ||||||
|         val name: String, |         val name: String, | ||||||
|         @Serializable(with = Theme.ColorDrawableSerializer::class) val color: ColorDrawable = Color.GRAY.toDrawable() |         @Serializable(with = Theme.ColorDrawableSerializer::class) val color: ColorDrawable = ColorDrawable( | ||||||
|  |             Color.GRAY | ||||||
|  |         ) | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     @OptIn(ExperimentalSerializationApi::class) |     @OptIn(ExperimentalSerializationApi::class) | ||||||
|   | |||||||
| @@ -4,15 +4,19 @@ import android.content.Context | |||||||
| import android.content.SharedPreferences | import android.content.SharedPreferences | ||||||
| import androidx.preference.PreferenceManager | import androidx.preference.PreferenceManager | ||||||
| import net.helcel.beans.R | import net.helcel.beans.R | ||||||
| import net.helcel.beans.activity.MainScreen | import net.helcel.beans.activity.MainActivity | ||||||
|  | import net.helcel.beans.activity.fragment.SettingsFragment | ||||||
|  |  | ||||||
| object Settings { | object Settings { | ||||||
|  |  | ||||||
|     private lateinit var sp: SharedPreferences |     private lateinit var sp: SharedPreferences | ||||||
|     private lateinit var mainActivity: MainScreen |     private lateinit var mainActivity: MainActivity | ||||||
|     fun start(ctx: MainScreen) { |     fun start(ctx: MainActivity) { | ||||||
|         mainActivity = ctx |         mainActivity = ctx | ||||||
|         sp = PreferenceManager.getDefaultSharedPreferences(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 { |     fun isSingleGroup(ctx: Context): Boolean { | ||||||
| @@ -37,7 +41,7 @@ object Settings { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun refreshProjection(): Boolean { |     fun refreshProjection(): Boolean { | ||||||
|         (mainActivity).refreshProjection() |         mainActivity.refreshProjection() | ||||||
|         return true |         return true | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,16 +1,23 @@ | |||||||
| package net.helcel.beans.helper | package net.helcel.beans.helper | ||||||
|  |  | ||||||
|  | import android.content.Context | ||||||
| import android.graphics.Color | import android.graphics.Color | ||||||
| import android.graphics.drawable.ColorDrawable | import android.graphics.drawable.ColorDrawable | ||||||
|  | import android.util.TypedValue | ||||||
|  | import androidx.appcompat.app.AppCompatActivity | ||||||
| import androidx.core.graphics.ColorUtils | import androidx.core.graphics.ColorUtils | ||||||
| import kotlinx.serialization.KSerializer | import kotlinx.serialization.KSerializer | ||||||
| import kotlinx.serialization.descriptors.PrimitiveKind | import kotlinx.serialization.descriptors.PrimitiveKind | ||||||
| import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor | ||||||
| import kotlinx.serialization.encoding.Decoder | import kotlinx.serialization.encoding.Decoder | ||||||
| import kotlinx.serialization.encoding.Encoder | import kotlinx.serialization.encoding.Encoder | ||||||
| import androidx.core.graphics.drawable.toDrawable |  | ||||||
|  |  | ||||||
| object Theme { | 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 { |     fun colorToHex6(c: ColorDrawable): String { | ||||||
|         return '#' + colorToHex8(c).substring(3) |         return '#' + colorToHex8(c).substring(3) | ||||||
| @@ -21,6 +28,11 @@ object Theme { | |||||||
|         return '#' + c.color.toHexString() |         return '#' + c.color.toHexString() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fun createActionBar(ctx: AppCompatActivity, title: String) { | ||||||
|  |         ctx.supportActionBar?.title = title | ||||||
|  |         ctx.supportActionBar?.setDisplayHomeAsUpEnabled(true) | ||||||
|  |     } | ||||||
|  |  | ||||||
|     fun getContrastColor(color: Int): Int { |     fun getContrastColor(color: Int): Int { | ||||||
|         val whiteContrast = ColorUtils.calculateContrast(Color.WHITE, color) |         val whiteContrast = ColorUtils.calculateContrast(Color.WHITE, color) | ||||||
|         val blackContrast = ColorUtils.calculateContrast(Color.BLACK, color) |         val blackContrast = ColorUtils.calculateContrast(Color.BLACK, color) | ||||||
| @@ -31,7 +43,7 @@ object Theme { | |||||||
|         override val descriptor = PrimitiveSerialDescriptor("ColorDrawable", PrimitiveKind.INT) |         override val descriptor = PrimitiveSerialDescriptor("ColorDrawable", PrimitiveKind.INT) | ||||||
|  |  | ||||||
|         override fun deserialize(decoder: Decoder): ColorDrawable { |         override fun deserialize(decoder: Decoder): ColorDrawable { | ||||||
|             return decoder.decodeInt().toDrawable() |             return ColorDrawable(decoder.decodeInt()) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         override fun serialize(encoder: Encoder, value: ColorDrawable) { |         override fun serialize(encoder: Encoder, value: ColorDrawable) { | ||||||
|   | |||||||
| @@ -1,7 +1,5 @@ | |||||||
| package net.helcel.beans.helper | package net.helcel.beans.helper | ||||||
|  |  | ||||||
| import kotlinx.coroutines.flow.MutableStateFlow |  | ||||||
| import kotlinx.coroutines.flow.StateFlow |  | ||||||
| import kotlinx.serialization.ExperimentalSerializationApi | import kotlinx.serialization.ExperimentalSerializationApi | ||||||
| import kotlinx.serialization.Serializable | import kotlinx.serialization.Serializable | ||||||
| import kotlinx.serialization.Serializer | import kotlinx.serialization.Serializer | ||||||
| @@ -13,17 +11,9 @@ import java.io.InputStream | |||||||
| @Serializable | @Serializable | ||||||
| class Visits(val id: Int, private val locs: HashMap<String, Int>) { | class Visits(val id: Int, private val locs: HashMap<String, Int>) { | ||||||
|  |  | ||||||
|     @kotlinx.serialization.Transient |  | ||||||
|     private val _visitsFlow = MutableStateFlow<Map<String,Int>>(locs.toMutableMap()) |  | ||||||
|     @kotlinx.serialization.Transient |  | ||||||
|     val visitsFlow: StateFlow<Map<String,Int>> = _visitsFlow |  | ||||||
|  |  | ||||||
|     fun setVisited(key: GeoLoc?, b: Int) { |     fun setVisited(key: GeoLoc?, b: Int) { | ||||||
|         if (key == null) |         if (key == null) | ||||||
|             return |             return | ||||||
|         _visitsFlow.value = _visitsFlow.value.toMutableMap().apply { |  | ||||||
|             this[key.code] = b |  | ||||||
|         } |  | ||||||
|         locs[key.code] = b |         locs[key.code] = b | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -31,9 +21,6 @@ class Visits(val id: Int, private val locs: HashMap<String, Int>) { | |||||||
|         val keysToDelete = locs |         val keysToDelete = locs | ||||||
|             .filter { it.value == key } |             .filter { it.value == key } | ||||||
|             .map { it.key } |             .map { it.key } | ||||||
|         _visitsFlow.value = _visitsFlow.value.toMutableMap().apply { |  | ||||||
|             keysToDelete.forEach { this.remove(it)} |  | ||||||
|         } |  | ||||||
|         keysToDelete.forEach { |         keysToDelete.forEach { | ||||||
|             locs.remove(it) |             locs.remove(it) | ||||||
|         } |         } | ||||||
| @@ -66,7 +53,6 @@ class Visits(val id: Int, private val locs: HashMap<String, Int>) { | |||||||
|         keys.forEach { |         keys.forEach { | ||||||
|             locs[it] = group |             locs[it] = group | ||||||
|         } |         } | ||||||
|         _visitsFlow.value = locs |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @OptIn(ExperimentalSerializationApi::class) |     @OptIn(ExperimentalSerializationApi::class) | ||||||
|   | |||||||
| @@ -1,10 +1,6 @@ | |||||||
| package net.helcel.beans.svg | package net.helcel.beans.svg | ||||||
|  |  | ||||||
| import android.content.Context | import android.content.Context | ||||||
| import androidx.compose.material.MaterialTheme |  | ||||||
| import androidx.compose.runtime.Composable |  | ||||||
| import androidx.compose.ui.graphics.toArgb |  | ||||||
| import androidx.core.graphics.drawable.toDrawable |  | ||||||
| import net.helcel.beans.countries.World | import net.helcel.beans.countries.World | ||||||
| import net.helcel.beans.helper.AUTO_GROUP | import net.helcel.beans.helper.AUTO_GROUP | ||||||
| import net.helcel.beans.helper.Data.groups | import net.helcel.beans.helper.Data.groups | ||||||
| @@ -12,10 +8,15 @@ import net.helcel.beans.helper.Data.visits | |||||||
| import net.helcel.beans.helper.NO_GROUP | import net.helcel.beans.helper.NO_GROUP | ||||||
| import net.helcel.beans.helper.Settings | import net.helcel.beans.helper.Settings | ||||||
| import net.helcel.beans.helper.Theme.colorToHex6 | import net.helcel.beans.helper.Theme.colorToHex6 | ||||||
|  | import net.helcel.beans.helper.Theme.colorWrapper | ||||||
|  |  | ||||||
| class CSSWrapper(private val ctx: Context) { | class CSSWrapper(private val ctx: Context) { | ||||||
|  |  | ||||||
|  |     private val colorForeground: String = | ||||||
|  |         colorToHex6(colorWrapper(ctx, android.R.attr.panelColorBackground)) | ||||||
|  |     private val colorBackground: String = | ||||||
|  |         colorToHex6(colorWrapper(ctx, android.R.attr.colorBackground)) | ||||||
|  |  | ||||||
|     private val continents: String = World.WWW.children.joinToString(",") { "#${it.code}2" } |     private val continents: String = World.WWW.children.joinToString(",") { "#${it.code}2" } | ||||||
|     private val countries: String = World.WWW.children.joinToString(",") { itt -> |     private val countries: String = World.WWW.children.joinToString(",") { itt -> | ||||||
|         itt.children.joinToString(",") { "#${it.code}2" } |         itt.children.joinToString(",") { "#${it.code}2" } | ||||||
| @@ -23,22 +24,18 @@ class CSSWrapper(private val ctx: Context) { | |||||||
|     private val regional: String = World.WWW.children.joinToString(",") { itt -> |     private val regional: String = World.WWW.children.joinToString(",") { itt -> | ||||||
|         itt.children.joinToString(",") { "#${it.code}1" } |         itt.children.joinToString(",") { "#${it.code}1" } | ||||||
|     } |     } | ||||||
|  |     private val countryOnlyCSS: String = | ||||||
|     @Composable |         "svg{fill:$colorForeground;stroke:$colorBackground;stroke-width:0.1;}" + | ||||||
|     fun getBaseColors() : Pair<String, String> { |                 "${regional}{display:none;}" | ||||||
|         val colorForeground = colorToHex6(MaterialTheme.colors.onBackground.toArgb().toDrawable()) |     private val countryRegionalCSS: String = | ||||||
|         val colorBackground = colorToHex6(MaterialTheme.colors.background.toArgb().toDrawable()) |         "svg{fill:$colorForeground;stroke:$colorBackground;stroke-width:0.01;}" + | ||||||
|  |                 "$continents,$countries{fill:none;stroke:$colorBackground;stroke-width:0.1;}" | ||||||
|         return Pair(colorForeground, colorBackground) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private var customCSS: String = "" |     private var customCSS: String = "" | ||||||
|  |  | ||||||
|     init { |     init { | ||||||
|         refresh() |         refresh() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     private fun refresh() { |     private fun refresh() { | ||||||
|         val id = if (Settings.isRegional(ctx)) "1" else "2" |         val id = if (Settings.isRegional(ctx)) "1" else "2" | ||||||
|         customCSS = visits.getVisitedByValue().map { (k, v) -> |         customCSS = visits.getVisitedByValue().map { (k, v) -> | ||||||
| @@ -50,24 +47,20 @@ class CSSWrapper(private val ctx: Context) { | |||||||
|                 emptyList() |                 emptyList() | ||||||
|             }).takeIf { it.isNotEmpty() } |             }).takeIf { it.isNotEmpty() } | ||||||
|                 ?.joinToString(",") { "#${it}$id,#${it}" } + "{fill:${ |                 ?.joinToString(",") { "#${it}$id,#${it}" } + "{fill:${ | ||||||
|                 if (k == AUTO_GROUP) colorToHex6(groups.getGroupFromPos(0).second.color)  |                 colorToHex6( | ||||||
|                 else colorToHex6(groups.getGroupFromKey(k).color) |                     if (k == AUTO_GROUP) | ||||||
|  |                         colorWrapper(ctx, android.R.attr.colorPrimary) | ||||||
|  |                     else groups.getGroupFromKey(k).color | ||||||
|  |                 ) | ||||||
|             };}" |             };}" | ||||||
|         }.joinToString("") |         }.joinToString("") | ||||||
|     } |     } | ||||||
|     @Composable |  | ||||||
|     fun get(): String { |     fun get(): String { | ||||||
|         val (colorForeground,colorBackground) = getBaseColors() |  | ||||||
|         refresh() |         refresh() | ||||||
|         return if (Settings.isRegional(ctx)) { |         return if (Settings.isRegional(ctx)) { | ||||||
|             val countryRegionalCSS: String = |  | ||||||
|                 "svg{fill:$colorForeground;stroke:$colorBackground;stroke-width:0.01;}" + |  | ||||||
|                         "$continents,$countries{fill:none;stroke:$colorBackground;stroke-width:0.1;}" |  | ||||||
|             countryRegionalCSS + customCSS |             countryRegionalCSS + customCSS | ||||||
|         } else { |         } else { | ||||||
|             val countryOnlyCSS: String = |  | ||||||
|                 "svg{fill:$colorForeground;stroke:$colorBackground;stroke-width:0.1;}" + |  | ||||||
|                         "${regional}{display:none;}" |  | ||||||
|             countryOnlyCSS + customCSS |             countryOnlyCSS + customCSS | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								app/src/main/res/drawable/about.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								app/src/main/res/drawable/about.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  | android:width="24dp" | ||||||
|  | android:height="24dp" | ||||||
|  | android:viewportWidth="960" | ||||||
|  | android:viewportHeight="960"> | ||||||
|  | <path | ||||||
|  |     android:fillColor="?attr/colorOnBackground" | ||||||
|  |     android:pathData="M80,160Q80,127 103.5,103.5Q127,80 160,80L800,80Q833,80 856.5,103.5Q880,127 880,160L880,640Q880,673 856.5,696.5Q833,720 800,720L240,720L80,880ZM206,640L800,640Q800,640 800,640Q800,640 800,640L800,160Q800,160 800,160Q800,160 800,160L160,160Q160,160 160,160Q160,160 160,160L160,685L206,640ZM160,640L160,640L160,160Q160,160 160,160Q160,160 160,160L160,160Q160,160 160,160Q160,160 160,160L160,640Q160,640 160,640Q160,640 160,640L160,640Z" /> | ||||||
|  |  | ||||||
|  | <path | ||||||
|  |     android:fillColor="?attr/colorPrimary" | ||||||
|  |     android:pathData="M480,280Q497,280 508.5,268.5Q520,257 520,240Q520,223 508.5,211.5Q497,200 480,200Q463,200 451.5,211.5Q440,223 440,240Q440,257 451.5,268.5Q463,280 480,280ZM440,600L520,600L520,360L440,360L440,600ZM80,880L80,160" /> | ||||||
|  | </vector> | ||||||
							
								
								
									
										9
									
								
								app/src/main/res/drawable/add.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/src/main/res/drawable/add.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  | android:width="24dp" | ||||||
|  | android:height="24dp" | ||||||
|  | android:viewportWidth="24" | ||||||
|  | android:viewportHeight="24"> | ||||||
|  | <path | ||||||
|  |     android:fillColor="?attr/colorOnBackground" | ||||||
|  |     android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" /> | ||||||
|  | </vector> | ||||||
							
								
								
									
										5
									
								
								app/src/main/res/drawable/back.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/src/main/res/drawable/back.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | <vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> | ||||||
|  |        | ||||||
|  |     <path android:fillColor="@android:color/white" android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/> | ||||||
|  |      | ||||||
|  | </vector> | ||||||
							
								
								
									
										10
									
								
								app/src/main/res/drawable/color.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/src/main/res/drawable/color.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | <vector | ||||||
|  |     android:height="24dp" | ||||||
|  |     android:viewportHeight="960" | ||||||
|  |     android:viewportWidth="960" | ||||||
|  |     android:width="24dp" | ||||||
|  |     xmlns:android="http://schemas.android.com/apk/res/android"> | ||||||
|  | <path | ||||||
|  |     android:fillColor="@color/white" | ||||||
|  |     android:pathData="M346,820L100,574Q90,564 85,552Q80,540 80,527Q80,514 85,502Q90,490 100,480L330,251L224,145L286,80L686,480Q696,490 700.5,502Q705,514 705,527Q705,540 700.5,552Q696,564 686,574L440,820Q430,830 418,835Q406,840 393,840Q380,840 368,835Q356,830 346,820ZM393,314L179,528Q179,528 179,528Q179,528 179,528L607,528Q607,528 607,528Q607,528 607,528L393,314ZM792,840Q756,840 731,814.5Q706,789 706,752Q706,725 719.5,701Q733,677 750,654L792,600L836,654Q852,677 866,701Q880,725 880,752Q880,789 854,814.5Q828,840 792,840Z"/> | ||||||
|  | </vector> | ||||||
							
								
								
									
										41
									
								
								app/src/main/res/drawable/delete.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								app/src/main/res/drawable/delete.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     android:width="72dp" | ||||||
|  |     android:height="72dp" | ||||||
|  |     android:viewportWidth="72" | ||||||
|  |     android:viewportHeight="72"> | ||||||
|  |   <path | ||||||
|  |       android:pathData="M31,16l0,-4l10,0l0,4" | ||||||
|  |       android:strokeLineJoin="round" | ||||||
|  |       android:strokeWidth="4" | ||||||
|  |       android:fillColor="#00000000" | ||||||
|  |       android:strokeColor="?attr/colorOnBackground" | ||||||
|  |       android:strokeLineCap="round"/> | ||||||
|  |   <path | ||||||
|  |       android:pathData="M51,25v31c0,2.209 -1.791,4 -4,4H25c-2.209,0 -4,-1.791 -4,-4V25" | ||||||
|  |       android:strokeLineJoin="round" | ||||||
|  |       android:strokeWidth="4" | ||||||
|  |       android:fillColor="#00000000" | ||||||
|  |       android:strokeColor="?attr/colorOnBackground" | ||||||
|  |       android:strokeLineCap="round"/> | ||||||
|  |   <path | ||||||
|  |       android:pathData="M17,16h38v4h-38z" | ||||||
|  |       android:strokeLineJoin="round" | ||||||
|  |       android:strokeWidth="4" | ||||||
|  |       android:fillColor="#00000000" | ||||||
|  |       android:strokeColor="?attr/colorOnBackground" | ||||||
|  |       android:strokeLineCap="round"/> | ||||||
|  |   <path | ||||||
|  |       android:pathData="M41,28.25L41,55" | ||||||
|  |       android:strokeLineJoin="round" | ||||||
|  |       android:strokeWidth="4" | ||||||
|  |       android:fillColor="#00000000" | ||||||
|  |       android:strokeColor="?attr/colorOnBackground" | ||||||
|  |       android:strokeLineCap="round"/> | ||||||
|  |   <path | ||||||
|  |       android:pathData="M31,28.25L31,55" | ||||||
|  |       android:strokeLineJoin="round" | ||||||
|  |       android:strokeWidth="4" | ||||||
|  |       android:fillColor="#00000000" | ||||||
|  |       android:strokeColor="?attr/colorOnBackground" | ||||||
|  |       android:strokeLineCap="round"/> | ||||||
|  | </vector> | ||||||
							
								
								
									
										10
									
								
								app/src/main/res/drawable/edit.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/src/main/res/drawable/edit.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | <vector | ||||||
|  |     android:height="24dp" | ||||||
|  |     android:viewportHeight="24" | ||||||
|  |     android:viewportWidth="24" | ||||||
|  |     android:width="24dp" | ||||||
|  |     xmlns:android="http://schemas.android.com/apk/res/android"> | ||||||
|  |     <path | ||||||
|  |         android:fillColor="@color/white" | ||||||
|  |         android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/> | ||||||
|  | </vector> | ||||||
							
								
								
									
										16
									
								
								app/src/main/res/drawable/group.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								app/src/main/res/drawable/group.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | <vector | ||||||
|  | android:height="24dp" | ||||||
|  | android:viewportHeight="24" | ||||||
|  | android:viewportWidth="24" | ||||||
|  | android:width="24dp" | ||||||
|  | xmlns:android="http://schemas.android.com/apk/res/android" > | ||||||
|  |  | ||||||
|  | <path android:fillColor="?attr/colorOnBackground" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/> | ||||||
|  |  | ||||||
|  | <path android:fillColor="?attr/colorPrimary" android:pathData="M8,14m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/> | ||||||
|  |  | ||||||
|  | <path android:fillColor="?attr/colorPrimary" android:pathData="M12,8m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/> | ||||||
|  |  | ||||||
|  | <path android:fillColor="?attr/colorPrimary" android:pathData="M16,14m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/> | ||||||
|  |  | ||||||
|  | </vector> | ||||||
							
								
								
									
										18
									
								
								app/src/main/res/drawable/licenses.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								app/src/main/res/drawable/licenses.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  | android:width="24dp" | ||||||
|  | android:height="24dp" | ||||||
|  | android:viewportWidth="24.0" | ||||||
|  | android:viewportHeight="24.0"> | ||||||
|  |  | ||||||
|  | <path | ||||||
|  |     android:fillColor="?attr/colorOnBackground" | ||||||
|  |     android:pathData="M22,7h-9v2h9V7zM22,15h-9v2h9V15z"/> | ||||||
|  |  | ||||||
|  | <path | ||||||
|  |     android:fillColor="?attr/colorPrimary" | ||||||
|  |     android:pathData="M5.54,11L2,7.46l1.41,-1.41l2.12,2.12l4.24,-4.24l1.41,1.41L5.54,11z"/> | ||||||
|  |  | ||||||
|  | <path | ||||||
|  |     android:fillColor="?attr/colorPrimary" | ||||||
|  |     android:pathData="M5.54,19L2,15.46l1.41,-1.41l2.12,2.12l4.24,-4.24l1.41,1.41L5.54,19z"/> | ||||||
|  | </vector> | ||||||
							
								
								
									
										16
									
								
								app/src/main/res/drawable/map.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								app/src/main/res/drawable/map.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | <vector | ||||||
|  |     android:height="24dp" | ||||||
|  |     android:viewportHeight="24" | ||||||
|  |     android:viewportWidth="24" | ||||||
|  |     android:width="24dp" | ||||||
|  |     xmlns:android="http://schemas.android.com/apk/res/android" > | ||||||
|  |  | ||||||
|  |     <path | ||||||
|  |         android:fillColor="?attr/colorOnBackground" | ||||||
|  |         android:pathData="M20.5,3l-0.16,0.03L15,5.1 9,3 3.36,4.9c-0.21,0.07 -0.36,0.25 -0.36,0.48L3,20.5c0,0.28 0.22,0.5 0.5,0.5l0.16,-0.03L9,18.9l6,2.1 5.64,-1.9c0.21,-0.07 0.36,-0.25 0.36,-0.48L21,3.5c0,-0.28 -0.22,-0.5 -0.5,-0.5zM10,5.47l4,1.4v11.66l-4,-1.4L10,5.47zM5,6.46l3,-1.01v11.7l-3,1.16L5,6.46zM19,17.54l-3,1.01L16,6.86l3,-1.16v11.84z" /> | ||||||
|  |  | ||||||
|  |     <path | ||||||
|  |         android:fillColor="?attr/colorPrimary" | ||||||
|  |         android:pathData="M15,18.89l-6,-2.11L9,5.11l6,2.11v11.67z"/> | ||||||
|  |  | ||||||
|  | </vector> | ||||||
							
								
								
									
										14
									
								
								app/src/main/res/drawable/palette.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								app/src/main/res/drawable/palette.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | <vector | ||||||
|  |     android:height="24dp" | ||||||
|  |     android:viewportHeight="24" | ||||||
|  |     android:viewportWidth="24" | ||||||
|  |     android:width="24dp" | ||||||
|  |     xmlns:android="http://schemas.android.com/apk/res/android" > | ||||||
|  |  | ||||||
|  |     <path | ||||||
|  |         android:fillColor="?attr/colorOnBackground" | ||||||
|  |         android:pathData="M12,22C6.49,22 2,17.51 2,12S6.49,2 12,2s10,4.04 10,9c0,3.31 -2.69,6 -6,6h-1.77c-0.28,0 -0.5,0.22 -0.5,0.5c0,0.12 0.05,0.23 0.13,0.33c0.41,0.47 0.64,1.06 0.64,1.67C14.5,20.88 13.38,22 12,22zM12,4c-4.41,0 -8,3.59 -8,8s3.59,8 8,8c0.28,0 0.5,-0.22 0.5,-0.5c0,-0.16 -0.08,-0.28 -0.14,-0.35c-0.41,-0.46 -0.63,-1.05 -0.63,-1.65c0,-1.38 1.12,-2.5 2.5,-2.5H16c2.21,0 4,-1.79 4,-4C20,7.14 16.41,4 12,4z" /> | ||||||
|  |     <path | ||||||
|  |         android:fillColor="?attr/colorPrimary" | ||||||
|  |         android:pathData="M17.5,13c-0.83,0 -1.5,-0.67 -1.5,-1.5c0,-0.83 0.67,-1.5 1.5,-1.5s1.5,0.67 1.5,1.5C19,12.33 18.33,13 17.5,13zM14.5,9C13.67,9 13,8.33 13,7.5C13,6.67 13.67,6 14.5,6S16,6.67 16,7.5C16,8.33 15.33,9 14.5,9zM5,11.5C5,10.67 5.67,10 6.5,10S8,10.67 8,11.5C8,12.33 7.33,13 6.5,13S5,12.33 5,11.5zM11,7.5C11,8.33 10.33,9 9.5,9S8,8.33 8,7.5C8,6.67 8.67,6 9.5,6S11,6.67 11,7.5z" /> | ||||||
|  | </vector> | ||||||
							
								
								
									
										14
									
								
								app/src/main/res/drawable/stats.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								app/src/main/res/drawable/stats.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | <vector | ||||||
|  | android:height="24dp" | ||||||
|  | android:viewportHeight="960" | ||||||
|  | android:viewportWidth="960" | ||||||
|  | android:width="24dp" | ||||||
|  | xmlns:android="http://schemas.android.com/apk/res/android" > | ||||||
|  |  | ||||||
|  | <path | ||||||
|  |     android:fillColor="?attr/colorOnBackground" | ||||||
|  |     android:pathData="M105,727L40,680L240,360L360,500L520,240L629,403Q606,404 585.5,408.5Q565,413 545,421L523,388L371,635L250,494L105,727ZM732,423Q713,415 692.5,410Q672,405 650,404L855,80L920,127L732,423Z" /> | ||||||
|  | <path | ||||||
|  |     android:fillColor="?attr/colorPrimary" | ||||||
|  |     android:pathData="M863,920L738,795Q718,809 693.5,816Q669,823 643,823Q568,823 515.5,770.5Q463,718 463,643Q463,568 515.5,515.5Q568,463 643,463Q718,463 770.5,515.5Q823,568 823,643Q823,669 816,693.5Q809,718 795,739L920,863L863,920ZM643,743Q685,743 714,714Q743,685 743,643Q743,601 714,572Q685,543 643,543Q601,543 572,572Q543,601 543,643Q543,685 572,714Q601,743 643,743Z" /> | ||||||
|  | </vector> | ||||||
							
								
								
									
										12
									
								
								app/src/main/res/drawable/zoomin.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								app/src/main/res/drawable/zoomin.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | <vector | ||||||
|  | android:height="24dp" | ||||||
|  | android:viewportHeight="24" | ||||||
|  | android:viewportWidth="24" | ||||||
|  | android:width="24dp" | ||||||
|  | xmlns:android="http://schemas.android.com/apk/res/android" > | ||||||
|  |  | ||||||
|  | <path android:fillColor="?attr/colorOnBackground" android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/> | ||||||
|  |  | ||||||
|  | <path android:fillColor="?attr/colorPrimary" android:pathData="M12,10h-2v2H9v-2H7V9h2V7h1v2h2v1z"/> | ||||||
|  |  | ||||||
|  | </vector> | ||||||
							
								
								
									
										21
									
								
								app/src/main/res/layout/activity_edit.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								app/src/main/res/layout/activity_edit.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     xmlns:tools="http://schemas.android.com/tools" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="match_parent" | ||||||
|  |     android:orientation="vertical" | ||||||
|  |     android:theme="@style/Theme.Beans" | ||||||
|  |     tools:context=".activity.EditActivity"> | ||||||
|  |  | ||||||
|  |     <com.google.android.material.tabs.TabLayout | ||||||
|  |         android:id="@+id/tab" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" /> | ||||||
|  |  | ||||||
|  |     <androidx.viewpager2.widget.ViewPager2 | ||||||
|  |         android:id="@+id/pager" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="0dp" | ||||||
|  |         android:layout_weight="1" /> | ||||||
|  |  | ||||||
|  | </LinearLayout> | ||||||
							
								
								
									
										14
									
								
								app/src/main/res/layout/activity_main.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								app/src/main/res/layout/activity_main.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     xmlns:tools="http://schemas.android.com/tools" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="match_parent" | ||||||
|  |     android:orientation="vertical" | ||||||
|  |     android:theme="@style/Theme.Beans" | ||||||
|  |     tools:context=".activity.MainActivity"> | ||||||
|  |      | ||||||
|  |     <com.github.chrisbanes.photoview.PhotoView | ||||||
|  |         android:id="@+id/photo_view" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="match_parent" /> | ||||||
|  | </LinearLayout> | ||||||
							
								
								
									
										15
									
								
								app/src/main/res/layout/activity_settings.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								app/src/main/res/layout/activity_settings.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     xmlns:tools="http://schemas.android.com/tools" | ||||||
|  |     android:orientation="vertical" | ||||||
|  |     android:theme="@style/Theme.Beans" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="match_parent" | ||||||
|  |     tools:context=".activity.SettingsActivity"> | ||||||
|  |  | ||||||
|  | <androidx.fragment.app.FragmentContainerView | ||||||
|  |     android:id="@+id/fragment_view" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="match_parent" /> | ||||||
|  |  | ||||||
|  | </LinearLayout> | ||||||
							
								
								
									
										66
									
								
								app/src/main/res/layout/activity_stat.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								app/src/main/res/layout/activity_stat.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||||
|  |     xmlns:tools="http://schemas.android.com/tools" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="match_parent" | ||||||
|  |     android:orientation="vertical" | ||||||
|  |     android:theme="@style/Theme.Beans" | ||||||
|  |     tools:context=".activity.StatsActivity"> | ||||||
|  |  | ||||||
|  |     <com.google.android.material.tabs.TabLayout | ||||||
|  |         android:id="@+id/tab" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" /> | ||||||
|  |  | ||||||
|  |     <androidx.viewpager2.widget.ViewPager2 | ||||||
|  |         android:id="@+id/pager" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="0dp" | ||||||
|  |         android:layout_weight="1" | ||||||
|  |         android:visibility="gone" /> | ||||||
|  |  | ||||||
|  |     <androidx.constraintlayout.widget.ConstraintLayout | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:paddingBottom="10dp"> | ||||||
|  |  | ||||||
|  |         <com.google.android.material.button.MaterialButton | ||||||
|  |             android:id="@+id/group_color" | ||||||
|  |             android:layout_width="0dp" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:layout_marginTop="2dp" | ||||||
|  |             android:layout_marginBottom="2dp" | ||||||
|  |             android:paddingStart="56dp" | ||||||
|  |             android:text="@string/total" | ||||||
|  |             android:textAlignment="textStart" | ||||||
|  |             android:textColor="?attr/colorOnPrimary" | ||||||
|  |             app:cornerRadius="0dp" | ||||||
|  |             app:layout_constraintBottom_toBottomOf="parent" | ||||||
|  |             app:layout_constraintEnd_toEndOf="parent" | ||||||
|  |             app:layout_constraintStart_toStartOf="parent" | ||||||
|  |             app:layout_constraintTop_toTopOf="parent" | ||||||
|  |             tools:ignore="RtlSymmetry" /> | ||||||
|  |  | ||||||
|  |         <com.google.android.material.textview.MaterialTextView | ||||||
|  |             android:id="@+id/name" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="50dp" | ||||||
|  |             android:gravity="start|center_vertical" | ||||||
|  |             android:paddingStart="20dp" | ||||||
|  |             android:paddingEnd="52dp" | ||||||
|  |             android:text="" | ||||||
|  |             android:textColor="?attr/colorOnPrimary" | ||||||
|  |             app:layout_constraintBottom_toBottomOf="@id/group_color" | ||||||
|  |             app:layout_constraintEnd_toEndOf="@id/group_color" | ||||||
|  |             app:layout_constraintTop_toTopOf="@id/group_color" /> | ||||||
|  |  | ||||||
|  |     </androidx.constraintlayout.widget.ConstraintLayout> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     <androidx.recyclerview.widget.RecyclerView | ||||||
|  |         android:id="@+id/stats" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" /> | ||||||
|  |  | ||||||
|  | </LinearLayout> | ||||||
							
								
								
									
										67
									
								
								app/src/main/res/layout/fragment_about.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								app/src/main/res/layout/fragment_about.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     xmlns:tools="http://schemas.android.com/tools" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="match_parent" | ||||||
|  |     tools:context=".activity.fragment.AboutFragment"> | ||||||
|  |  | ||||||
|  |     <LinearLayout | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="match_parent" | ||||||
|  |         android:gravity="center_horizontal" | ||||||
|  |         android:orientation="vertical"> | ||||||
|  |  | ||||||
|  |         <com.google.android.material.imageview.ShapeableImageView | ||||||
|  |             android:layout_width="300dp" | ||||||
|  |             android:layout_height="300dp" | ||||||
|  |             android:layout_marginTop="20dp" | ||||||
|  |             android:contentDescription="@string/logo" | ||||||
|  |             android:src="@drawable/ic_launcher_foreground" /> | ||||||
|  |  | ||||||
|  |         <com.google.android.material.textview.MaterialTextView | ||||||
|  |             android:layout_width="match_parent" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:layout_marginStart="10dp" | ||||||
|  |             android:layout_marginTop="15dp" | ||||||
|  |             android:layout_marginEnd="10dp" | ||||||
|  |             android:layout_marginBottom="10dp" | ||||||
|  |             android:text="@string/app_name" | ||||||
|  |             android:textAlignment="center" | ||||||
|  |             android:textSize="30sp" | ||||||
|  |             android:textStyle="bold" /> | ||||||
|  |  | ||||||
|  |         <com.google.android.material.textview.MaterialTextView | ||||||
|  |             android:layout_width="match_parent" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:layout_marginStart="10dp" | ||||||
|  |             android:layout_marginTop="0dp" | ||||||
|  |             android:layout_marginEnd="10dp" | ||||||
|  |             android:layout_marginBottom="15dp" | ||||||
|  |             android:text="@string/app_version" | ||||||
|  |             android:textAlignment="center" | ||||||
|  |             android:textSize="25sp" /> | ||||||
|  |  | ||||||
|  |         <com.google.android.material.textview.MaterialTextView | ||||||
|  |             android:layout_width="match_parent" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:layout_marginStart="10dp" | ||||||
|  |             android:layout_marginTop="15dp" | ||||||
|  |             android:layout_marginEnd="10dp" | ||||||
|  |             android:layout_marginBottom="15dp" | ||||||
|  |             android:text="@string/beans_is_foss" | ||||||
|  |             android:textAlignment="center" /> | ||||||
|  |  | ||||||
|  |         <com.google.android.material.textview.MaterialTextView | ||||||
|  |             android:layout_width="match_parent" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:layout_marginStart="10dp" | ||||||
|  |             android:layout_marginTop="15dp" | ||||||
|  |             android:layout_marginEnd="10dp" | ||||||
|  |             android:layout_marginBottom="15dp" | ||||||
|  |             android:autoLink="web" | ||||||
|  |             android:text="@string/beans_repo" | ||||||
|  |             android:textAlignment="center" /> | ||||||
|  |  | ||||||
|  |     </LinearLayout> | ||||||
|  |  | ||||||
|  | </androidx.constraintlayout.widget.ConstraintLayout> | ||||||
							
								
								
									
										137
									
								
								app/src/main/res/layout/fragment_edit_groups_add.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								app/src/main/res/layout/fragment_edit_groups_add.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,137 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||||
|  |     xmlns:tools="http://schemas.android.com/tools" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="wrap_content" | ||||||
|  |     android:orientation="vertical" | ||||||
|  |     android:padding="16dp" | ||||||
|  |     tools:context=".activity.fragment.EditGroupAddFragment"> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     <com.google.android.material.textfield.TextInputEditText | ||||||
|  |         android:id="@+id/group_name" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="48dp" | ||||||
|  |         android:autofillHints="" | ||||||
|  |         android:hint="@string/name" | ||||||
|  |         android:inputType="text" /> | ||||||
|  |  | ||||||
|  |     <androidx.constraintlayout.widget.ConstraintLayout | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="match_parent"> | ||||||
|  |  | ||||||
|  |         <View | ||||||
|  |             android:id="@+id/colorView" | ||||||
|  |             android:layout_width="0dp" | ||||||
|  |             android:layout_height="0dp" | ||||||
|  |             android:background="@color/black" | ||||||
|  |             app:layout_constraintBottom_toBottomOf="parent" | ||||||
|  |             app:layout_constraintEnd_toStartOf="@+id/colorR" | ||||||
|  |             app:layout_constraintStart_toStartOf="parent" | ||||||
|  |             app:layout_constraintTop_toTopOf="parent" /> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         <com.google.android.material.slider.Slider | ||||||
|  |             android:id="@+id/colorR" | ||||||
|  |             android:layout_width="0dp" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             app:layout_constraintBottom_toTopOf="@id/colorG" | ||||||
|  |             app:layout_constraintEnd_toEndOf="parent" | ||||||
|  |             app:layout_constraintStart_toEndOf="@id/colorView" | ||||||
|  |             app:layout_constraintTop_toTopOf="parent" | ||||||
|  |             app:thumbColor="@color/red" | ||||||
|  |             app:trackColorActive="@color/red" /> | ||||||
|  |  | ||||||
|  |         <com.google.android.material.slider.Slider | ||||||
|  |             android:id="@+id/colorG" | ||||||
|  |             android:layout_width="0dp" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:foregroundTint="#FF0000" | ||||||
|  |             app:layout_constraintBottom_toTopOf="@id/colorB" | ||||||
|  |             app:layout_constraintEnd_toEndOf="parent" | ||||||
|  |             app:layout_constraintStart_toEndOf="@id/colorView" | ||||||
|  |             app:layout_constraintTop_toBottomOf="@id/colorR" | ||||||
|  |             app:thumbColor="@color/green" | ||||||
|  |             app:trackColorActive="@color/green" /> | ||||||
|  |  | ||||||
|  |         <com.google.android.material.slider.Slider | ||||||
|  |             android:id="@+id/colorB" | ||||||
|  |             android:layout_width="0dp" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             app:layout_constraintBottom_toBottomOf="parent" | ||||||
|  |             app:layout_constraintEnd_toEndOf="parent" | ||||||
|  |             app:layout_constraintStart_toEndOf="@id/colorView" | ||||||
|  |             app:layout_constraintTop_toBottomOf="@id/colorG" | ||||||
|  |             app:thumbColor="@color/blue" | ||||||
|  |             app:trackColorActive="@color/blue" /> | ||||||
|  |  | ||||||
|  |     </androidx.constraintlayout.widget.ConstraintLayout> | ||||||
|  |  | ||||||
|  |     <LinearLayout | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="match_parent"> | ||||||
|  |  | ||||||
|  |         <com.google.android.material.textview.MaterialTextView | ||||||
|  |             android:id="@+id/textView" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:labelFor="@id/group_color" | ||||||
|  |             android:text="@string/hashtag" /> | ||||||
|  |  | ||||||
|  |         <com.google.android.material.textfield.TextInputEditText | ||||||
|  |             android:id="@+id/group_color" | ||||||
|  |             android:layout_width="wrap_content" | ||||||
|  |             android:layout_height="48dp" | ||||||
|  |             android:autofillHints="" | ||||||
|  |             android:hint="@string/color_rrggbb" | ||||||
|  |             android:inputType="text" | ||||||
|  |             android:maxLength="6" /> | ||||||
|  |     </LinearLayout> | ||||||
|  |  | ||||||
|  |     <androidx.constraintlayout.widget.ConstraintLayout | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="match_parent" | ||||||
|  |         android:layout_marginTop="10dp"> | ||||||
|  |  | ||||||
|  |         <com.google.android.material.button.MaterialButton | ||||||
|  |             android:id="@+id/btnDelete" | ||||||
|  |             android:layout_width="52dp" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:paddingLeft="6dp" | ||||||
|  |             android:paddingRight="6dp" | ||||||
|  |             android:tooltipText="@string/delete" | ||||||
|  |             app:icon="@drawable/delete" | ||||||
|  |             app:iconGravity="textStart" | ||||||
|  |             app:iconPadding="0dp" | ||||||
|  |             app:layout_constraintBottom_toBottomOf="parent" | ||||||
|  |             app:layout_constraintStart_toStartOf="parent" | ||||||
|  |             app:layout_constraintTop_toTopOf="parent" /> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         <com.google.android.material.button.MaterialButton | ||||||
|  |             android:id="@+id/btnCancel" | ||||||
|  |             android:layout_width="80dp" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:layout_marginEnd="6dp" | ||||||
|  |             android:paddingLeft="6dp" | ||||||
|  |             android:paddingRight="6dp" | ||||||
|  |             android:text="@string/cancel" | ||||||
|  |             app:layout_constraintBottom_toBottomOf="parent" | ||||||
|  |             app:layout_constraintEnd_toStartOf="@+id/btnOk" | ||||||
|  |             app:layout_constraintTop_toTopOf="parent" /> | ||||||
|  |  | ||||||
|  |         <com.google.android.material.button.MaterialButton | ||||||
|  |             android:id="@+id/btnOk" | ||||||
|  |             android:layout_width="52dp" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:paddingLeft="6dp" | ||||||
|  |             android:paddingRight="6dp" | ||||||
|  |             android:text="@string/ok" | ||||||
|  |             app:layout_constraintBottom_toBottomOf="parent" | ||||||
|  |             app:layout_constraintEnd_toEndOf="parent" | ||||||
|  |             app:layout_constraintTop_toTopOf="parent" /> | ||||||
|  |  | ||||||
|  |     </androidx.constraintlayout.widget.ConstraintLayout> | ||||||
|  |  | ||||||
|  | </LinearLayout> | ||||||
							
								
								
									
										26
									
								
								app/src/main/res/layout/fragment_edit_places.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								app/src/main/res/layout/fragment_edit_places.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||||
|  |     xmlns:tools="http://schemas.android.com/tools" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="match_parent" | ||||||
|  |     tools:context=".activity.fragment.EditPlaceFragment"> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     <androidx.core.widget.NestedScrollView | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="match_parent" | ||||||
|  |         android:scrollbars="vertical" | ||||||
|  |         app:layout_constraintBottom_toBottomOf="parent" | ||||||
|  |         app:layout_constraintEnd_toEndOf="parent" | ||||||
|  |         app:layout_constraintStart_toStartOf="parent" | ||||||
|  |         app:layout_constraintTop_toTopOf="parent"> | ||||||
|  |  | ||||||
|  |         <androidx.recyclerview.widget.RecyclerView | ||||||
|  |             android:id="@+id/list" | ||||||
|  |             android:layout_width="match_parent" | ||||||
|  |             android:layout_height="match_parent" | ||||||
|  |             android:nestedScrollingEnabled="false" | ||||||
|  |             android:scrollbars="vertical" /> | ||||||
|  |     </androidx.core.widget.NestedScrollView> | ||||||
|  | </androidx.constraintlayout.widget.ConstraintLayout> | ||||||
							
								
								
									
										59
									
								
								app/src/main/res/layout/fragment_edit_places_colors.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								app/src/main/res/layout/fragment_edit_places_colors.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="wrap_content" | ||||||
|  |     android:orientation="vertical" | ||||||
|  |     android:padding="16dp"> | ||||||
|  |  | ||||||
|  |     <TextView | ||||||
|  |         android:id="@+id/warning_text" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:layout_marginBottom="10dp" /> | ||||||
|  |  | ||||||
|  |     <ScrollView | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:orientation="vertical" | ||||||
|  |         android:padding="4dp"> | ||||||
|  |  | ||||||
|  |         <androidx.recyclerview.widget.RecyclerView | ||||||
|  |             android:id="@+id/groups_color" | ||||||
|  |             android:layout_width="match_parent" | ||||||
|  |             android:layout_height="wrap_content" /> | ||||||
|  |     </ScrollView> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     <androidx.constraintlayout.widget.ConstraintLayout | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:layout_marginTop="10dp"> | ||||||
|  |  | ||||||
|  |         <com.google.android.material.button.MaterialButton | ||||||
|  |             android:id="@+id/btnAdd" | ||||||
|  |             android:layout_width="64dp" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:paddingLeft="6dp" | ||||||
|  |             android:paddingRight="6dp" | ||||||
|  |             android:text="@string/add" | ||||||
|  |             app:layout_constraintBottom_toBottomOf="parent" | ||||||
|  |             app:layout_constraintStart_toStartOf="parent" | ||||||
|  |             app:layout_constraintTop_toTopOf="parent" /> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         <com.google.android.material.button.MaterialButton | ||||||
|  |             android:id="@+id/btnClear" | ||||||
|  |             android:layout_width="64dp" | ||||||
|  |             android:layout_height="wrap_content" | ||||||
|  |             android:paddingLeft="6dp" | ||||||
|  |             android:paddingRight="6dp" | ||||||
|  |             android:text="@string/clear" | ||||||
|  |             app:layout_constraintBottom_toBottomOf="parent" | ||||||
|  |             app:layout_constraintEnd_toEndOf="parent" | ||||||
|  |             app:layout_constraintTop_toTopOf="parent" /> | ||||||
|  |  | ||||||
|  |     </androidx.constraintlayout.widget.ConstraintLayout> | ||||||
|  |  | ||||||
|  | </LinearLayout> | ||||||
|  |  | ||||||
							
								
								
									
										13
									
								
								app/src/main/res/layout/fragment_license.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								app/src/main/res/layout/fragment_license.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     xmlns:tools="http://schemas.android.com/tools" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="match_parent" | ||||||
|  |     tools:context=".activity.fragment.LicenseFragment"> | ||||||
|  |  | ||||||
|  |     <androidx.fragment.app.FragmentContainerView | ||||||
|  |         android:id="@+id/license_fragment_view" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="match_parent" /> | ||||||
|  |  | ||||||
|  | </androidx.constraintlayout.widget.ConstraintLayout> | ||||||
							
								
								
									
										52
									
								
								app/src/main/res/layout/item_list_geoloc.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								app/src/main/res/layout/item_list_geoloc.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  |  | ||||||
|  | <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="wrap_content"> | ||||||
|  |  | ||||||
|  |     <com.google.android.material.button.MaterialButton | ||||||
|  |         android:id="@+id/textView" | ||||||
|  |         android:layout_width="0dp" | ||||||
|  |         android:layout_height="50dp" | ||||||
|  |         android:clickable="true" | ||||||
|  |         android:focusable="true" | ||||||
|  |         android:gravity="start|center_vertical" | ||||||
|  |         android:insetTop="4dp" | ||||||
|  |         android:insetBottom="4dp" | ||||||
|  |         android:paddingStart="20dp" | ||||||
|  |         android:paddingEnd="20dp" | ||||||
|  |         android:textAllCaps="false" | ||||||
|  |         android:textAppearance="?attr/textAppearanceBody2" | ||||||
|  |         android:textColor="?attr/colorOnBackground" | ||||||
|  |  | ||||||
|  |         app:cornerRadius="4dp" | ||||||
|  |         app:layout_constraintBottom_toBottomOf="@id/checkBox" | ||||||
|  |         app:layout_constraintEnd_toStartOf="@id/checkBox" | ||||||
|  |         app:layout_constraintStart_toStartOf="parent" | ||||||
|  |         app:layout_constraintTop_toTopOf="parent" /> | ||||||
|  |  | ||||||
|  |     <com.google.android.material.textview.MaterialTextView | ||||||
|  |         android:id="@+id/count" | ||||||
|  |         android:layout_width="wrap_content" | ||||||
|  |         android:layout_height="50dp" | ||||||
|  |         android:gravity="start|center_vertical" | ||||||
|  |         android:paddingStart="20dp" | ||||||
|  |         android:paddingEnd="20dp" | ||||||
|  |         app:layout_constraintBottom_toBottomOf="@id/checkBox" | ||||||
|  |         app:layout_constraintEnd_toStartOf="@id/checkBox" | ||||||
|  |         app:layout_constraintTop_toTopOf="parent" /> | ||||||
|  |  | ||||||
|  |     <com.google.android.material.checkbox.MaterialCheckBox | ||||||
|  |         android:id="@+id/checkBox" | ||||||
|  |         android:layout_width="50dp" | ||||||
|  |         android:layout_height="50dp" | ||||||
|  |         app:checkedState="indeterminate" | ||||||
|  |         app:layout_constraintBottom_toBottomOf="@id/textView" | ||||||
|  |         app:layout_constraintEnd_toEndOf="parent" | ||||||
|  |         app:layout_constraintHorizontal_bias="1" | ||||||
|  |         app:layout_constraintStart_toEndOf="@id/textView" | ||||||
|  |         app:layout_constraintTop_toTopOf="@id/textView" | ||||||
|  |         app:layout_constraintVertical_bias="0.5" /> | ||||||
|  |  | ||||||
|  | </androidx.constraintlayout.widget.ConstraintLayout> | ||||||
							
								
								
									
										34
									
								
								app/src/main/res/layout/item_list_group.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								app/src/main/res/layout/item_list_group.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="wrap_content"> | ||||||
|  |  | ||||||
|  |     <com.google.android.material.button.MaterialButton | ||||||
|  |         android:id="@+id/group_color" | ||||||
|  |         android:layout_width="0dp" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:layout_marginStart="32dp" | ||||||
|  |         android:layout_marginTop="2dp" | ||||||
|  |         android:layout_marginEnd="32dp" | ||||||
|  |         android:layout_marginBottom="2dp" | ||||||
|  |         android:textAlignment="textStart" | ||||||
|  |         android:textColor="?attr/colorOnPrimary" | ||||||
|  |         app:layout_constraintBottom_toBottomOf="parent" | ||||||
|  |         app:layout_constraintEnd_toEndOf="parent" | ||||||
|  |         app:layout_constraintStart_toStartOf="parent" | ||||||
|  |         app:layout_constraintTop_toTopOf="parent" /> | ||||||
|  |  | ||||||
|  |     <com.google.android.material.textview.MaterialTextView | ||||||
|  |         android:id="@+id/name" | ||||||
|  |         android:layout_width="wrap_content" | ||||||
|  |         android:layout_height="50dp" | ||||||
|  |         android:gravity="start|center_vertical" | ||||||
|  |         android:paddingStart="20dp" | ||||||
|  |         android:paddingEnd="20dp" | ||||||
|  |         android:textColor="?attr/colorOnPrimary" | ||||||
|  |         app:layout_constraintBottom_toBottomOf="@id/group_color" | ||||||
|  |         app:layout_constraintEnd_toEndOf="@id/group_color" | ||||||
|  |         app:layout_constraintTop_toTopOf="@id/group_color" /> | ||||||
|  |  | ||||||
|  | </androidx.constraintlayout.widget.ConstraintLayout> | ||||||
							
								
								
									
										13
									
								
								app/src/main/res/menu/menu_edit.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								app/src/main/res/menu/menu_edit.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | <menu xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||||
|  |     xmlns:tools="http://schemas.android.com/tools" | ||||||
|  |     tools:context="net.helcel.beans.activity.EditActivity" > | ||||||
|  |  | ||||||
|  |     <item | ||||||
|  |         android:id="@+id/action_color" | ||||||
|  |         android:orderInCategory="100" | ||||||
|  |         android:icon="@drawable/color" | ||||||
|  |         android:title="@string/action_color" | ||||||
|  |         app:showAsAction="ifRoom" /> | ||||||
|  |  | ||||||
|  | </menu> | ||||||
							
								
								
									
										25
									
								
								app/src/main/res/menu/menu_main.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								app/src/main/res/menu/menu_main.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | <menu xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||||
|  |     xmlns:tools="http://schemas.android.com/tools" | ||||||
|  |     tools:context="net.helcel.beans.activity.MainActivity" > | ||||||
|  |  | ||||||
|  |     <item | ||||||
|  |         android:id="@+id/action_edit" | ||||||
|  |         android:orderInCategory="100" | ||||||
|  |         android:icon="@drawable/edit" | ||||||
|  |         android:title="@string/action_edit" | ||||||
|  |         app:showAsAction="ifRoom" /> | ||||||
|  |     <item | ||||||
|  |         android:id="@+id/action_stats" | ||||||
|  |         android:layout_width="wrap_content" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:orderInCategory="100" | ||||||
|  |         android:title="@string/action_stat" | ||||||
|  |         app:showAsAction="never" /> | ||||||
|  |     <item | ||||||
|  |         android:id="@+id/action_settings" | ||||||
|  |         android:orderInCategory="100" | ||||||
|  |         android:title="@string/action_settings" | ||||||
|  |         app:showAsAction="never" /> | ||||||
|  |  | ||||||
|  | </menu> | ||||||
							
								
								
									
										25
									
								
								app/src/main/res/values/arrays.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								app/src/main/res/values/arrays.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <resources> | ||||||
|  |     <string-array name="entries_theme"> | ||||||
|  |         <item>@string/system</item> | ||||||
|  |         <item>@string/light</item> | ||||||
|  |         <item>@string/dark</item> | ||||||
|  |     </string-array> | ||||||
|  |  | ||||||
|  |     <string-array name="entries_stats"> | ||||||
|  |         <item>@string/counters</item> | ||||||
|  |         <item>@string/percentages</item> | ||||||
|  |     </string-array> | ||||||
|  |  | ||||||
|  |     <string-array name="entries_onoff"> | ||||||
|  |         <item>@string/on</item> | ||||||
|  |         <item>@string/off</item> | ||||||
|  |     </string-array> | ||||||
|  |  | ||||||
|  |     <string-array name="map_projection"> | ||||||
|  |         <item>@string/azimuthalequidistant</item> | ||||||
|  |         <item>@string/loximuthal</item> | ||||||
|  |         <item>@string/mercator</item> | ||||||
|  |  | ||||||
|  |     </string-array> | ||||||
|  | </resources> | ||||||
| @@ -1,10 +1,10 @@ | |||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <resources> | <resources> | ||||||
|  |     <string name="app_name">Beans</string> | ||||||
|  |     <string name="app_version">1.0</string> | ||||||
|     <string name="action_settings">Settings</string> |     <string name="action_settings">Settings</string> | ||||||
|     <string name="action_stat">Stats</string> |     <string name="action_stat">Stats</string> | ||||||
|     <string name="action_edit">Edit</string> |     <string name="action_edit">Edit</string> | ||||||
|     <string name="action_add">Add</string> |  | ||||||
|     <string name="action_clear">Clear</string> |  | ||||||
|     <string name="action_color">Color</string> |     <string name="action_color">Color</string> | ||||||
|     <string name="key_theme">App theme</string> |     <string name="key_theme">App theme</string> | ||||||
|     <string name="system">System</string> |     <string name="system">System</string> | ||||||
| @@ -18,17 +18,16 @@ | |||||||
|     <string name="key_regional">Regional</string> |     <string name="key_regional">Regional</string> | ||||||
|     <string name="about">About</string> |     <string name="about">About</string> | ||||||
|     <string name="beans_is_foss">Beans is free and open source software, licensed under the GNU General Public License (version 3 or later)</string> |     <string name="beans_is_foss">Beans is free and open source software, licensed under the GNU General Public License (version 3 or later)</string> | ||||||
|     <string name="beans_repo_uri">https://github.com/helcel-net/beans</string> |     <string name="beans_repo">Project repository: https://github.com/helcel-net/beans\n Feel free to report issues or contribute to the project.</string> | ||||||
|     <string name="beans_repo">Project repository: %1$s\n Feel free to report issues or contribute.</string> |  | ||||||
|     <string name="foss_licenses">Free and open source dependencies and licenses</string> |     <string name="foss_licenses">Free and open source dependencies and licenses</string> | ||||||
|     <string name="about_beans">About the Beans application</string> |     <string name="about_beans">About the Beans application</string> | ||||||
|     <string name="edit_group">Select the group to assign.</string> |     <string name="edit_group">Select the group to assign. Long press on a group to edit its name and color.</string> | ||||||
|     <string name="edit_group_sub">Long press on a group to edit its name and color.</string> |  | ||||||
|     <string name="select_group">Select the group to keep.</string> |  | ||||||
|     <string name="select_group_sub">All others will be deleted and its mappings reassigned to the group you choose here.</string> |  | ||||||
|     <string name="delete_group">Are your sure you want to delete this group and remove all its country mappings?</string> |     <string name="delete_group">Are your sure you want to delete this group and remove all its country mappings?</string> | ||||||
|  |     <string name="select_group">Select one group you want to keep. All others will be deleted and its mappings reassigned to the group you choose here.</string> | ||||||
|     <string name="delete_regions">Are you sure you want to disable regions and reassign all regional mappings to the corresponding countries?</string> |     <string name="delete_regions">Are you sure you want to disable regions and reassign all regional mappings to the corresponding countries?</string> | ||||||
|  |     <string name="add">Add</string> | ||||||
|  |     <string name="clear">Clear</string> | ||||||
|  |     <string name="logo">Logo</string> | ||||||
|     <string name="name">Name</string> |     <string name="name">Name</string> | ||||||
|     <string name="rate">%1$d/%2$d</string> |     <string name="rate">%1$d/%2$d</string> | ||||||
|     <string name="rate_with_unit">%1$d / %2$d %3$s</string> |     <string name="rate_with_unit">%1$d / %2$d %3$s</string> | ||||||
| @@ -40,10 +39,9 @@ | |||||||
|     <string name="off">Off</string> |     <string name="off">Off</string> | ||||||
|     <string name="delete">Delete</string> |     <string name="delete">Delete</string> | ||||||
|     <string name="cancel">Cancel</string> |     <string name="cancel">Cancel</string> | ||||||
|     <string name="ok">OK</string> |     <string name="ok">Ok</string> | ||||||
|     <string name="total">Total</string> |     <string name="total">Total</string> | ||||||
|     <string name="uncategorized">Uncategorized</string> |     <string name="uncategorized">Uncategorized</string> | ||||||
|  |  | ||||||
|     <string name="azimuthalequidistant">Azimuthal Equidistant</string> |     <string name="azimuthalequidistant">Azimuthal Equidistant</string> | ||||||
|     <string name="mercator">Mercator</string> |     <string name="mercator">Mercator</string> | ||||||
|     <string name="loximuthal">Loximuthal</string> |     <string name="loximuthal">Loximuthal</string> | ||||||
|   | |||||||
							
								
								
									
										32
									
								
								app/src/main/res/values/themes.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								app/src/main/res/values/themes.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | <resources> | ||||||
|  |     <style name="Theme.Beans" parent="Theme.Material3.DayNight"> | ||||||
|  |         <item name="colorPrimary">@color/blue</item> | ||||||
|  |         <item name="background">@color/darkgray</item> | ||||||
|  |         <item name="android:colorPrimary">?attr/colorPrimary</item> | ||||||
|  |         <item name="android:panelColorBackground">@color/lightgray</item> | ||||||
|  |         <item name="android:statusBarColor">?attr/colorPrimary</item> | ||||||
|  |  | ||||||
|  |         <item name="checkboxStyle">@style/Theme.Beans.CheckBox</item> | ||||||
|  |         <item name="actionBarStyle">@style/Theme.Beans.ActionBar</item> | ||||||
|  |         <item name="android:actionOverflowButtonStyle">@style/Theme.Beans.ActionBar.ButtonOverflow</item> | ||||||
|  |     </style> | ||||||
|  |  | ||||||
|  |     <style name="Theme.Beans.CheckBox" parent="Widget.Material3.CompoundButton.CheckBox"> | ||||||
|  |     </style> | ||||||
|  |  | ||||||
|  |     <style name="Theme.Beans.ActionBar" parent="Widget.Material3.ActionBar.Solid"> | ||||||
|  |         <item name="background">?attr/colorPrimary</item> | ||||||
|  |         <item name="titleTextStyle">@style/Theme.Beans.ActionBar.Text</item> | ||||||
|  |         <item name="android:tint">@color/white</item> | ||||||
|  |         <item name="actionMenuTextColor">@color/white</item> | ||||||
|  |         <item name="homeAsUpIndicator">@drawable/back</item> | ||||||
|  |     </style> | ||||||
|  |  | ||||||
|  |     <style name="Theme.Beans.ActionBar.Text" parent="TextAppearance.Material3.ActionBar.Title"> | ||||||
|  |         <item name="android:textColor">@color/white</item> | ||||||
|  |     </style> | ||||||
|  |  | ||||||
|  |     <style name="Theme.Beans.ActionBar.ButtonOverflow" parent="Widget.Material3.Search.ActionButton.Overflow"> | ||||||
|  |         <item name="android:tint">@color/white</item> | ||||||
|  |     </style> | ||||||
|  | </resources> | ||||||
							
								
								
									
										73
									
								
								app/src/main/res/xml/fragment_settings.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								app/src/main/res/xml/fragment_settings.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||||
|  |     android:theme="@style/Theme.Beans"> | ||||||
|  |  | ||||||
|  |     <ListPreference | ||||||
|  |         app:defaultValue="@string/system" | ||||||
|  |         app:enabled="true" | ||||||
|  |         app:entries="@array/entries_theme" | ||||||
|  |         app:entryValues="@array/entries_theme" | ||||||
|  |         app:icon="@drawable/palette" | ||||||
|  |         app:key="@string/key_theme" | ||||||
|  |         app:title="@string/key_theme" | ||||||
|  |         app:useSimpleSummaryProvider="true" /> | ||||||
|  |  | ||||||
|  |     <ListPreference | ||||||
|  |         app:defaultValue="@string/mercator" | ||||||
|  |         app:enabled="true" | ||||||
|  |         app:entries="@array/map_projection" | ||||||
|  |         app:entryValues="@array/map_projection" | ||||||
|  |         app:icon="@drawable/map" | ||||||
|  |         app:key="@string/key_projection" | ||||||
|  |         app:title="@string/key_projection" | ||||||
|  |         app:useSimpleSummaryProvider="true" /> | ||||||
|  |  | ||||||
|  |     <ListPreference | ||||||
|  |         app:defaultValue="@string/counters" | ||||||
|  |         app:enabled="true" | ||||||
|  |         app:entries="@array/entries_stats" | ||||||
|  |         app:entryValues="@array/entries_stats" | ||||||
|  |         app:icon="@drawable/stats" | ||||||
|  |         app:key="@string/key_stats" | ||||||
|  |         app:title="@string/key_stats" | ||||||
|  |         app:useSimpleSummaryProvider="true" /> | ||||||
|  |  | ||||||
|  |     <ListPreference | ||||||
|  |         app:defaultValue="@string/off" | ||||||
|  |         app:enabled="true" | ||||||
|  |         app:allowDividerAbove="true" | ||||||
|  |         app:entries="@array/entries_onoff" | ||||||
|  |         app:entryValues="@array/entries_onoff" | ||||||
|  |         app:icon="@drawable/group" | ||||||
|  |         app:key="@string/key_group" | ||||||
|  |         app:title="@string/key_group" | ||||||
|  |         app:useSimpleSummaryProvider="true" /> | ||||||
|  |  | ||||||
|  |     <ListPreference | ||||||
|  |         app:defaultValue="@string/off" | ||||||
|  |         app:enabled="true" | ||||||
|  |         app:entries="@array/entries_onoff" | ||||||
|  |         app:entryValues="@array/entries_onoff" | ||||||
|  |         app:icon="@drawable/zoomin" | ||||||
|  |         app:key="@string/key_regional" | ||||||
|  |         app:title="@string/key_regional" | ||||||
|  |         app:useSimpleSummaryProvider="true" /> | ||||||
|  |  | ||||||
|  |     <Preference | ||||||
|  |         android:summary="@string/foss_licenses" | ||||||
|  |         app:enabled="true" | ||||||
|  |         app:allowDividerAbove="true" | ||||||
|  |         app:icon="@drawable/licenses" | ||||||
|  |         app:key="@string/licenses" | ||||||
|  |         app:title="@string/licenses" /> | ||||||
|  |  | ||||||
|  |     <Preference | ||||||
|  |         android:summary="@string/about_beans" | ||||||
|  |         app:enabled="true" | ||||||
|  |         app:icon="@drawable/about" | ||||||
|  |         app:key="@string/about" | ||||||
|  |         app:title="@string/about" /> | ||||||
|  |  | ||||||
|  |  | ||||||
|  | </PreferenceScreen> | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| // Top-level build file where you can add configuration options common to all sub-projects/modules. | // Top-level build file where you can add configuration options common to all sub-projects/modules. | ||||||
| plugins { | plugins { | ||||||
|     id 'com.android.application' version '8.13.0' apply false |     id 'com.android.application' version '8.4.0' apply false | ||||||
|     id 'com.android.library' version '8.13.0' apply false |     id 'com.android.library' version '8.4.0' apply false | ||||||
|     id 'org.jetbrains.kotlin.android' version '2.2.20' apply false |     id 'org.jetbrains.kotlin.android' version '1.9.23' apply false | ||||||
| } | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								gradle/wrapper/gradle-wrapper.jar
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								gradle/wrapper/gradle-wrapper.jar
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										4
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| @@ -1,8 +1,8 @@ | |||||||
| distributionBase=GRADLE_USER_HOME | distributionBase=GRADLE_USER_HOME | ||||||
| distributionPath=wrapper/dists | distributionPath=wrapper/dists | ||||||
| distributionSha256Sum=a17ddd85a26b6a7f5ddb71ff8b05fc5104c0202c6e64782429790c933686c806 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip | ||||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip |  | ||||||
| networkTimeout=10000 | networkTimeout=10000 | ||||||
| validateDistributionUrl=true | validateDistributionUrl=true | ||||||
| zipStoreBase=GRADLE_USER_HOME | zipStoreBase=GRADLE_USER_HOME | ||||||
| zipStorePath=wrapper/dists | zipStorePath=wrapper/dists | ||||||
|  | distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								gradlew
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								gradlew
									
									
									
									
										vendored
									
									
								
							| @@ -1,7 +1,7 @@ | |||||||
| #!/bin/sh | #!/bin/sh | ||||||
|  |  | ||||||
| # | # | ||||||
| # Copyright © 2015 the original authors. | # Copyright © 2015-2021 the original authors. | ||||||
| # | # | ||||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | # Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
| # you may not use this file except in compliance with the License. | # you may not use this file except in compliance with the License. | ||||||
| @@ -15,8 +15,6 @@ | |||||||
| # See the License for the specific language governing permissions and | # See the License for the specific language governing permissions and | ||||||
| # limitations under the License. | # limitations under the License. | ||||||
| # | # | ||||||
| # SPDX-License-Identifier: Apache-2.0 |  | ||||||
| # |  | ||||||
|  |  | ||||||
| ############################################################################## | ############################################################################## | ||||||
| # | # | ||||||
| @@ -57,7 +55,7 @@ | |||||||
| #       Darwin, MinGW, and NonStop. | #       Darwin, MinGW, and NonStop. | ||||||
| # | # | ||||||
| #   (3) This script is generated from the Groovy template | #   (3) This script is generated from the Groovy template | ||||||
| #       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt | #       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt | ||||||
| #       within the Gradle project. | #       within the Gradle project. | ||||||
| # | # | ||||||
| #       You can find Gradle at https://github.com/gradle/gradle/. | #       You can find Gradle at https://github.com/gradle/gradle/. | ||||||
| @@ -86,7 +84,7 @@ done | |||||||
| # shellcheck disable=SC2034 | # shellcheck disable=SC2034 | ||||||
| APP_BASE_NAME=${0##*/} | APP_BASE_NAME=${0##*/} | ||||||
| # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) | ||||||
| APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit | ||||||
|  |  | ||||||
| # Use the maximum available, or set MAX_FD != -1 to use that value. | # Use the maximum available, or set MAX_FD != -1 to use that value. | ||||||
| MAX_FD=maximum | MAX_FD=maximum | ||||||
| @@ -114,6 +112,7 @@ case "$( uname )" in                #( | |||||||
|   NONSTOP* )        nonstop=true ;; |   NONSTOP* )        nonstop=true ;; | ||||||
| esac | esac | ||||||
|  |  | ||||||
|  | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar | ||||||
|  |  | ||||||
|  |  | ||||||
| # Determine the Java command to use to start the JVM. | # Determine the Java command to use to start the JVM. | ||||||
| @@ -171,6 +170,7 @@ fi | |||||||
| # For Cygwin or MSYS, switch paths to Windows format before running java | # For Cygwin or MSYS, switch paths to Windows format before running java | ||||||
| if "$cygwin" || "$msys" ; then | if "$cygwin" || "$msys" ; then | ||||||
|     APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) |     APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) | ||||||
|  |     CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) | ||||||
|  |  | ||||||
|     JAVACMD=$( cygpath --unix "$JAVACMD" ) |     JAVACMD=$( cygpath --unix "$JAVACMD" ) | ||||||
|  |  | ||||||
| @@ -203,14 +203,15 @@ fi | |||||||
| DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' | ||||||
|  |  | ||||||
| # Collect all arguments for the java command: | # Collect all arguments for the java command: | ||||||
| #   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, | #   * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, | ||||||
| #     and any embedded shellness will be escaped. | #     and any embedded shellness will be escaped. | ||||||
| #   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be | #   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be | ||||||
| #     treated as '${Hostname}' itself on the command line. | #     treated as '${Hostname}' itself on the command line. | ||||||
|  |  | ||||||
| set -- \ | set -- \ | ||||||
|         "-Dorg.gradle.appname=$APP_BASE_NAME" \ |         "-Dorg.gradle.appname=$APP_BASE_NAME" \ | ||||||
|         -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ |         -classpath "$CLASSPATH" \ | ||||||
|  |         org.gradle.wrapper.GradleWrapperMain \ | ||||||
|         "$@" |         "$@" | ||||||
|  |  | ||||||
| # Stop when "xargs" is not available. | # Stop when "xargs" is not available. | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								gradlew.bat
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								gradlew.bat
									
									
									
									
										vendored
									
									
								
							| @@ -13,8 +13,6 @@ | |||||||
| @rem See the License for the specific language governing permissions and | @rem See the License for the specific language governing permissions and | ||||||
| @rem limitations under the License. | @rem limitations under the License. | ||||||
| @rem | @rem | ||||||
| @rem SPDX-License-Identifier: Apache-2.0 |  | ||||||
| @rem |  | ||||||
|  |  | ||||||
| @if "%DEBUG%"=="" @echo off | @if "%DEBUG%"=="" @echo off | ||||||
| @rem ########################################################################## | @rem ########################################################################## | ||||||
| @@ -70,10 +68,11 @@ goto fail | |||||||
| :execute | :execute | ||||||
| @rem Setup the command line | @rem Setup the command line | ||||||
|  |  | ||||||
|  | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar | ||||||
|  |  | ||||||
|  |  | ||||||
| @rem Execute Gradle | @rem Execute Gradle | ||||||
| "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* | ||||||
|  |  | ||||||
| :end | :end | ||||||
| @rem End local scope for the variables with windows NT shell | @rem End local scope for the variables with windows NT shell | ||||||
|   | |||||||
| @@ -2,10 +2,10 @@ Beans is a scratchmap of the world for Android. | |||||||
|  |  | ||||||
| Keep track of your discovery of the world on a colorful visual map. | Keep track of your discovery of the world on a colorful visual map. | ||||||
|  |  | ||||||
| * Color a map of places based on custom labels | • Color a map of places based on custom labels | ||||||
| * Country/State based coloring | • Country/State based coloring | ||||||
| * Single/Multi color modes | • Single/Multi color modes | ||||||
| * Different map projections available | • Different map projections available | ||||||
| * Small & Fast | • Small & Fast | ||||||
| * Statistics (WIP) | • Statistics (WIP) | ||||||
| * 100% Free and Open Source software, with no proprietary dependencies | • 100% Free and Open Source software, with no proprietary dependencies | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| { | { | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@turf/area": "^7.0.0", |     "@turf/area": "^6.5.0", | ||||||
|     "@turf/turf": "^7.0.0", |     "@turf/turf": "^6.5.0", | ||||||
|     "jsdom": "^27.0.0", |     "jsdom": "^24.0.0", | ||||||
|     "mapshaper": "^0.6.79" |     "mapshaper": "^0.6.79" | ||||||
|   }, |   }, | ||||||
|   "type": "module" |   "type": "module" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user