Compare commits
	
		
			200 Commits
		
	
	
		
			1.0aa
			...
			86aa888be6
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 86aa888be6 | ||
|  | 9c1374c99f | ||
|  | 6b2f786afe | ||
|  | 2ded750c46 | ||
|  | 686be17fd2 | ||
|  | 7339e16e61 | ||
|  | e43264b327 | ||
|  | 532a34cc7f | ||
|  | 59a02332d0 | ||
|  | 02a83a491f | ||
|  | 0a01aa95f1 | ||
|  | 438eae23b0 | ||
|  | 796faf393e | ||
|  | 9643d0ebb8 | ||
|  | 0999a0c512 | ||
|  | 7a3eb6c8f3 | ||
|  | 5a01c846fa | ||
|  | 4533f36bd2 | ||
|  | ec45f36ec4 | ||
|  | 5396b570ec | ||
|  | d0c656e3d0 | ||
|  | adc416a5e3 | ||
|  | b30430ec7d | ||
|  | 767d018d45 | ||
|  | cbfc484ed8 | ||
|  | 39418809f6 | ||
|  | 40ab056bc0 | ||
|  | ad64b289d4 | ||
|  | 5bdb16b48b | ||
|  | 89ca3200b4 | ||
|  | 54dbf74721 | ||
|  | 2cc7a69b0f | ||
|  | 37026870c7 | ||
|  | 8a9e389a53 | ||
|  | 7a2bae0e6b | ||
|  | 5b199aef8e | ||
|  | 99ff29af88 | ||
|  | 1d36d282f4 | ||
|  | 122dfe8981 | ||
|  | 0f9f428eed | ||
|  | ff4a2513c3 | ||
|  | 14a3fb9ffd | ||
|  | 952bddf9e2 | ||
|  | f346a93b1f | ||
|  | 7ac9dee6d8 | ||
|  | 767b98468b | ||
|  | 84c20349c7 | ||
|  | 097b86b6a5 | ||
|  | 30f6853d98 | ||
|  | fe201a139d | ||
|  | 4da09b509f | ||
|  | c009e750bb | ||
|  | 69ea4cf7c0 | ||
|  | 2f3c3ee84c | ||
|  | dbd84a91bf | ||
|  | 34560b2da2 | ||
|  | c8bb846d0f | ||
|  | 5bd6920857 | ||
|  | 5080498b96 | ||
|  | 3cc522aaa5 | ||
|  | 832b06f424 | ||
|  | 19dfd83351 | ||
|  | 75058f0705 | ||
|  | da2d72246e | ||
|  | e13517317f | ||
|  | ca6d74b581 | ||
|  | 23b47df111 | ||
|  | 38422cc30a | ||
|  | 2f314e5082 | ||
|  | 27befa1790 | ||
|  | 86aaa3899b | ||
|  | 205f81bdab | ||
|  | 9899f8038b | ||
|  | 62b3ff8500 | ||
|  | 2dbb4a2d77 | ||
|  | f4f56e0711 | ||
|  | 8b4ceae602 | ||
|  | 6619e7c891 | ||
|  | 0aac38c05e | ||
|  | cb67901e41 | ||
|  | 4b418942da | ||
|  | 5e03bb33e3 | ||
|  | 22a75e9bc7 | ||
|  | 8ef3702f89 | ||
|  | a89c916e1a | ||
|  | 6eeab1103a | ||
|  | 89c10a67fd | ||
|  | aa2b503732 | ||
|  | f01fdbad00 | ||
|  | 0fc5da8693 | ||
|  | 891a15c1f9 | ||
|  | ff7e05a747 | ||
|  | 9e741fe04e | ||
|  | e8307d79f5 | ||
|  | ac694850da | ||
|  | 2aaf3661e5 | ||
|  | b661a7da47 | ||
|  | c38ca8d8c4 | ||
|  | 80a8c21cb0 | ||
|  | 944ff3529b | ||
|  | fe4ae26428 | ||
|  | bee13854f5 | ||
|  | c02cfa4344 | ||
|  | a4376e5513 | ||
|  | 731ef956d2 | ||
|  | 714abc2a96 | ||
|  | 542fc9b01f | ||
|  | 0e436cb1fc | ||
|  | 324e887d68 | ||
|  | c6c4c072a8 | ||
|  | 02f289d4d0 | ||
|  | fb89deabdf | ||
|  | 5ef6f76355 | ||
|  | 40edcff528 | ||
|  | 370fa6f15f | ||
|  | 1b2a2f5ff4 | ||
|  | 0c7490dea4 | ||
|  | 5ffc1cce50 | ||
|  | bebfaf0921 | ||
|  | 21db9c6d23 | ||
|  | 5cb12372e1 | ||
|  | 1c45957cc3 | ||
|  | 86e233e93b | ||
|  | 2575e0f159 | ||
|  | 4442b94211 | ||
|  | 9a88c4a943 | ||
|  | 6bf9570916 | ||
|  | 1c7aca98c1 | ||
|  | 4982e168ff | ||
|  | 0b9e42ac6c | ||
|  | 05d44d274e | ||
|  | 25c06cd390 | ||
|  | 0e7a939172 | ||
|  | 0b8d699d68 | ||
|  | 34f9fb53bc | ||
|  | ae782b1c32 | ||
|  | bc78898f8e | ||
|  | 8ae862e292 | ||
| 2b1b82e163 | |||
|  | edc5df342d | ||
|  | 7d32e267c6 | ||
| fa8f4218be | |||
| f7a166c9f2 | |||
| 8cc871203d | |||
|  | 4449b5cf8f | ||
|  | 0f1046fcd2 | ||
|  | 395fab45f4 | ||
|  | 0b44d67d61 | ||
|  | d3242a1304 | ||
|  | 9a451764c2 | ||
|  | 898fe6e862 | ||
|  | e357b60b14 | ||
|  | cfb23ed1a0 | ||
|  | 4769901955 | ||
|  | 5c577ec763 | ||
|  | 0724c9a021 | ||
|  | d031aa4ee4 | ||
|  | 6c7a82475e | ||
|  | 3bc34cef94 | ||
|  | c0b489ae21 | ||
|  | 96f5e01d5f | ||
|  | 8c5793a75b | ||
|  | 858162ba47 | ||
|  | 191b3c3eff | ||
|  | 7ddc29275d | ||
|  | 0df7bb7f2c | ||
|  | 43fe9ab868 | ||
| 43f6acfab3 | |||
|  | 126cbfe7b1 | ||
|  | 4aad449a18 | ||
|  | ee7fbf4d5a | ||
|  | 2d48cc8dae | ||
|  | bdca9fe2a1 | ||
|  | ff83b0abe3 | ||
|  | dbe93b6884 | ||
|  | 53f2bd5a57 | ||
|  | ff0714f942 | ||
|  | ed067a616e | ||
|  | e259d401ad | ||
|  | 9599933c5f | ||
|  | 00b0b6c746 | ||
|  | adbae39d27 | ||
|  | 8097d25a18 | ||
|  | fed3e55572 | ||
|  | d76057f17c | ||
|  | aab452f798 | ||
|  | 40fd4522ad | ||
|  | fad65f76ee | ||
|  | 8ad3a26fb0 | ||
|  | 1118ed9b10 | ||
|  | 53db8be5f9 | ||
|  | 111a587793 | ||
|  | cc5ade027a | ||
|  | 026fc7562f | ||
|  | 63a49455e7 | ||
|  | 3f59d876a1 | ||
|  | 05b78ed9a9 | ||
|  | 7241cdd5a1 | ||
|  | 8c65aeb2b9 | ||
|  | 4119518ff5 | 
							
								
								
									
										4
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -23,7 +23,7 @@ jobs: | ||||
|       contents: write | ||||
|  | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - uses: actions/checkout@v5 | ||||
|  | ||||
|       - name: set up secrets | ||||
|         run: | | ||||
| @@ -41,7 +41,7 @@ jobs: | ||||
|         run: git checkout -B "$BRANCH" | ||||
|  | ||||
|       - name: set up JDK | ||||
|         uses: actions/setup-java@v4 | ||||
|         uses: actions/setup-java@v5 | ||||
|         with: | ||||
|           java-version: 17 | ||||
|           distribution: "temurin" | ||||
|   | ||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -20,8 +20,6 @@ app/build/ | ||||
| app/debug/ | ||||
| app/release/ | ||||
| captures/ | ||||
| .externalNativeBuild | ||||
| .cxx | ||||
| local.properties | ||||
| keystore.properties | ||||
| key.jks | ||||
| @@ -39,8 +39,11 @@ | ||||
| ## 📳 Installation | ||||
|  | ||||
| <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"> | ||||
|     </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 href="https://github.com/helcel-net/beans/releases/latest"> | ||||
|         <img width="200" height="84" alt="APK Download" src=".github/images/apk.png"> | ||||
|   | ||||
| @@ -1,21 +1,24 @@ | ||||
| plugins { | ||||
|     id 'com.android.application' | ||||
|     id 'org.jetbrains.kotlin.android' | ||||
|     id 'org.jetbrains.kotlin.plugin.serialization' version '2.0.0' | ||||
|     id 'com.mikepenz.aboutlibraries.plugin' version '11.2.1' | ||||
|     id 'org.jetbrains.kotlin.plugin.serialization' version '2.2.20' | ||||
|     id 'org.jetbrains.kotlin.plugin.compose' version '2.2.20' | ||||
|     id 'com.mikepenz.aboutlibraries.plugin' version '12.2.4' | ||||
| } | ||||
|  | ||||
|  | ||||
| android { | ||||
|     namespace 'net.helcel.beans' | ||||
|     compileSdk 34 | ||||
|     compileSdk 36 | ||||
|  | ||||
|     defaultConfig { | ||||
|         buildConfigField("String", "APP_NAME", "\"Beans\"") | ||||
|         manifestPlaceholders["APP_NAME"] = "Beans" | ||||
|         applicationId 'net.helcel.beans' | ||||
|         minSdk 28 | ||||
|         targetSdk 34 | ||||
|         versionCode 1 | ||||
|         versionName "1.0" | ||||
|         targetSdk 36 | ||||
|         versionCode 4 | ||||
|         versionName "1.0d" | ||||
|     } | ||||
|     signingConfigs { | ||||
|         create("release") { | ||||
| @@ -54,17 +57,15 @@ android { | ||||
|     compileOptions { | ||||
|         coreLibraryDesugaringEnabled true | ||||
|  | ||||
|         sourceCompatibility JavaVersion.VERSION_17 | ||||
|         targetCompatibility JavaVersion.VERSION_17 | ||||
|         sourceCompatibility JavaVersion.VERSION_21 | ||||
|         targetCompatibility JavaVersion.VERSION_21 | ||||
|         encoding 'utf-8' | ||||
|     } | ||||
|  | ||||
|     kotlinOptions { | ||||
|         jvmTarget = JavaVersion.VERSION_17 | ||||
|     } | ||||
|  | ||||
|     buildFeatures { | ||||
|         viewBinding true | ||||
|         compose true | ||||
|         buildConfig true | ||||
|     } | ||||
|  | ||||
|     dependenciesInfo { | ||||
| @@ -73,20 +74,47 @@ android { | ||||
|         // Disables dependency metadata when building Android App Bundles. | ||||
|         includeInBundle = false | ||||
|     } | ||||
|     composeOptions { | ||||
|         kotlinCompilerExtensionVersion = "2.2.20" | ||||
|     } | ||||
|  | ||||
|     kotlin { | ||||
|         jvmToolchain(21) | ||||
|     } | ||||
|  | ||||
|     lint { | ||||
|         disable 'UsingMaterialAndMaterial3Libraries' | ||||
|     } | ||||
|  | ||||
| } | ||||
| aboutLibraries { | ||||
|     library { | ||||
|         exclusionPatterns = [~"androidx.*", ~"com.google.android.*", ~"org.jetbrains.*"] | ||||
|     configPath = "config" | ||||
|     } | ||||
|     excludeFields = ["generated"] | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|     coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.0.4' | ||||
|     implementation 'androidx.compose.material3:material3:1.3.2' | ||||
|     implementation 'androidx.navigation:navigation-compose:2.9.4' | ||||
|     coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.1.5' | ||||
|     implementation 'androidx.preference:preference-ktx:1.2.1' | ||||
|     implementation 'com.google.android.material:material:1.12.0' | ||||
|     implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0' | ||||
|     implementation 'androidx.compose.ui:ui' | ||||
|     implementation "androidx.compose.material:material:1.9.1" | ||||
|     implementation "androidx.activity:activity-ktx:1.11.0" | ||||
|  | ||||
|     implementation 'androidx.compose.ui:ui-tooling-preview' | ||||
|     implementation 'com.google.android.material:material:1.13.0' | ||||
|     implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0' | ||||
|  | ||||
|     implementation 'com.caverock:androidsvg-aar:1.4' | ||||
|     implementation 'com.github.chrisbanes:PhotoView:2.3.0' | ||||
|     implementation 'com.mikepenz:aboutlibraries:11.2.1' | ||||
|  | ||||
|     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,52 +1,22 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:versionCode="1" | ||||
|     android:versionName="1.0a"> | ||||
|  | ||||
|     xmlns:tools="http://schemas.android.com/tools"> | ||||
|     <application | ||||
|         android:allowBackup="true" | ||||
|         android:dataExtractionRules="@xml/data_extraction_rules" | ||||
|         android:fullBackupContent="@xml/backup_rules" | ||||
|         android:hardwareAccelerated="false" | ||||
|         android:icon="@mipmap/ic_launcher_round" | ||||
|         android:label="@string/app_name" | ||||
|         android:label="${APP_NAME}" | ||||
|         android:supportsRtl="true" | ||||
|         android:theme="@style/Theme.Beans" | ||||
|         tools:replace="android:allowBackup" | ||||
|         tools:targetApi="31"> | ||||
|         <profileable android:shell="true" /> | ||||
|  | ||||
|         tools:replace="android:allowBackup"> | ||||
|         <activity | ||||
|             android:name=".activity.MainActivity" | ||||
|             android:name=".activity.MainScreen" | ||||
|             android:exported="true"> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.MAIN" /> | ||||
|                 <category android:name="android.intent.category.LAUNCHER" /> | ||||
|             </intent-filter> | ||||
|         </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> | ||||
|  | ||||
| </manifest> | ||||
| @@ -1,69 +0,0 @@ | ||||
| package net.helcel.beans.activity | ||||
|  | ||||
| import android.os.Bundle | ||||
| import android.view.Menu | ||||
| import android.view.MenuItem | ||||
| import androidx.activity.addCallback | ||||
| import androidx.appcompat.app.AppCompatActivity | ||||
| import com.google.android.material.tabs.TabLayoutMediator | ||||
| import net.helcel.beans.R | ||||
| import net.helcel.beans.activity.adapter.ViewPagerAdapter | ||||
| import net.helcel.beans.activity.fragment.EditGroupAddFragment | ||||
| import net.helcel.beans.activity.fragment.EditPlaceFragment | ||||
| import net.helcel.beans.countries.World | ||||
| import net.helcel.beans.databinding.ActivityEditBinding | ||||
| import net.helcel.beans.helper.Data | ||||
| import net.helcel.beans.helper.Settings | ||||
| import net.helcel.beans.helper.Theme.createActionBar | ||||
|  | ||||
|  | ||||
| class EditActivity : AppCompatActivity() { | ||||
|  | ||||
|     private lateinit var _binding: ActivityEditBinding | ||||
|  | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|         _binding = ActivityEditBinding.inflate(layoutInflater) | ||||
|  | ||||
|         setContentView(_binding.root) | ||||
|         createActionBar(this, getString(R.string.action_edit)) | ||||
|  | ||||
|         val adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, _binding.pager) | ||||
|         _binding.pager.adapter = adapter | ||||
|         adapter.addFragment(null, EditPlaceFragment(World.WWW, adapter)) | ||||
|  | ||||
|         TabLayoutMediator(_binding.tab, _binding.pager) { tab, position -> | ||||
|             tab.text = adapter.getLabel(position) | ||||
|         }.attach() | ||||
|  | ||||
|         onBackPressedDispatcher.addCallback { | ||||
|             if (!adapter.backPressed()) { | ||||
|                 finish() | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun onCreateOptionsMenu(menu: Menu): Boolean { | ||||
|         if (Settings.isSingleGroup(this)) { | ||||
|             menuInflater.inflate(R.menu.menu_edit, menu) | ||||
|         } | ||||
|         return true | ||||
|     } | ||||
|  | ||||
|     override fun onOptionsItemSelected(item: MenuItem): Boolean { | ||||
|         when (item.itemId) { | ||||
|             R.id.action_color -> { | ||||
|                 Data.groups.getUniqueEntry()?.let { group -> | ||||
|                     EditGroupAddFragment(group.key, { | ||||
|                         (_binding.pager.adapter as ViewPagerAdapter?)?.refreshColors(group.color) | ||||
|                     }, {}, false).show(supportFragmentManager, "AddColorDialogFragment") | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             else -> finish() | ||||
|         } | ||||
|         return super.onOptionsItemSelected(item) | ||||
|     } | ||||
|  | ||||
|  | ||||
| } | ||||
							
								
								
									
										64
									
								
								app/src/main/java/net/helcel/beans/activity/EditScreen.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								app/src/main/java/net/helcel/beans/activity/EditScreen.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| package net.helcel.beans.activity | ||||
|  | ||||
| import androidx.compose.foundation.background | ||||
| import androidx.compose.foundation.layout.Box | ||||
| import androidx.compose.foundation.layout.Column | ||||
| import androidx.compose.foundation.layout.Spacer | ||||
| import androidx.compose.foundation.layout.fillMaxSize | ||||
| import androidx.compose.foundation.layout.fillMaxWidth | ||||
| import androidx.compose.foundation.layout.height | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.material.Icon | ||||
| import androidx.compose.material.IconButton | ||||
| import androidx.compose.material.MaterialTheme | ||||
| import androidx.compose.material.Scaffold | ||||
| import androidx.compose.material.Text | ||||
| import androidx.compose.material.TopAppBar | ||||
| import androidx.compose.material.icons.Icons | ||||
| import androidx.compose.material.icons.automirrored.filled.ArrowBack | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.res.stringResource | ||||
| import androidx.compose.ui.unit.dp | ||||
| import net.helcel.beans.R | ||||
| import net.helcel.beans.activity.sub.EditPlaceScreen | ||||
| import net.helcel.beans.countries.World | ||||
|  | ||||
| @Composable | ||||
| fun EditScreen(onExit:()->Unit) { | ||||
|     SysTheme { | ||||
|         Scaffold( | ||||
|             topBar = { | ||||
|                 TopAppBar( | ||||
|                     title = { Text(stringResource(R.string.action_edit)) }, | ||||
|                     navigationIcon = { | ||||
|                         IconButton(onClick = onExit) { | ||||
|                             Icon( | ||||
|                                 Icons.AutoMirrored.Filled.ArrowBack, | ||||
|                                 contentDescription = null | ||||
|                             ) | ||||
|                         } | ||||
|                     } | ||||
|                 ) | ||||
|             }, | ||||
|  | ||||
|             ) { innerPadding -> | ||||
|             Box( | ||||
|                 modifier = Modifier | ||||
|                     .fillMaxSize() | ||||
|                     .background(MaterialTheme.colors.background) | ||||
|                     .padding(innerPadding) // ensures content is below the app bar | ||||
|             ) { | ||||
|                 Column { | ||||
|                     Spacer( | ||||
|                         modifier = Modifier.height(2.dp).fillMaxWidth() | ||||
|                             .background(MaterialTheme.colors.background) | ||||
|                     ) | ||||
|                     EditPlaceScreen(World.WWW, onExit) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -1,75 +0,0 @@ | ||||
| package net.helcel.beans.activity | ||||
|  | ||||
| import android.content.Intent | ||||
| import android.graphics.drawable.PictureDrawable | ||||
| import android.os.Bundle | ||||
| import android.view.Menu | ||||
| import android.view.MenuItem | ||||
| import androidx.appcompat.app.AppCompatActivity | ||||
| import com.caverock.androidsvg.RenderOptions | ||||
| import net.helcel.beans.R | ||||
| import net.helcel.beans.countries.GeoLocImporter | ||||
| import net.helcel.beans.databinding.ActivityMainBinding | ||||
| import net.helcel.beans.helper.Data | ||||
| import net.helcel.beans.helper.Settings | ||||
| import net.helcel.beans.svg.CSSWrapper | ||||
| import net.helcel.beans.svg.SVGWrapper | ||||
|  | ||||
|  | ||||
| class MainActivity : AppCompatActivity() { | ||||
|     private lateinit var _binding: ActivityMainBinding | ||||
|  | ||||
|     private lateinit var psvg: SVGWrapper | ||||
|     private lateinit var css: CSSWrapper | ||||
|  | ||||
|     override fun onRestart() { | ||||
|         refreshProjection() | ||||
|         refreshMap() | ||||
|         super.onRestart() | ||||
|     } | ||||
|  | ||||
|     override fun onCreateOptionsMenu(menu: Menu): Boolean { | ||||
|         menuInflater.inflate(R.menu.menu_main, menu) | ||||
|         return true | ||||
|     } | ||||
|  | ||||
|     override fun onOptionsItemSelected(item: MenuItem): Boolean { | ||||
|         val d = when (item.itemId) { | ||||
|             R.id.action_edit -> EditActivity::class.java | ||||
|             R.id.action_stats -> StatsActivity::class.java | ||||
|             R.id.action_settings -> SettingsActivity::class.java | ||||
|             else -> throw Exception("Non Existent Menu Item") | ||||
|         } | ||||
|         startActivity(Intent(this@MainActivity, d)) | ||||
|         return true | ||||
|     } | ||||
|  | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|         _binding = ActivityMainBinding.inflate(layoutInflater) | ||||
|         Settings.start(this) | ||||
|  | ||||
|         setContentView(_binding.root) | ||||
|  | ||||
|         _binding.photoView.minimumScale = 1f | ||||
|         _binding.photoView.maximumScale = 40f | ||||
|  | ||||
|         GeoLocImporter.importStates(this) | ||||
|         Data.loadData(this, Int.MIN_VALUE) | ||||
|  | ||||
|         refreshProjection() | ||||
|         refreshMap() | ||||
|     } | ||||
|  | ||||
|     private fun refreshMap() { | ||||
|         val opt: RenderOptions = RenderOptions.create() | ||||
|         opt.css(css.get()) | ||||
|         _binding.photoView.setImageDrawable(PictureDrawable(psvg.get()?.renderToPicture(opt))) | ||||
|     } | ||||
|  | ||||
|     fun refreshProjection() { | ||||
|         psvg = SVGWrapper(this) | ||||
|         css = CSSWrapper(this) | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										123
									
								
								app/src/main/java/net/helcel/beans/activity/MainScreen.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								app/src/main/java/net/helcel/beans/activity/MainScreen.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | ||||
| package net.helcel.beans.activity | ||||
|  | ||||
| import android.graphics.drawable.PictureDrawable | ||||
| import android.os.Bundle | ||||
| import android.widget.ImageView | ||||
| import androidx.activity.ComponentActivity | ||||
| import androidx.activity.compose.setContent | ||||
| import androidx.compose.foundation.background | ||||
| import androidx.compose.foundation.layout.Box | ||||
| import androidx.compose.foundation.layout.fillMaxSize | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.material.Icon | ||||
| import androidx.compose.material.IconButton | ||||
| import androidx.compose.material.MaterialTheme | ||||
| import androidx.compose.material.Scaffold | ||||
| import androidx.compose.material.Text | ||||
| import androidx.compose.material.TopAppBar | ||||
| import androidx.compose.material.icons.Icons | ||||
| import androidx.compose.material.icons.filled.DateRange | ||||
| import androidx.compose.material.icons.filled.Edit | ||||
| import androidx.compose.material.icons.filled.Settings | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.remember | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.viewinterop.AndroidView | ||||
| import androidx.navigation.NavHostController | ||||
| import androidx.navigation.compose.NavHost | ||||
| import androidx.navigation.compose.composable | ||||
| import androidx.navigation.compose.rememberNavController | ||||
| import com.caverock.androidsvg.RenderOptions | ||||
| import com.github.chrisbanes.photoview.PhotoView | ||||
| import net.helcel.beans.BuildConfig | ||||
| import net.helcel.beans.countries.GeoLocImporter | ||||
| import net.helcel.beans.helper.Data | ||||
| import net.helcel.beans.helper.Settings | ||||
| import net.helcel.beans.svg.CSSWrapper | ||||
| import net.helcel.beans.svg.SVGWrapper | ||||
|  | ||||
|  | ||||
| class MainScreen : ComponentActivity() { | ||||
|  | ||||
|     private lateinit var psvg: SVGWrapper | ||||
|     private lateinit var css: CSSWrapper | ||||
|  | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|         actionBar?.hide() | ||||
|         Settings.start(this) | ||||
|         GeoLocImporter.importStates(this) | ||||
|         Data.loadData(this, Int.MIN_VALUE) | ||||
|  | ||||
|         setContent { | ||||
|             SysTheme { | ||||
|                 Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background)) { | ||||
|                     AppNavHost(psvg, css) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         refreshProjection() | ||||
|     } | ||||
|  | ||||
|     @Composable | ||||
|     fun AppNavHost(psvg: SVGWrapper, css: CSSWrapper) { | ||||
|         val navController = rememberNavController() | ||||
|         NavHost(navController, startDestination = "main") { | ||||
|             composable("main") { MainScreenC(psvg,css, navController) } | ||||
|             composable("settings") { SettingsMainScreen { navController.navigate("main")} } | ||||
|             composable("edit") { EditScreen { navController.navigate("main") } } | ||||
|             composable("stats") { StatsScreen { navController.navigate("main") } } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Composable | ||||
|     fun MainScreenC(psvg: SVGWrapper,css: CSSWrapper, nav: NavHostController){ | ||||
|         SysTheme { | ||||
|             Scaffold( | ||||
|                 topBar = { | ||||
|                     TopAppBar( | ||||
|                         title = { Text(BuildConfig.APP_NAME) }, | ||||
|                         actions = { | ||||
|                             IconButton(onClick = { nav.navigate("edit") }) { | ||||
|                                 Icon(Icons.Default.Edit, contentDescription = "Edit") | ||||
|                             } | ||||
|                             IconButton(onClick = {  nav.navigate("stats") }){ | ||||
|                                 Icon(Icons.Default.DateRange, contentDescription = "Stats") | ||||
|                             } | ||||
|                             IconButton(onClick = { nav.navigate("settings") }) { | ||||
|                                 Icon(Icons.Default.Settings, contentDescription = "Settings") | ||||
|                             } | ||||
|                         } | ||||
|                     ) | ||||
|                 } | ||||
|             ) { innerPadding -> | ||||
|                 Box(modifier = Modifier.padding(innerPadding)) { | ||||
|                     MapScreen(psvg, css) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Composable | ||||
|     fun MapScreen(psvg: SVGWrapper, css: CSSWrapper) { | ||||
|         Box { | ||||
|             val opt: RenderOptions = RenderOptions.create() | ||||
|             opt.css(css.get()) | ||||
|             val drawable = remember(psvg, css) { | ||||
|                 PictureDrawable(psvg.get()?.renderToPicture(opt)) | ||||
|             } | ||||
|             AndroidView(factory = { ctx -> | ||||
|                 PhotoView(ctx).apply { | ||||
|                     setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null) | ||||
|                     setImageDrawable(drawable) | ||||
|                     scaleType = ImageView.ScaleType.FIT_CENTER | ||||
|                 } | ||||
|             }, modifier = Modifier.fillMaxSize()) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun refreshProjection() { | ||||
|         psvg = SVGWrapper(this) | ||||
|         css = CSSWrapper(this) | ||||
|     } | ||||
| } | ||||
| @@ -1,63 +0,0 @@ | ||||
| package net.helcel.beans.activity | ||||
|  | ||||
| import android.os.Bundle | ||||
| import android.view.MenuItem | ||||
| import androidx.appcompat.app.AppCompatActivity | ||||
| import net.helcel.beans.R | ||||
| import net.helcel.beans.activity.fragment.AboutFragment | ||||
| import net.helcel.beans.activity.fragment.LicenseFragment | ||||
| import net.helcel.beans.activity.fragment.SettingsFragment | ||||
| import net.helcel.beans.helper.Theme.createActionBar | ||||
|  | ||||
| class SettingsActivity : AppCompatActivity() { | ||||
|  | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|  | ||||
|         setContentView(R.layout.activity_settings) | ||||
|         createActionBar(this, getString(R.string.action_settings)) | ||||
|  | ||||
|         // Populate activity with settings fragment | ||||
|         supportFragmentManager.beginTransaction() | ||||
|             .replace(R.id.fragment_view, SettingsFragment(), getString(R.string.action_settings)) | ||||
|             .commit() | ||||
|  | ||||
|         // Change title in action bar according to current fragment | ||||
|         supportFragmentManager.addFragmentOnAttachListener { _, _ -> | ||||
|             supportActionBar?.title = | ||||
|                 supportFragmentManager.findFragmentById(R.id.fragment_view).let { | ||||
|                     when (it) { | ||||
|                         is LicenseFragment -> getString(R.string.licenses) | ||||
|                         is AboutFragment -> getString(R.string.about) | ||||
|                         else -> getString(R.string.action_settings) | ||||
|                     } | ||||
|                 } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun onOptionsItemSelected(item: MenuItem): Boolean { | ||||
|         // Configure on back pressed | ||||
|         supportFragmentManager.findFragmentById(R.id.fragment_view).let { | ||||
|             when (it) { | ||||
|                 is LicenseFragment, is AboutFragment -> { | ||||
|                     supportFragmentManager.beginTransaction() | ||||
|                         .remove(it) | ||||
|                         .commit() | ||||
|                     supportFragmentManager.beginTransaction() | ||||
|                         .replace( | ||||
|                             R.id.fragment_view, | ||||
|                             SettingsFragment(), | ||||
|                             getString(R.string.action_settings) | ||||
|                         ) | ||||
|                         .commit() | ||||
|                     supportActionBar?.title = getString(R.string.action_settings) | ||||
|                 } | ||||
|  | ||||
|                 else -> { | ||||
|                     finish() | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return super.onOptionsItemSelected(item) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										381
									
								
								app/src/main/java/net/helcel/beans/activity/SettingsScreen.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										381
									
								
								app/src/main/java/net/helcel/beans/activity/SettingsScreen.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,381 @@ | ||||
| package net.helcel.beans.activity | ||||
|  | ||||
| import android.os.Build | ||||
| import androidx.activity.compose.rememberLauncherForActivityResult | ||||
| import androidx.activity.result.contract.ActivityResultContracts | ||||
| import androidx.compose.foundation.background | ||||
| import androidx.compose.foundation.clickable | ||||
| import androidx.compose.foundation.isSystemInDarkTheme | ||||
| import androidx.compose.foundation.layout.Box | ||||
| import androidx.compose.foundation.layout.Column | ||||
| import androidx.compose.foundation.layout.Row | ||||
| import androidx.compose.foundation.layout.Spacer | ||||
| import androidx.compose.foundation.layout.fillMaxSize | ||||
| import androidx.compose.foundation.layout.fillMaxWidth | ||||
| import androidx.compose.foundation.layout.height | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.foundation.layout.size | ||||
| import androidx.compose.foundation.lazy.LazyColumn | ||||
| import androidx.compose.foundation.shape.CornerSize | ||||
| import androidx.compose.foundation.shape.RoundedCornerShape | ||||
| import androidx.compose.material.Button | ||||
| import androidx.compose.material.CircularProgressIndicator | ||||
| import androidx.compose.material.Colors | ||||
| import androidx.compose.material.Icon | ||||
| import androidx.compose.material.IconButton | ||||
| import androidx.compose.material.MaterialTheme | ||||
| import androidx.compose.material.RadioButton | ||||
| import androidx.compose.material.Scaffold | ||||
| import androidx.compose.material.Text | ||||
| import androidx.compose.material.TopAppBar | ||||
| import androidx.compose.material.icons.Icons | ||||
| import androidx.compose.material.icons.automirrored.filled.ArrowBack | ||||
| import androidx.compose.material3.HorizontalDivider | ||||
| import androidx.compose.material3.darkColorScheme | ||||
| import androidx.compose.material3.dynamicDarkColorScheme | ||||
| import androidx.compose.material3.dynamicLightColorScheme | ||||
| import androidx.compose.material3.lightColorScheme | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.getValue | ||||
| import androidx.compose.runtime.mutableStateOf | ||||
| import androidx.compose.runtime.remember | ||||
| import androidx.compose.runtime.rememberCoroutineScope | ||||
| import androidx.compose.runtime.setValue | ||||
| import androidx.compose.ui.Alignment | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.platform.LocalContext | ||||
| import androidx.compose.ui.res.stringResource | ||||
| import androidx.compose.ui.tooling.preview.Preview | ||||
| import androidx.compose.ui.unit.dp | ||||
| import androidx.compose.ui.window.Dialog | ||||
| import androidx.preference.PreferenceManager | ||||
| import net.helcel.beans.R | ||||
| import net.helcel.beans.countries.GeoLocImporter | ||||
| import net.helcel.beans.helper.Settings | ||||
| import androidx.core.content.edit | ||||
| import androidx.navigation.NavHostController | ||||
| import androidx.navigation.compose.NavHost | ||||
| import androidx.navigation.compose.composable | ||||
| import androidx.navigation.compose.rememberNavController | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.launch | ||||
| import kotlinx.coroutines.withContext | ||||
| import net.helcel.beans.activity.sub.AboutScreen | ||||
| import net.helcel.beans.activity.sub.EditPlaceColorDialog | ||||
| import net.helcel.beans.activity.sub.EditPlaceDialog | ||||
| import net.helcel.beans.activity.sub.LicenseScreen | ||||
| import net.helcel.beans.helper.Data | ||||
|  | ||||
| @Composable | ||||
| fun SysTheme( | ||||
|     content: @Composable () -> Unit | ||||
| ) { | ||||
|     val context = LocalContext.current | ||||
|     val prefs = PreferenceManager.getDefaultSharedPreferences(context) | ||||
|     val themeKey = prefs.getString(stringResource(R.string.key_theme), stringResource(R.string.system)) | ||||
|     val darkTheme = when (themeKey) { | ||||
|         stringResource(R.string.system) -> isSystemInDarkTheme() | ||||
|         stringResource(R.string.light) -> false | ||||
|         stringResource(R.string.dark) -> true | ||||
|         else -> isSystemInDarkTheme() | ||||
|     } | ||||
|     val colorScheme = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { | ||||
|         if(darkTheme) dynamicDarkColorScheme(LocalContext.current ) else dynamicLightColorScheme(LocalContext.current ) | ||||
|     } else { | ||||
|         if(darkTheme) darkColorScheme() else lightColorScheme() | ||||
|     } | ||||
|     val m2colors = Colors( | ||||
|         primary = colorScheme.primary, | ||||
|         primaryVariant = colorScheme.primaryContainer, | ||||
|         secondary = colorScheme.secondary, | ||||
|         background = colorScheme.background, | ||||
|         surface = colorScheme.surface, | ||||
|         onPrimary = colorScheme.onPrimary, | ||||
|         onSecondary = colorScheme.onSecondary, | ||||
|         onBackground = colorScheme.onBackground, | ||||
|         onSurface = colorScheme.onSurface, | ||||
|         secondaryVariant = colorScheme.secondary, | ||||
|         error = colorScheme.error, | ||||
|         onError = colorScheme.onError, | ||||
|         isLight = !darkTheme, | ||||
|     ) | ||||
|  | ||||
|     MaterialTheme( | ||||
|         colors = m2colors, | ||||
|         content = content | ||||
|     ) | ||||
| } | ||||
|  | ||||
|  | ||||
| @Composable | ||||
| fun settingsNav(): NavHostController { | ||||
|     val navController = rememberNavController() | ||||
|     NavHost(navController, startDestination= "settings"){ | ||||
|         composable("settings"){SettingsScreen(navController)} | ||||
|         composable("licenses"){ LicenseScreen() } | ||||
|         composable("about"){ AboutScreen() } | ||||
|     } | ||||
|     return navController | ||||
| } | ||||
|  | ||||
| @Composable | ||||
| fun SettingsMainScreen(onExit: ()->Unit = {}) { | ||||
|     var nav: NavHostController? = null | ||||
|     SysTheme { | ||||
|         Scaffold( | ||||
|             topBar = { | ||||
|                 TopAppBar( | ||||
|                     title = { Text(stringResource(R.string.action_settings)) }, | ||||
|                     navigationIcon = { | ||||
|                         IconButton(onClick = { | ||||
|                             if(nav!=null && !nav!!.popBackStack()) | ||||
|                                 onExit() | ||||
|                         }) { | ||||
|                             Icon( | ||||
|                                 Icons.AutoMirrored.Filled.ArrowBack, | ||||
|                                 contentDescription = null | ||||
|                             ) | ||||
|                         } | ||||
|                     } | ||||
|                 ) | ||||
|             } | ||||
|         ) { innerPadding -> | ||||
|             Box(modifier = Modifier.padding(innerPadding)) { | ||||
|                 nav = settingsNav() | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @Preview | ||||
| @Composable | ||||
| fun SettingsScreen(navController: NavHostController = settingsNav()) { | ||||
|     val context = LocalContext.current | ||||
|     val prefs = PreferenceManager.getDefaultSharedPreferences(context) | ||||
|     var showEdit by remember { mutableStateOf(false) } | ||||
|  | ||||
|     var theme by remember { mutableStateOf(prefs.getString(context.getString(R.string.key_theme), context.getString(R.string.system))!!) } | ||||
|     var projection by remember { mutableStateOf(prefs.getString(context.getString(R.string.key_projection), "default")!!) } | ||||
|     var groups by remember { mutableStateOf(prefs.getString(context.getString(R.string.key_group), context.getString(R.string.off))!!) } | ||||
|  | ||||
|         if(showEdit) | ||||
|             EditPlaceDialog(true) { | ||||
|                 showEdit = false | ||||
|                 val g = Data.selected_group | ||||
|                 if (it && g != null) | ||||
|                     Data.visits.reassignAllVisitedToGroup(g.key) | ||||
|             } | ||||
|  | ||||
|         LazyColumn( | ||||
|             modifier = Modifier | ||||
|                 .fillMaxSize() | ||||
|                 .padding(16.dp) | ||||
|                 .background(MaterialTheme.colors.background) | ||||
|         ) { | ||||
|             item { | ||||
|                 Text( | ||||
|                     "Theme", style = MaterialTheme.typography.h6, | ||||
|                     color = MaterialTheme.colors.onBackground, | ||||
|                 ) | ||||
|                 MultiPreference(arrayOf(stringResource(R.string.system),stringResource(R.string.light),stringResource(R.string.dark)), theme) { newTheme -> | ||||
|                     theme = newTheme | ||||
|                     prefs.edit { putString(context.getString(R.string.key_theme), newTheme) } | ||||
|                 } | ||||
|                 HorizontalDivider() | ||||
|             } | ||||
|             item { | ||||
|                 Text( | ||||
|                     "Map Projection", | ||||
|                     style = MaterialTheme.typography.h6, | ||||
|                     color = MaterialTheme.colors.onBackground, | ||||
|                     modifier = Modifier.padding(top = 16.dp) | ||||
|                 ) | ||||
|                 MultiPreference(arrayOf(stringResource(R.string.mercator), stringResource(R.string.azimuthalequidistant)), projection) { newProj -> | ||||
|                     projection = newProj | ||||
|                     prefs.edit { putString(context.getString(R.string.key_projection), newProj) } | ||||
|                     Settings.refreshProjection() | ||||
|                 } | ||||
|                 HorizontalDivider() | ||||
|             } | ||||
|             item { | ||||
|                 Text( | ||||
|                     "Groups", | ||||
|                     style = MaterialTheme.typography.h6, | ||||
|                     color = MaterialTheme.colors.onBackground, | ||||
|                     modifier = Modifier.padding(top = 16.dp) | ||||
|                 ) | ||||
|                 var showDialog by remember{mutableStateOf(false)} | ||||
|                 if(showDialog){ | ||||
|                     EditPlaceColorDialog( | ||||
|                         deleteMode = true, | ||||
|                         onDismiss = { | ||||
|                             val g = Data.selected_group | ||||
|                             if (g != null) | ||||
|                                 Data.visits.reassignAllVisitedToGroup(g.key) | ||||
|                             showDialog = false | ||||
|                         }) | ||||
|                 } | ||||
|                 MultiPreference( | ||||
|                     arrayOf(stringResource(R.string.on), stringResource(R.string.off)), | ||||
|                     groups | ||||
|                 ) { key -> | ||||
|                     if (key == context.getString(R.string.off)) { | ||||
|                         showDialog=true | ||||
|                     } | ||||
|                     groups = key | ||||
|                     prefs.edit { putString(context.getString(R.string.key_group), key) } | ||||
|                 } | ||||
|                 HorizontalDivider() | ||||
|             } | ||||
|             item { | ||||
|                 Text( | ||||
|                     text = "Regional", | ||||
|                     style = MaterialTheme.typography.h6, | ||||
|                     color = MaterialTheme.colors.onBackground, | ||||
|                     modifier = Modifier | ||||
|                         .padding(top = 16.dp) | ||||
|                         .clickable(onClick = {}), | ||||
|                 ) | ||||
|                 RegionalScreen() | ||||
|                 HorizontalDivider() | ||||
|             } | ||||
|             item{ | ||||
|                 val launcher = rememberLauncherForActivityResult( | ||||
|                 contract = ActivityResultContracts.OpenDocument(), | ||||
|                 onResult = { uri -> Data.doImport(context, uri)   } | ||||
|                 ) | ||||
|                 Row( | ||||
|                     modifier = Modifier.fillMaxWidth() | ||||
|                 ) { | ||||
|                     Button(onClick = { | ||||
|                         launcher.launch(arrayOf("*/*")) | ||||
|                     }, modifier = Modifier | ||||
|                         .fillMaxWidth(fraction = 0.4f) | ||||
|                         .padding(vertical = 8.dp)) { | ||||
|                         Text("Import") | ||||
|                     } | ||||
|                     Spacer( | ||||
|                         modifier = Modifier.fillMaxWidth(0.4f) | ||||
|                     ) | ||||
|                     Button(onClick = { | ||||
|                         Data.doExport(context) | ||||
|                     }, modifier = Modifier | ||||
|                         .fillMaxWidth(fraction = 1f) | ||||
|                         .padding(vertical = 8.dp)) { | ||||
|                         Text("Export") | ||||
|                     } | ||||
|                 } | ||||
|                 HorizontalDivider() | ||||
|             } | ||||
|             item { | ||||
|                 PreferenceButton("Licenses") { | ||||
|                     if (navController.currentDestination?.route != "licenses") | ||||
|                         navController.navigate("licenses") | ||||
|                 } | ||||
|                 PreferenceButton("About") { | ||||
|                     if (navController.currentDestination?.route != "about") | ||||
|                         navController.navigate("about") | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @Composable | ||||
| fun RegionalScreen() { | ||||
|     val context = LocalContext.current | ||||
|     val prefs = PreferenceManager.getDefaultSharedPreferences(context) | ||||
|     var selected by remember {  mutableStateOf(prefs.getString(context.getString(R.string.key_regional),context.getString(R.string.off))!!)} | ||||
|     var regional by remember{ mutableStateOf(prefs.getString(context.getString(R.string.key_regional), context.getString(R.string.off))!!)} | ||||
|     var showDialog by remember{mutableStateOf(false)} | ||||
|     var showLoad by remember{mutableStateOf(false)} | ||||
|  | ||||
|     if(showDialog) | ||||
|         Dialog( | ||||
|             content = { | ||||
|                 Column( | ||||
|                     modifier = Modifier | ||||
|                         .background( | ||||
|                             MaterialTheme.colors.background, | ||||
|                             RoundedCornerShape(corner = CornerSize(16.dp)) | ||||
|                         ) | ||||
|                         .padding(16.dp),){ | ||||
|                 Text(style=MaterialTheme.typography.caption, text=  stringResource(R.string.delete_regions)) | ||||
|                     Button(onClick = { | ||||
|                         GeoLocImporter.clearStates() | ||||
|                         regional= selected | ||||
|                         prefs.edit { | ||||
|                             putString( | ||||
|                                 context.getString(R.string.key_regional), | ||||
|                                 regional | ||||
|                             ) | ||||
|                         } | ||||
|                         showDialog=false | ||||
|                     }){ | ||||
|                        Text(stringResource(R.string.ok)) | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             onDismissRequest = { showDialog=false } | ||||
|         ) | ||||
|     if(showLoad){ | ||||
|         Dialog( | ||||
|             content = { | ||||
|                 CircularProgressIndicator( | ||||
|                     color = MaterialTheme.colors.primary, | ||||
|                     strokeWidth = 4.dp, | ||||
|                     modifier = Modifier.size(50.dp) | ||||
|                 ) | ||||
|             }, | ||||
|             onDismissRequest = {} | ||||
|         ) | ||||
|     } | ||||
|     val scope = rememberCoroutineScope() | ||||
|     MultiPreference(arrayOf(stringResource(R.string.on),stringResource(R.string.off)),regional) { key -> | ||||
|                 when (key) { | ||||
|                     context.getString(R.string.off) -> { showDialog=true | ||||
|                         selected=key | ||||
|                     } | ||||
|                     context.getString(R.string.on) -> { | ||||
|                         regional = key | ||||
|                         prefs.edit { putString(context.getString(R.string.key_regional), key) } | ||||
|                         showLoad=true | ||||
|                         scope.launch { | ||||
|                             withContext(Dispatchers.IO) { | ||||
|                                 GeoLocImporter.importStates(context, true) | ||||
|                             } | ||||
|                             showLoad = false | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| } | ||||
|  | ||||
|  | ||||
| @Composable | ||||
| fun MultiPreference(list: Array<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,57 +1,159 @@ | ||||
| package net.helcel.beans.activity | ||||
|  | ||||
| import android.os.Bundle | ||||
| import android.view.MenuItem | ||||
| import androidx.appcompat.app.AppCompatActivity | ||||
| import androidx.fragment.app.Fragment | ||||
| import androidx.recyclerview.widget.LinearLayoutManager | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import androidx.viewpager2.adapter.FragmentStateAdapter | ||||
| import androidx.viewpager2.widget.ViewPager2 | ||||
| import com.google.android.material.tabs.TabLayoutMediator | ||||
| import androidx.compose.foundation.background | ||||
| import androidx.compose.foundation.layout.Arrangement | ||||
| import androidx.compose.foundation.layout.Column | ||||
| import androidx.compose.foundation.layout.Row | ||||
| import androidx.compose.foundation.layout.fillMaxSize | ||||
| import androidx.compose.foundation.layout.fillMaxWidth | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.foundation.lazy.LazyColumn | ||||
| import androidx.compose.foundation.lazy.items | ||||
| import androidx.compose.material.Button | ||||
| import androidx.compose.material.Icon | ||||
| import androidx.compose.material.IconButton | ||||
| import androidx.compose.material.Scaffold | ||||
| import androidx.compose.material.Tab | ||||
| import androidx.compose.material.TabRow | ||||
| import androidx.compose.material.Text | ||||
| import androidx.compose.material.TopAppBar | ||||
| import androidx.compose.material.icons.Icons | ||||
| import androidx.compose.material.icons.automirrored.filled.ArrowBack | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.getValue | ||||
| import androidx.compose.runtime.mutableIntStateOf | ||||
| import androidx.compose.runtime.mutableStateOf | ||||
| import androidx.compose.runtime.remember | ||||
| import androidx.compose.runtime.setValue | ||||
| import androidx.compose.ui.Alignment | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.graphics.Color | ||||
| import androidx.compose.ui.platform.LocalContext | ||||
| import androidx.compose.ui.res.stringResource | ||||
| import androidx.compose.ui.unit.dp | ||||
| import net.helcel.beans.R | ||||
| import net.helcel.beans.activity.adapter.StatsListAdapter | ||||
| import net.helcel.beans.countries.GeoLoc.LocType | ||||
| import net.helcel.beans.databinding.ActivityStatBinding | ||||
| import net.helcel.beans.helper.Settings | ||||
| import net.helcel.beans.helper.Theme.createActionBar | ||||
| import net.helcel.beans.countries.World | ||||
| import net.helcel.beans.helper.AUTO_GROUP | ||||
| import net.helcel.beans.helper.Data | ||||
| import net.helcel.beans.helper.Groups | ||||
| import net.helcel.beans.helper.Settings.isRegional | ||||
| import net.helcel.beans.helper.Theme.getContrastColor | ||||
|  | ||||
| private val MODE_LIST = listOf(LocType.WORLD, LocType.COUNTRY, LocType.STATE) | ||||
|  | ||||
| class StatsActivity : AppCompatActivity() { | ||||
|     private lateinit var _binding: ActivityStatBinding | ||||
|     private var activeMode = LocType.WORLD | ||||
| @Composable | ||||
| fun StatsScreen( | ||||
|     onExit: ()-> Unit | ||||
| ) { | ||||
|     val modes = if (isRegional(LocalContext.current)) MODE_LIST else MODE_LIST.take(2) | ||||
|     var selectedTab by remember { mutableIntStateOf(0) } | ||||
|     var countMode by remember { mutableStateOf(true) } | ||||
|  | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|         _binding = ActivityStatBinding.inflate(layoutInflater) | ||||
|         setContentView(_binding.root) | ||||
|         createActionBar(this, getString(R.string.action_stat)) | ||||
|  | ||||
|         _binding.stats.layoutManager = | ||||
|             LinearLayoutManager(this, RecyclerView.VERTICAL, false) | ||||
|         val adapter = StatsListAdapter(_binding.stats, _binding.name) | ||||
|         _binding.groupColor.setOnClickListener { adapter.invertCountMode() } | ||||
|         _binding.stats.adapter = adapter | ||||
|  | ||||
|         _binding.pager.adapter = object : FragmentStateAdapter(supportFragmentManager, lifecycle) { | ||||
|             override fun getItemCount(): Int = if (Settings.isRegional(applicationContext)) 3 else 2 | ||||
|             override fun createFragment(position: Int): Fragment = Fragment() | ||||
|         } | ||||
|         TabLayoutMediator(_binding.tab, _binding.pager) { tab, position -> | ||||
|             tab.text = MODE_LIST[position].txt | ||||
|         }.attach() | ||||
|  | ||||
|         _binding.pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { | ||||
|             override fun onPageSelected(position: Int) { | ||||
|                 activeMode = MODE_LIST[position] | ||||
|                 adapter.refreshMode(activeMode) | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     override fun onOptionsItemSelected(item: MenuItem): Boolean { | ||||
|         finish() | ||||
|         return super.onOptionsItemSelected(item) | ||||
|     SysTheme { | ||||
|         Scaffold( | ||||
|             topBar = { | ||||
|                 TopAppBar( | ||||
|                     title = { | ||||
|                         Row(verticalAlignment = Alignment.CenterVertically){ | ||||
|                             Text(text=stringResource(R.string.action_edit), modifier = Modifier.weight(1f)) | ||||
|                             Button(onClick = { countMode = !countMode }) { | ||||
|                                 Text(if (countMode) "Count" else "Area") | ||||
|                             } | ||||
|                         } | ||||
|                     }, | ||||
|                     navigationIcon = { | ||||
|                         IconButton(onClick = onExit) { | ||||
|                             Icon( | ||||
|                                 Icons.AutoMirrored.Filled.ArrowBack, | ||||
|                                 contentDescription = null | ||||
|                             ) | ||||
|                         } | ||||
|                     }, | ||||
|  | ||||
|                 ) | ||||
|             }, | ||||
|         ) { padding -> | ||||
|             Column(Modifier.padding(padding)) { | ||||
|                 TabRow(selectedTabIndex = selectedTab) { | ||||
|                     modes.forEachIndexed { index, mode -> | ||||
|                         Tab( | ||||
|                             selected = selectedTab == index, | ||||
|                             onClick = { selectedTab = index }, | ||||
|                             text = { Text(mode.txt) } | ||||
|                         ) | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 Row( | ||||
|                     modifier = Modifier | ||||
|                         .fillMaxWidth() | ||||
|                         .padding(8.dp), | ||||
|                     horizontalArrangement = Arrangement.End | ||||
|                 ) { | ||||
|                 } | ||||
|  | ||||
|                 val activeMode = modes.getOrNull(selectedTab) ?: LocType.WORLD | ||||
|                 StatsList(activeMode, countMode) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @Composable | ||||
| fun StatsList(activeMode: LocType, countMode: Boolean) { | ||||
|     val groups = remember { Data.groups.groupsFlow } | ||||
|     val unCat = stringResource(R.string.uncategorized) | ||||
|  | ||||
|     LazyColumn(modifier = Modifier.fillMaxSize()) { | ||||
|         items(groups.value + listOf(Groups.Group(AUTO_GROUP, unCat))) { group -> | ||||
|             StatsRow(group, activeMode, countMode) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @Composable | ||||
| fun StatsRow(group: Groups.Group, activeMode: LocType, countMode: Boolean) { | ||||
|     val context = LocalContext.current | ||||
|  | ||||
|     val visited = remember(group, activeMode) { | ||||
|         Data.visits.getVisitedByValue(group.key) | ||||
|     } | ||||
|  | ||||
|     val count = when (activeMode) { | ||||
|         LocType.WORLD -> World.WWW.children.filter { it.code in visited }.size | ||||
|         LocType.COUNTRY -> World.WWW.children.flatMap { it.children.filter { c -> c.code in visited } }.size | ||||
|         LocType.STATE -> World.WWW.children.flatMap { itc->itc.children.flatMap { it.children.filter { it.code in visited } } }.size | ||||
|         else -> 0 | ||||
|     } | ||||
|  | ||||
|     val area = when (activeMode) { | ||||
|         LocType.WORLD -> World.WWW.children.filter { it.code in visited }.sumOf { it.area } | ||||
|         LocType.COUNTRY -> World.WWW.children.flatMap { it.children.filter { c -> c.code in visited } }.sumOf { it.area } | ||||
|         LocType.STATE -> World.WWW.children.flatMap { it.children.flatMap { it.children.filter { it.code in visited } } }.sumOf { it.area } | ||||
|         else -> 0 | ||||
|     } | ||||
|  | ||||
|     val displayValue = if (countMode) count.toString() else context.getString(R.string.number_with_unit, area, "km²") | ||||
|  | ||||
|     val backgroundColor = group.color.color | ||||
|     val textColor = getContrastColor(backgroundColor) | ||||
|  | ||||
|     Row( | ||||
|         modifier = Modifier | ||||
|             .fillMaxWidth() | ||||
|             .background(Color(backgroundColor)) | ||||
|             .padding(16.dp), | ||||
|         verticalAlignment = Alignment.CenterVertically | ||||
|     ) { | ||||
|         Text( | ||||
|             text=group.name, | ||||
|             modifier= Modifier.weight(1f), | ||||
|             color = Color(textColor) | ||||
|         ) | ||||
|         Text(text=displayValue, | ||||
|             color = Color(textColor) | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -1,193 +0,0 @@ | ||||
| package net.helcel.beans.activity.adapter | ||||
|  | ||||
| import android.content.res.ColorStateList | ||||
| import android.graphics.Color | ||||
| import android.graphics.Typeface | ||||
| import android.graphics.drawable.ColorDrawable | ||||
| import android.view.LayoutInflater | ||||
| import android.view.ViewGroup | ||||
| import androidx.fragment.app.FragmentActivity | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import com.google.android.material.checkbox.MaterialCheckBox | ||||
| import net.helcel.beans.activity.fragment.EditPlaceColorFragment | ||||
| import net.helcel.beans.activity.fragment.EditPlaceFragment | ||||
| import net.helcel.beans.countries.GeoLoc | ||||
| import net.helcel.beans.databinding.ItemListGeolocBinding | ||||
| import net.helcel.beans.helper.* | ||||
| import net.helcel.beans.helper.Theme.colorWrapper | ||||
|  | ||||
| class GeolocListAdapter( | ||||
|     private val ctx: EditPlaceFragment, private val l: GeoLoc, private val pager: ViewPagerAdapter, | ||||
|     private val parentHolder: FoldingListViewHolder? | ||||
| ) : RecyclerView.Adapter<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) | ||||
|         } | ||||
|  | ||||
|     } | ||||
| } | ||||
| @@ -1,78 +0,0 @@ | ||||
| package net.helcel.beans.activity.adapter | ||||
|  | ||||
| import android.view.LayoutInflater | ||||
| import android.view.ViewGroup | ||||
| import androidx.fragment.app.DialogFragment | ||||
| import androidx.fragment.app.FragmentActivity | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import net.helcel.beans.activity.fragment.EditGroupAddFragment | ||||
| import net.helcel.beans.databinding.ItemListGroupBinding | ||||
| import net.helcel.beans.helper.Data | ||||
| import net.helcel.beans.helper.Groups | ||||
| import net.helcel.beans.helper.Theme.getContrastColor | ||||
|  | ||||
| class GroupListAdapter( | ||||
|     private val activity: FragmentActivity, | ||||
|     private val selectDialog: DialogFragment, | ||||
|     private val delete: Boolean = false | ||||
| ) : RecyclerView.Adapter<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 | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,152 +0,0 @@ | ||||
| package net.helcel.beans.activity.adapter | ||||
|  | ||||
| import android.content.Context | ||||
| import android.view.LayoutInflater | ||||
| import android.view.ViewGroup | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import com.google.android.material.textview.MaterialTextView | ||||
| import net.helcel.beans.R | ||||
| import net.helcel.beans.countries.GeoLoc | ||||
| import net.helcel.beans.countries.GeoLoc.LocType | ||||
| import net.helcel.beans.countries.World | ||||
| import net.helcel.beans.databinding.ItemListGroupBinding | ||||
| import net.helcel.beans.helper.AUTO_GROUP | ||||
| import net.helcel.beans.helper.Data | ||||
| import net.helcel.beans.helper.Groups | ||||
| import net.helcel.beans.helper.Settings | ||||
| import net.helcel.beans.helper.Theme.getContrastColor | ||||
|  | ||||
| class StatsListAdapter(private val stats: RecyclerView, private val total: MaterialTextView) : | ||||
|     RecyclerView.Adapter<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 | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     } | ||||
| } | ||||
| @@ -1,61 +0,0 @@ | ||||
| package net.helcel.beans.activity.adapter | ||||
|  | ||||
| import android.graphics.drawable.ColorDrawable | ||||
| import androidx.fragment.app.Fragment | ||||
| import androidx.fragment.app.FragmentManager | ||||
| import androidx.lifecycle.Lifecycle | ||||
| import androidx.viewpager2.adapter.FragmentStateAdapter | ||||
| import androidx.viewpager2.widget.ViewPager2 | ||||
| import net.helcel.beans.activity.fragment.EditPlaceFragment | ||||
| import kotlin.math.max | ||||
|  | ||||
| class ViewPagerAdapter( | ||||
|     fragmentManager: FragmentManager, | ||||
|     lifecycle: Lifecycle, | ||||
|     private val viewPager: ViewPager2 | ||||
| ) : | ||||
|     FragmentStateAdapter(fragmentManager, lifecycle) { | ||||
|  | ||||
|     private val fragmentList: MutableList<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)} | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -1,21 +0,0 @@ | ||||
| package net.helcel.beans.activity.fragment | ||||
|  | ||||
| import android.os.Bundle | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import androidx.fragment.app.Fragment | ||||
| import net.helcel.beans.databinding.FragmentAboutBinding | ||||
|  | ||||
| class AboutFragment : Fragment() { | ||||
|     private lateinit var _binding: FragmentAboutBinding | ||||
|  | ||||
|     override fun onCreateView( | ||||
|         inflater: LayoutInflater, | ||||
|         container: ViewGroup?, | ||||
|         savedInstanceState: Bundle? | ||||
|     ): View { | ||||
|         _binding = FragmentAboutBinding.inflate(inflater, container, false) | ||||
|         return _binding.root | ||||
|     } | ||||
| } | ||||
| @@ -1,155 +0,0 @@ | ||||
| package net.helcel.beans.activity.fragment | ||||
|  | ||||
| import android.app.Dialog | ||||
| import android.graphics.Color | ||||
| import android.graphics.drawable.ColorDrawable | ||||
| import android.os.Bundle | ||||
| import android.text.Editable | ||||
| import android.text.TextWatcher | ||||
| import android.view.View | ||||
| import androidx.core.graphics.blue | ||||
| import androidx.core.graphics.green | ||||
| import androidx.core.graphics.red | ||||
| import androidx.fragment.app.DialogFragment | ||||
| import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||||
| import com.google.android.material.slider.Slider | ||||
| import com.google.android.material.textfield.TextInputEditText | ||||
| import net.helcel.beans.R | ||||
| import net.helcel.beans.databinding.FragmentEditGroupsAddBinding | ||||
| import net.helcel.beans.helper.Data | ||||
| import net.helcel.beans.helper.Groups | ||||
| import net.helcel.beans.helper.Theme.colorToHex6 | ||||
|  | ||||
|  | ||||
| class EditGroupAddFragment( | ||||
|     private val key: Int = 0, | ||||
|     val onAddCb: (Int) -> Unit, | ||||
|     val onDelCb: (Int) -> Unit, | ||||
|     private val deleteEnabled: Boolean = true | ||||
| ) : DialogFragment() { | ||||
|  | ||||
|     private lateinit var _binding: FragmentEditGroupsAddBinding | ||||
|     private val grp = Data.groups.getGroupFromKey(key) | ||||
|  | ||||
|     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | ||||
|         val builder = MaterialAlertDialogBuilder(requireContext()) | ||||
|         _binding = FragmentEditGroupsAddBinding.inflate(layoutInflater) | ||||
|  | ||||
|         setupSlider(_binding.colorR, grp.color.color.red / 255F) | ||||
|         setupSlider(_binding.colorG, grp.color.color.green / 255F) | ||||
|         setupSlider(_binding.colorB, grp.color.color.blue / 255F) | ||||
|         setupText(_binding.groupColor, grp) | ||||
|  | ||||
|         _binding.colorView.background = ColorDrawable(grp.color.color) | ||||
|  | ||||
|  | ||||
|         if (key == 0 || !deleteEnabled) { | ||||
|             _binding.btnDelete.visibility = View.INVISIBLE | ||||
|             _binding.btnDelete.isEnabled = false | ||||
|         } | ||||
|         _binding.btnDelete.setOnClickListener { | ||||
|             MaterialAlertDialogBuilder(requireActivity()) | ||||
|                 .setMessage(R.string.delete_group) | ||||
|                 .setPositiveButton(android.R.string.ok) { _, _ -> | ||||
|                     val pos = Data.groups.findGroupPos(key) | ||||
|                     // Remove all countries belonging to that group | ||||
|                     Data.visits.deleteVisited(key) | ||||
|                     // Delete the group | ||||
|                     Data.groups.deleteGroup(key) | ||||
|                     Data.saveData() | ||||
|                     onDelCb(pos) | ||||
|                     dialog?.dismiss() | ||||
|                 } | ||||
|                 .setNegativeButton(android.R.string.cancel) { _, _ -> } | ||||
|                 .show() | ||||
|         } | ||||
|  | ||||
|         _binding.btnOk.setOnClickListener { | ||||
|             val name = _binding.groupName.text.toString() | ||||
|             val color = _binding.groupColor.text.toString() | ||||
|             val key = (if (key != 0) key else Data.groups.genKey()) | ||||
|             Data.groups.setGroup(key, name, ColorDrawable(Color.parseColor("#$color"))) | ||||
|             Data.saveData() | ||||
|             onAddCb(key) | ||||
|             dialog?.dismiss() | ||||
|         } | ||||
|  | ||||
|         _binding.btnCancel.setOnClickListener { | ||||
|             dialog?.cancel() | ||||
|         } | ||||
|  | ||||
|         _binding.groupName.setText(grp.name) | ||||
|         builder.setView(_binding.root) | ||||
|         return builder.create() | ||||
|     } | ||||
|  | ||||
|     private fun setupText(s: TextInputEditText, grp: Groups.Group?) { | ||||
|         s.setText(colorToHex6(ColorDrawable(grp?.color?.color ?: 0)).substring(1)) | ||||
|         s.addTextChangedListener( | ||||
|             EditTextListener( | ||||
|                 _binding.colorR, | ||||
|                 _binding.colorG, | ||||
|                 _binding.colorB, | ||||
|                 _binding.groupColor, | ||||
|                 _binding.colorView | ||||
|             ) | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     private fun setupSlider(s: Slider, v: Float) { | ||||
|         s.valueFrom = 0F | ||||
|         s.valueTo = 1F | ||||
|         s.value = v | ||||
|         s.addOnChangeListener( | ||||
|             SliderOnChangeListener( | ||||
|                 _binding.colorR, | ||||
|                 _binding.colorG, | ||||
|                 _binding.colorB, | ||||
|                 _binding.groupColor, | ||||
|                 _binding.colorView | ||||
|             ) | ||||
|         ) | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| private class EditTextListener( | ||||
|     private val colorEditR: Slider, | ||||
|     private val colorEditG: Slider, | ||||
|     private val colorEditB: Slider, | ||||
|     private val colorEditText: TextInputEditText, | ||||
|     private val colorView: View | ||||
| ) : TextWatcher { | ||||
|  | ||||
|     override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} | ||||
|     override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} | ||||
|     override fun afterTextChanged(s: Editable?) { | ||||
|         val col: Color | ||||
|         try { | ||||
|             col = Color.valueOf(Color.parseColor("#${colorEditText.text}")) | ||||
|         } catch (e: Exception) { | ||||
|             return | ||||
|         } | ||||
|  | ||||
|         colorEditR.value = col.red() | ||||
|         colorEditG.value = col.green() | ||||
|         colorEditB.value = col.blue() | ||||
|         colorView.background = ColorDrawable(col.toArgb()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| private class SliderOnChangeListener( | ||||
|     private val colorEditR: Slider, | ||||
|     private val colorEditG: Slider, | ||||
|     private val colorEditB: Slider, | ||||
|     private val colorEditText: TextInputEditText, | ||||
|     private val colorView: View | ||||
| ) : Slider.OnChangeListener { | ||||
|     override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) { | ||||
|         val rgb = | ||||
|             ColorDrawable(Color.argb(1F, colorEditR.value, colorEditG.value, colorEditB.value)) | ||||
|         colorEditText.setText(colorToHex6(rgb).substring(1)) | ||||
|         colorView.background = rgb | ||||
|     } | ||||
| } | ||||
| @@ -1,60 +0,0 @@ | ||||
| package net.helcel.beans.activity.fragment | ||||
|  | ||||
| import android.app.Dialog | ||||
| import android.content.DialogInterface | ||||
| import android.os.Bundle | ||||
| import android.view.View | ||||
| import androidx.fragment.app.DialogFragment | ||||
| import androidx.recyclerview.widget.LinearLayoutManager | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||||
| import net.helcel.beans.R | ||||
| import net.helcel.beans.activity.adapter.GroupListAdapter | ||||
| import net.helcel.beans.databinding.FragmentEditPlacesColorsBinding | ||||
| import net.helcel.beans.helper.Data | ||||
| import net.helcel.beans.helper.DialogCloser | ||||
|  | ||||
|  | ||||
| class EditPlaceColorFragment(private val parent: DialogCloser, private val delete: Boolean = false) : | ||||
|     DialogFragment() { | ||||
|  | ||||
|     private lateinit var _binding: FragmentEditPlacesColorsBinding | ||||
|     private lateinit var listAdapt: GroupListAdapter | ||||
|     private var clear: Boolean = false | ||||
|  | ||||
|     override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | ||||
|         val ctx = requireContext() | ||||
|         val builder = MaterialAlertDialogBuilder(ctx) | ||||
|         _binding = FragmentEditPlacesColorsBinding.inflate(layoutInflater) | ||||
|         _binding.btnAdd.setOnClickListener { | ||||
|             EditGroupAddFragment(0, { | ||||
|                 listAdapt.notifyItemInserted(Data.groups.findGroupPos(it)) | ||||
|             }, {}).show(requireActivity().supportFragmentManager, "AddColorDialogFragment") | ||||
|         } | ||||
|         _binding.btnClear.setOnClickListener { | ||||
|             clear = true | ||||
|             dialog?.dismiss() | ||||
|         } | ||||
|  | ||||
|         val dialog = builder.setView(_binding.root).create() | ||||
|         listAdapt = GroupListAdapter(requireActivity(), this, delete) | ||||
|         _binding.groupsColor.layoutManager = | ||||
|             LinearLayoutManager(ctx, RecyclerView.VERTICAL, false) | ||||
|         _binding.groupsColor.adapter = listAdapt | ||||
|  | ||||
|         if (delete) { | ||||
|             _binding.btnAdd.visibility = View.GONE | ||||
|             _binding.btnClear.text = ctx.getString(R.string.cancel) | ||||
|             _binding.warningText.text = ctx.getString(R.string.select_group) | ||||
|         } else { | ||||
|             _binding.warningText.text = ctx.getString(R.string.edit_group) | ||||
|         } | ||||
|  | ||||
|         return dialog | ||||
|     } | ||||
|  | ||||
|     override fun onDismiss(dialog: DialogInterface) { | ||||
|         super.onDismiss(dialog) | ||||
|         parent.onDialogDismiss(clear) | ||||
|     } | ||||
| } | ||||
| @@ -1,38 +0,0 @@ | ||||
| package net.helcel.beans.activity.fragment | ||||
|  | ||||
| import android.graphics.drawable.ColorDrawable | ||||
| import android.os.Bundle | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import androidx.fragment.app.Fragment | ||||
| import androidx.recyclerview.widget.LinearLayoutManager | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import net.helcel.beans.activity.adapter.GeolocListAdapter | ||||
| import net.helcel.beans.activity.adapter.GeolocListAdapter.FoldingListViewHolder | ||||
| import net.helcel.beans.activity.adapter.ViewPagerAdapter | ||||
| import net.helcel.beans.countries.GeoLoc | ||||
| import net.helcel.beans.databinding.FragmentEditPlacesBinding | ||||
|  | ||||
| class EditPlaceFragment(val loc: GeoLoc, private val pager: ViewPagerAdapter, private val holder: FoldingListViewHolder? = null) : Fragment() { | ||||
|     private lateinit var _binding: FragmentEditPlacesBinding | ||||
|  | ||||
|     override fun onCreateView( | ||||
|         inflater: LayoutInflater, | ||||
|         container: ViewGroup?, | ||||
|         savedInstanceState: Bundle? | ||||
|     ): View { | ||||
|         _binding = FragmentEditPlacesBinding.inflate(inflater, container, false) | ||||
|  | ||||
|         _binding.list.setItemViewCacheSize(5) | ||||
|         _binding.list.setHasFixedSize(true) | ||||
|         _binding.list.layoutManager = | ||||
|             LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false) | ||||
|         _binding.list.adapter = GeolocListAdapter(this, loc, pager, holder) | ||||
|         return _binding.root | ||||
|     } | ||||
|  | ||||
|     fun refreshColors(colorDrawable: ColorDrawable) { | ||||
|         (_binding.list.adapter as GeolocListAdapter?)?.refreshColors(colorDrawable) | ||||
|     } | ||||
| } | ||||
| @@ -1,33 +0,0 @@ | ||||
| package net.helcel.beans.activity.fragment | ||||
|  | ||||
| import android.os.Bundle | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import androidx.fragment.app.Fragment | ||||
| import com.mikepenz.aboutlibraries.LibsBuilder | ||||
| import net.helcel.beans.R | ||||
| import net.helcel.beans.databinding.FragmentLicenseBinding | ||||
|  | ||||
| class LicenseFragment : Fragment() { | ||||
|     private lateinit var _binding: FragmentLicenseBinding | ||||
|  | ||||
|  | ||||
|     override fun onCreateView( | ||||
|         inflater: LayoutInflater, | ||||
|         container: ViewGroup?, | ||||
|         savedInstanceState: Bundle? | ||||
|     ): View { | ||||
|         _binding = FragmentLicenseBinding.inflate(inflater, container, false) | ||||
|  | ||||
|         val librariesFragment = LibsBuilder() | ||||
|             .withLicenseShown(true) | ||||
|             .supportFragment() | ||||
|  | ||||
|         requireActivity().supportFragmentManager.beginTransaction() | ||||
|             .replace(R.id.license_fragment_view, librariesFragment) | ||||
|             .commit() | ||||
|  | ||||
|         return _binding.root | ||||
|     } | ||||
| } | ||||
| @@ -1,142 +0,0 @@ | ||||
| package net.helcel.beans.activity.fragment | ||||
|  | ||||
| import android.content.Context | ||||
| import android.os.Bundle | ||||
| import androidx.appcompat.app.AppCompatDelegate | ||||
| import androidx.preference.Preference | ||||
| import androidx.preference.PreferenceFragmentCompat | ||||
| import androidx.preference.PreferenceManager | ||||
| import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||||
| import net.helcel.beans.R | ||||
| import net.helcel.beans.countries.GeoLocImporter | ||||
| import net.helcel.beans.helper.Data | ||||
| import net.helcel.beans.helper.DialogCloser | ||||
| import net.helcel.beans.helper.Settings | ||||
|  | ||||
|  | ||||
| class SettingsFragment : PreferenceFragmentCompat(), DialogCloser { | ||||
|     private var savedInstanceState: Bundle? = null | ||||
|     private var rootKey: String? = null | ||||
|  | ||||
|     override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { | ||||
|         this.savedInstanceState = savedInstanceState | ||||
|         this.rootKey = rootKey | ||||
|  | ||||
|         setPreferencesFromResource(R.xml.fragment_settings, rootKey) | ||||
|         val ctx = requireContext() | ||||
|  | ||||
|         // Select Light/Dark/System Mode | ||||
|         findPreference<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) | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,78 @@ | ||||
| package net.helcel.beans.activity.sub | ||||
|  | ||||
| import androidx.compose.foundation.Image | ||||
| import androidx.compose.foundation.background | ||||
| import androidx.compose.foundation.clickable | ||||
| import androidx.compose.foundation.layout.* | ||||
| import androidx.compose.material.MaterialTheme | ||||
| import androidx.compose.material.Text | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.ui.Alignment | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.platform.LocalUriHandler | ||||
| import androidx.compose.ui.res.painterResource | ||||
| import androidx.compose.ui.res.stringResource | ||||
| import androidx.compose.ui.text.font.FontWeight | ||||
| import androidx.compose.ui.text.style.TextAlign | ||||
| import androidx.compose.ui.tooling.preview.Preview | ||||
| import androidx.compose.ui.unit.dp | ||||
| import androidx.compose.ui.unit.sp | ||||
| import net.helcel.beans.R | ||||
| import net.helcel.beans.BuildConfig | ||||
|  | ||||
| @Preview | ||||
| @Composable | ||||
| fun AboutScreen( | ||||
|     modifier: Modifier = Modifier | ||||
| ) { | ||||
|     Column( | ||||
|         modifier = modifier | ||||
|             .fillMaxSize() | ||||
|             .padding(top = 20.dp).background(MaterialTheme.colors.background), | ||||
|         horizontalAlignment = Alignment.CenterHorizontally, | ||||
|     ) { | ||||
|         Image( | ||||
|             painter = painterResource(id = R.drawable.ic_launcher_foreground), | ||||
|             contentDescription = "Logo", | ||||
|             modifier = Modifier | ||||
|                 .size(300.dp) | ||||
|         ) | ||||
|  | ||||
|         Text( | ||||
|             text = BuildConfig.APP_NAME, | ||||
|             fontSize = 30.sp, | ||||
|             color = MaterialTheme.colors.onBackground, | ||||
|             fontWeight = FontWeight.Bold, | ||||
|             textAlign = TextAlign.Center, | ||||
|             modifier = Modifier.padding(vertical = 15.dp, horizontal = 10.dp) | ||||
|         ) | ||||
|  | ||||
|         Text( | ||||
|             text = BuildConfig.VERSION_NAME, | ||||
|             fontSize = 25.sp, | ||||
|             color = MaterialTheme.colors.onBackground, | ||||
|             textAlign = TextAlign.Center, | ||||
|             modifier = Modifier.padding(vertical = 15.dp, horizontal = 10.dp) | ||||
|         ) | ||||
|  | ||||
|         Text( | ||||
|             text = stringResource(R.string.beans_is_foss), | ||||
|             textAlign = TextAlign.Center, | ||||
|             color = MaterialTheme.colors.onBackground, | ||||
|             modifier = Modifier.padding(vertical = 15.dp, horizontal = 10.dp) | ||||
|         ) | ||||
|  | ||||
|         val uriHandler = LocalUriHandler.current | ||||
|         val uri = stringResource(R.string.beans_repo_uri) | ||||
|         Text( | ||||
|             text = stringResource(id = R.string.beans_repo,uri), | ||||
|             textAlign = TextAlign.Center, | ||||
|             color = MaterialTheme.colors.onBackground, | ||||
|             modifier = Modifier | ||||
|                 .clickable { | ||||
|                     uriHandler.openUri(uri) | ||||
|                 } | ||||
|                 .padding(vertical = 15.dp, horizontal = 10.dp) | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,222 @@ | ||||
| package net.helcel.beans.activity.sub | ||||
|  | ||||
| import androidx.compose.foundation.background | ||||
| import androidx.compose.foundation.layout.Arrangement | ||||
| import androidx.compose.foundation.layout.Box | ||||
| import androidx.compose.foundation.layout.Column | ||||
| import androidx.compose.foundation.layout.Row | ||||
| import androidx.compose.foundation.layout.Spacer | ||||
| import androidx.compose.foundation.layout.fillMaxWidth | ||||
| import androidx.compose.foundation.layout.height | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.foundation.layout.size | ||||
| import androidx.compose.foundation.shape.CornerSize | ||||
| import androidx.compose.foundation.shape.RoundedCornerShape | ||||
| import androidx.compose.material.Button | ||||
| import androidx.compose.material.MaterialTheme | ||||
| import androidx.compose.material.Slider | ||||
| import androidx.compose.material.SliderDefaults | ||||
| import androidx.compose.material.Text | ||||
| import androidx.compose.material.TextButton | ||||
| import androidx.compose.material3.OutlinedTextField | ||||
| import androidx.compose.material3.OutlinedTextFieldDefaults | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.getValue | ||||
| import androidx.compose.runtime.mutableIntStateOf | ||||
| import androidx.compose.runtime.mutableStateOf | ||||
| import androidx.compose.runtime.remember | ||||
| import androidx.compose.runtime.setValue | ||||
| import androidx.compose.ui.Alignment | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.draw.clip | ||||
| import androidx.compose.ui.graphics.Color | ||||
| import androidx.compose.ui.graphics.toArgb | ||||
| import androidx.compose.ui.res.stringResource | ||||
| import androidx.compose.ui.text.TextStyle | ||||
| import androidx.compose.ui.tooling.preview.Preview | ||||
| import androidx.compose.ui.unit.dp | ||||
| import androidx.compose.ui.unit.sp | ||||
| import androidx.compose.ui.window.Dialog | ||||
| import net.helcel.beans.helper.Data | ||||
| import net.helcel.beans.helper.Theme.colorToHex6 | ||||
| import androidx.core.graphics.drawable.toDrawable | ||||
| import androidx.core.graphics.toColorInt | ||||
| import net.helcel.beans.R | ||||
|  | ||||
|  | ||||
| @Preview | ||||
| @Composable | ||||
| fun EditGroupPreview(){ | ||||
|     EditGroupDialog(0,true,{},{},{}) | ||||
| } | ||||
| @Composable | ||||
| fun EditGroupDialog( | ||||
|     key: Int = 0, | ||||
|     deleteEnabled: Boolean = true, | ||||
|     onAddCb: (Int) -> Unit, | ||||
|     onDelCb: (Int) -> Unit, | ||||
|     onDismiss: () -> Unit | ||||
| ) { | ||||
|     val group by remember { mutableStateOf(Data.groups.getGroupFromKey(key)) } | ||||
|     var name by remember { mutableStateOf(group.name) } | ||||
|     var colorHex by remember { | ||||
|         mutableStateOf(colorToHex6(group.color.color.toDrawable()).substring(1)) | ||||
|     } | ||||
|  | ||||
|     // Convert hex to Color safely | ||||
|     var color = remember {try { | ||||
|         Color("#$colorHex".toColorInt()) | ||||
|     } catch (_: Exception) { | ||||
|         Color.Gray | ||||
|     }} | ||||
|     var r by remember { mutableIntStateOf((color.red *255).toInt()) } | ||||
|     var g by remember { mutableIntStateOf((color.green*255).toInt()) } | ||||
|     var b by remember { mutableIntStateOf((color.blue*255).toInt()) } | ||||
|  | ||||
|     fun updateHexFromSliders() { | ||||
|         val newColor = Color(r, g, b) | ||||
|         colorHex = colorToHex6(newColor.toArgb().toDrawable()).substring(1) | ||||
|         color = newColor | ||||
|     } | ||||
|         Dialog( | ||||
|             onDismissRequest = onDismiss, | ||||
|             content = { | ||||
|                 Column( | ||||
|                     modifier = Modifier | ||||
|                         .background( | ||||
|                             MaterialTheme.colors.background, | ||||
|                             RoundedCornerShape(corner = CornerSize(16.dp)) | ||||
|                         ) | ||||
|                         .padding(16.dp), | ||||
|  | ||||
|                     ) { | ||||
|                     Text( | ||||
|                         color = MaterialTheme.colors.onBackground, | ||||
|                         style = MaterialTheme.typography.h6, | ||||
|                         text = if (key == 0) stringResource(R.string.action_add) | ||||
|                         else stringResource(R.string.action_edit), | ||||
|                     ) | ||||
|  | ||||
|                     Spacer(modifier = Modifier.height(16.dp)) | ||||
|                     Column( | ||||
|                         verticalArrangement = Arrangement.spacedBy(12.dp) | ||||
|                     ) { | ||||
|                         // Group name | ||||
|                         OutlinedTextField( | ||||
|                             value = name, | ||||
|                             onValueChange = { it: String -> name = it }, | ||||
|                             modifier = Modifier.fillMaxWidth(), | ||||
|                             placeholder = { Text(stringResource(R.string.name)) }, | ||||
|                             colors = OutlinedTextFieldDefaults.colors( | ||||
|                                 unfocusedTextColor = MaterialTheme.colors.onBackground, | ||||
|                                 focusedTextColor = MaterialTheme.colors.onBackground, | ||||
|                             ), | ||||
|                         ) | ||||
|                         Row( | ||||
|                             horizontalArrangement = Arrangement.spacedBy(16.dp), | ||||
|                             verticalAlignment = Alignment.CenterVertically | ||||
|                         ) { | ||||
|  | ||||
|                             // Color preview | ||||
|                             Box( | ||||
|                                 modifier = Modifier | ||||
|                                     .size(96.dp, (96).dp) | ||||
|                                     .clip(RoundedCornerShape(8.dp)) | ||||
|                                     .background(color), | ||||
|                                 propagateMinConstraints = true, | ||||
|  | ||||
|                                 content = {} | ||||
|                             ) | ||||
|                             Column { | ||||
|                                 ColorSlider( | ||||
|                                     r.toFloat(), | ||||
|                                     { r = it.toInt(); updateHexFromSliders() }, | ||||
|                                     Color(255, 0, 0) | ||||
|                                 ) | ||||
|                                 ColorSlider( | ||||
|                                     g.toFloat(), | ||||
|                                     { g = it.toInt(); updateHexFromSliders() }, | ||||
|                                     Color(0, 255, 0) | ||||
|                                 ) | ||||
|                                 ColorSlider( | ||||
|                                     b.toFloat(), | ||||
|                                     { b = it.toInt(); updateHexFromSliders() }, | ||||
|                                     Color(0, 0, 255) | ||||
|                                 ) | ||||
|                             } | ||||
|                         } | ||||
|                         // Hex input | ||||
|                         OutlinedTextField( | ||||
|                             value = colorHex, | ||||
|                             onValueChange = { n:String-> | ||||
|                                 colorHex = n.filter { it.isLetterOrDigit() } | ||||
|                             }, | ||||
|                             label = { Text(text="Color (hex)", color=MaterialTheme.colors.onBackground) }, | ||||
|                             singleLine = true, | ||||
|                             textStyle = TextStyle( | ||||
|                                 fontSize = 12.sp | ||||
|                             ), | ||||
|                             modifier = Modifier.fillMaxWidth(), | ||||
|                             colors = OutlinedTextFieldDefaults.colors( | ||||
|                                 unfocusedTextColor =MaterialTheme.colors.onBackground, | ||||
|                                 focusedTextColor = MaterialTheme.colors.onBackground, | ||||
|                             ), | ||||
|  | ||||
|                         ) | ||||
|                     } | ||||
|                     Spacer(modifier = Modifier.height(8.dp)) | ||||
|  | ||||
|                     Row( | ||||
|                         modifier = Modifier.fillMaxWidth(), | ||||
|                         horizontalArrangement = Arrangement.End | ||||
|                     ) { | ||||
|                         Button(onClick = { | ||||
|                             val newKey = if (key != 0) key else Data.groups.genKey() | ||||
|                             Data.groups.setGroup( | ||||
|                                 newKey, | ||||
|                                 name, | ||||
|                                 "#$colorHex".toColorInt().toDrawable() | ||||
|                             ) | ||||
|                             Data.saveData() | ||||
|                             onAddCb(newKey) | ||||
|                             onDismiss() | ||||
|                         }, ) { | ||||
|                             Text("OK") | ||||
|                         } | ||||
|                         if (key != 0 && deleteEnabled) { | ||||
|                             TextButton(onClick = { | ||||
|                                 val pos = Data.groups.findGroupPos(key) | ||||
|                                 Data.visits.deleteVisited(key) | ||||
|                                 Data.groups.deleteGroup(key) | ||||
|                                 Data.saveData() | ||||
|                                 onDelCb(pos) | ||||
|                                 onDismiss() | ||||
|                             }) { | ||||
|                                 Text("Delete") | ||||
|                             } | ||||
|                         } | ||||
|                         TextButton(onClick = { onDismiss() }) { | ||||
|                             Text("Cancel") | ||||
|                         } | ||||
|  | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|         ) | ||||
| } | ||||
|  | ||||
| @Composable | ||||
| fun ColorSlider(v: Float, onChange:(Float)->Unit, c:Color ){ | ||||
|     Slider( | ||||
|         value = v, | ||||
|         onValueChange = onChange, | ||||
|         valueRange = 0f..255f, | ||||
|         steps = 255, | ||||
|         modifier = Modifier.height(32.dp), | ||||
|         colors = SliderDefaults.colors( | ||||
|             thumbColor = c, | ||||
|             activeTickColor = c, | ||||
|             inactiveTickColor = MaterialTheme.colors.onBackground, | ||||
|         ) | ||||
|     ) | ||||
| } | ||||
| @@ -0,0 +1,193 @@ | ||||
| package net.helcel.beans.activity.sub | ||||
|  | ||||
| import androidx.compose.foundation.background | ||||
| import androidx.compose.foundation.combinedClickable | ||||
| import androidx.compose.foundation.layout.Arrangement | ||||
| import androidx.compose.foundation.layout.Box | ||||
| import androidx.compose.foundation.layout.Column | ||||
| import androidx.compose.foundation.layout.Row | ||||
| import androidx.compose.foundation.layout.Spacer | ||||
| import androidx.compose.foundation.layout.fillMaxWidth | ||||
| import androidx.compose.foundation.layout.height | ||||
| import androidx.compose.foundation.layout.heightIn | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.foundation.layout.size | ||||
| import androidx.compose.foundation.layout.width | ||||
| import androidx.compose.foundation.lazy.LazyColumn | ||||
| import androidx.compose.foundation.lazy.items | ||||
| import androidx.compose.foundation.shape.CircleShape | ||||
| import androidx.compose.foundation.shape.CornerSize | ||||
| import androidx.compose.foundation.shape.RoundedCornerShape | ||||
| import androidx.compose.material.Button | ||||
| import androidx.compose.material.MaterialTheme | ||||
| import androidx.compose.material.Text | ||||
| import androidx.compose.material.TextButton | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.collectAsState | ||||
| import androidx.compose.runtime.getValue | ||||
| import androidx.compose.runtime.mutableIntStateOf | ||||
| import androidx.compose.runtime.mutableStateOf | ||||
| import androidx.compose.runtime.remember | ||||
| import androidx.compose.runtime.setValue | ||||
| import androidx.compose.ui.Alignment | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.graphics.Color | ||||
| import androidx.compose.ui.graphics.toArgb | ||||
| import androidx.compose.ui.res.stringResource | ||||
| import androidx.compose.ui.tooling.preview.Preview | ||||
| import androidx.compose.ui.unit.dp | ||||
| import androidx.compose.ui.window.Dialog | ||||
| import net.helcel.beans.R | ||||
| import net.helcel.beans.helper.Data | ||||
| import net.helcel.beans.helper.Groups | ||||
| import androidx.core.graphics.drawable.toDrawable | ||||
| import net.helcel.beans.activity.SysTheme | ||||
|  | ||||
| @Composable | ||||
| fun EditPlaceDialog(delete: Boolean, onDialogDismiss: (Boolean)->Unit){ | ||||
|     SysTheme { | ||||
|         var showEditGroupDialog by remember { mutableStateOf(false) } | ||||
|         var showEditPlaceColorDialog by remember { mutableStateOf(true) } | ||||
|         var showSelectedKey by remember { mutableIntStateOf(-1) } | ||||
|         var showDelete by remember { mutableStateOf(false) } | ||||
|         if (showEditGroupDialog) | ||||
|             EditGroupDialog( | ||||
|                 key = showSelectedKey, | ||||
|                 deleteEnabled = showDelete, | ||||
|                 onAddCb = { }, | ||||
|                 onDelCb = { | ||||
|  | ||||
|                 }, | ||||
|                 onDismiss = { | ||||
|                     showEditGroupDialog = false | ||||
|                 }, | ||||
|             ) | ||||
|         if (showEditPlaceColorDialog) | ||||
|             EditPlaceColorDialog( | ||||
|                 delete, | ||||
|                 onAdd = { | ||||
|                     showSelectedKey = it | ||||
|                     showDelete = false | ||||
|                     showEditGroupDialog = true | ||||
|                 }, | ||||
|                 onDelete = { | ||||
|                     showSelectedKey = it | ||||
|                     showDelete = true | ||||
|                     showEditGroupDialog = true | ||||
|                 }, | ||||
|                 onClear = { | ||||
|                     showEditPlaceColorDialog = false | ||||
|                     onDialogDismiss(true) | ||||
|                 }, | ||||
|                 onDismiss = { | ||||
|                     showEditPlaceColorDialog = false | ||||
|                     onDialogDismiss(false) | ||||
|                 } | ||||
|             ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @Preview | ||||
| @Composable | ||||
| fun GroupListPreview() { | ||||
|     Data.groups = Groups(0, HashMap()) | ||||
|     Data.groups.setGroup(0, "Testing", Color.Red.toArgb().toDrawable()) | ||||
|     Data.groups.setGroup(1, "Testing", Color.Blue.toArgb().toDrawable()) | ||||
|     EditPlaceColorDialog(false,{},{},{},{}) | ||||
| } | ||||
|  | ||||
| @Composable | ||||
| fun EditPlaceColorDialog( | ||||
|     deleteMode: Boolean = false, | ||||
|     onAdd: (Int) -> Unit = {}, | ||||
|     onDelete: (Int) -> Unit= {}, | ||||
|     onClear: () -> Unit= {}, | ||||
|     onDismiss: () -> Unit= {}, | ||||
| ) { | ||||
|     val groups by Data.groups.groupsFlow.collectAsState() | ||||
|  | ||||
|     Dialog( | ||||
|         onDismissRequest = onDismiss, | ||||
|         content = { | ||||
|             Column( | ||||
|                 modifier = Modifier | ||||
|                     .background( | ||||
|                         MaterialTheme.colors.background, | ||||
|                         RoundedCornerShape(corner = CornerSize(16.dp))) | ||||
|                     .padding(16.dp) | ||||
|                 , | ||||
|  | ||||
|                 ) { | ||||
|                 Text( | ||||
|                     style = MaterialTheme.typography.h6, | ||||
|                     color=MaterialTheme.colors.onBackground, | ||||
|                     text = if (deleteMode) stringResource(R.string.select_group) | ||||
|                     else stringResource(R.string.edit_group) | ||||
|                 ) | ||||
|                 Text( | ||||
|                     style = MaterialTheme.typography.caption, | ||||
|                     color=MaterialTheme.colors.onBackground, | ||||
|                     text = if (deleteMode) stringResource(R.string.select_group_sub) | ||||
|                     else stringResource(R.string.edit_group_sub) | ||||
|                 ) | ||||
|                 Spacer(modifier = Modifier.height(16.dp)) | ||||
|                 Box( | ||||
|                     modifier = Modifier | ||||
|                         .fillMaxWidth() | ||||
|                         .heightIn(max = 300.dp) // cap dialog growth | ||||
|                 ) { | ||||
|                     LazyColumn( | ||||
|                         modifier = Modifier | ||||
|                             .fillMaxWidth() | ||||
|                         //.weight(1f) | ||||
|                     ) { | ||||
|                         items(groups, key = { it.key }) { group -> | ||||
|                             Row( | ||||
|                                 modifier = Modifier | ||||
|                                     .fillMaxWidth() | ||||
|                                     .combinedClickable( | ||||
|                                         onClick = { Data.selected_group = group; onDismiss() }, | ||||
|                                         onLongClick = { onDelete(group.key) }) | ||||
|                                     .background( | ||||
|                                         Color(88, 88, 88, 88), | ||||
|                                         RoundedCornerShape(corner = CornerSize(16.dp)) | ||||
|                                     ) | ||||
|                                     .padding(8.dp), | ||||
|                                 verticalAlignment = Alignment.CenterVertically, | ||||
|  | ||||
|                                 ) { | ||||
|                                 Box( | ||||
|                                     modifier = Modifier | ||||
|                                         .size(24.dp) | ||||
|                                         .background(Color(group.color.color), CircleShape) | ||||
|                                 ) | ||||
|                                 Spacer(modifier = Modifier.width(8.dp)) | ||||
|                                 Text(color=MaterialTheme.colors.onBackground,text=group.name) | ||||
|                             } | ||||
|                             Spacer(modifier = Modifier.height(8.dp)) | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 Spacer(modifier = Modifier.height(8.dp)) | ||||
|  | ||||
|                 Row( | ||||
|                     modifier = Modifier.fillMaxWidth(), | ||||
|                     horizontalArrangement = Arrangement.End) { | ||||
|                     if (!deleteMode) { | ||||
|                         Button(onClick = { onAdd(0) }) { | ||||
|                             Text(stringResource(R.string.action_add)) | ||||
|                         } | ||||
|                     } | ||||
|                     TextButton(onClick = { | ||||
|                         if (deleteMode) onDismiss() else onClear() | ||||
|                     }) { | ||||
|                         Text( | ||||
|                             text = if (deleteMode) stringResource(R.string.cancel) | ||||
|                             else stringResource(R.string.action_clear) | ||||
|                         ) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|     ) | ||||
| } | ||||
| @@ -0,0 +1,218 @@ | ||||
| package net.helcel.beans.activity.sub | ||||
|  | ||||
|  | ||||
| import androidx.activity.compose.BackHandler | ||||
| import androidx.compose.foundation.background | ||||
| import androidx.compose.foundation.clickable | ||||
| import androidx.compose.foundation.layout.Column | ||||
| import androidx.compose.foundation.layout.Row | ||||
| import androidx.compose.foundation.layout.Spacer | ||||
| import androidx.compose.foundation.layout.fillMaxSize | ||||
| import androidx.compose.foundation.layout.fillMaxWidth | ||||
| import androidx.compose.foundation.layout.height | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.foundation.layout.size | ||||
| import androidx.compose.foundation.lazy.LazyColumn | ||||
| import androidx.compose.foundation.lazy.items | ||||
| import androidx.compose.material.CheckboxDefaults | ||||
| import androidx.compose.material.MaterialTheme | ||||
| import androidx.compose.material.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)) | ||||
| } | ||||
| @@ -0,0 +1,43 @@ | ||||
| package net.helcel.beans.activity.sub | ||||
|  | ||||
| import androidx.compose.foundation.layout.fillMaxSize | ||||
| import androidx.compose.material.MaterialTheme | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.tooling.preview.Preview | ||||
| import com.mikepenz.aboutlibraries.ui.compose.DefaultChipColors | ||||
| import com.mikepenz.aboutlibraries.ui.compose.DefaultLibraryColors | ||||
| import com.mikepenz.aboutlibraries.ui.compose.android.rememberLibraries | ||||
| import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer | ||||
| import net.helcel.beans.R | ||||
| import net.helcel.beans.activity.SysTheme | ||||
|  | ||||
|  | ||||
| @Preview | ||||
| @Composable | ||||
| fun LicenseScreen() { | ||||
|     val libraries = rememberLibraries(R.raw.aboutlibraries) | ||||
|     SysTheme { | ||||
|         LibrariesContainer( | ||||
|             libraries = libraries.value, | ||||
|             modifier = Modifier.fillMaxSize(), | ||||
|             colors = DefaultLibraryColors( | ||||
|                 backgroundColor = MaterialTheme.colors.background, | ||||
|                 contentColor = MaterialTheme.colors.onBackground, | ||||
|                 licenseChipColors = DefaultChipColors( | ||||
|                     containerColor = MaterialTheme.colors.primary, | ||||
|                     contentColor = MaterialTheme.colors.onPrimary, | ||||
|                 ), | ||||
|                 versionChipColors = DefaultChipColors( | ||||
|                     containerColor = MaterialTheme.colors.secondary, | ||||
|                     contentColor = MaterialTheme.colors.onSecondary, | ||||
|                 ), | ||||
|                 fundingChipColors = DefaultChipColors( | ||||
|                     containerColor = MaterialTheme.colors.secondary, | ||||
|                     contentColor = MaterialTheme.colors.onSecondary, | ||||
|                 ), | ||||
|                 dialogConfirmButtonColor = MaterialTheme.colors.primary, | ||||
|             ) | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| @@ -1,13 +1,21 @@ | ||||
| package net.helcel.beans.helper | ||||
|  | ||||
| import android.content.ContentValues | ||||
| import android.content.Context | ||||
| import android.content.SharedPreferences | ||||
| import android.graphics.drawable.ColorDrawable | ||||
| import android.net.Uri | ||||
| import android.os.Build | ||||
| import android.os.Environment | ||||
| import android.provider.MediaStore | ||||
| import androidx.core.content.ContextCompat | ||||
| import androidx.preference.PreferenceManager | ||||
| import net.helcel.beans.R | ||||
| import net.helcel.beans.countries.GeoLoc | ||||
| import java.util.HashMap | ||||
| import androidx.core.graphics.drawable.toDrawable | ||||
| import androidx.core.content.edit | ||||
| import android.content.Intent | ||||
| import java.io.File | ||||
|  | ||||
| object Data { | ||||
|     var visits : Visits = Visits(0, HashMap()) | ||||
| @@ -33,7 +41,8 @@ object Data { | ||||
|  | ||||
|         // Add default group "Visited" with app's color if there is no group already | ||||
|         if (groups.size() == 0) { | ||||
|             groups.setGroup(DEFAULT_GROUP, "Visited", ColorDrawable(ContextCompat.getColor(ctx, R.color.blue))) | ||||
|             groups.setGroup(DEFAULT_GROUP, "Visited", | ||||
|                 ContextCompat.getColor(ctx, R.color.blue).toDrawable()) | ||||
|             saveData() | ||||
|         } | ||||
|     } | ||||
| @@ -41,9 +50,73 @@ object Data { | ||||
|     fun saveData() { | ||||
|         if(groups.id != visits.id) return | ||||
|         val id = groups.id | ||||
|         val editor = sharedPreferences.edit() | ||||
|         editor.putString("groups_$id", groupsSerial.writeTo(groups)) | ||||
|         editor.putString("visits_$id", visitsSerial.writeTo(visits)) | ||||
|         editor.apply() | ||||
|         sharedPreferences.edit { | ||||
|             putString("groups_$id", groupsSerial.writeTo(groups)) | ||||
|             putString("visits_$id", visitsSerial.writeTo(visits)) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun exportData(ctx: Context, filepath: Uri){ | ||||
|         val groupsJson = groupsSerial.writeTo(groups) | ||||
|         val visitsJson = visitsSerial.writeTo(visits) | ||||
|         val outputStream = ctx.contentResolver.openOutputStream(filepath) | ||||
|         outputStream?.write( | ||||
|             buildString { | ||||
|                 append(groupsJson) | ||||
|                 append("\n---\n") // optional separator | ||||
|                 append(visitsJson) | ||||
|             }.toByteArray()) | ||||
|         outputStream?.flush() | ||||
|         outputStream?.close() | ||||
|     } | ||||
|  | ||||
|  | ||||
|     fun importData(ctx: Context, filePath: Uri) { | ||||
|         val inputStream = ctx.contentResolver.openInputStream(filePath) | ||||
|         val data = inputStream?.bufferedReader().use { it?.readText() } | ||||
|         if(data==null) return | ||||
|         val lines = data.split("\n---\n") | ||||
|         val groupsJson = lines[0] | ||||
|         val visitsJson = lines[1] | ||||
|  | ||||
|         groups = if(groupsJson.isNotEmpty()) groupsSerial.readFrom(groupsJson.byteInputStream()) else groupsSerial.defaultValue | ||||
|         visits = if(visitsJson.isNotEmpty()) visitsSerial.readFrom(visitsJson.byteInputStream()) else visitsSerial.defaultValue | ||||
|  | ||||
|         // Add default group "Visited" with app's color if there is no group already | ||||
|         if (groups.size() == 0) { | ||||
|             groups.setGroup(DEFAULT_GROUP, "Visited", | ||||
|                 ContextCompat.getColor(ctx, R.color.blue).toDrawable()) | ||||
|         } | ||||
|         saveData() | ||||
|     } | ||||
|  | ||||
|     fun doImport(ctx: Context, file: Uri?){ | ||||
|         if(file!=null) { | ||||
|             importData(ctx, file) | ||||
|             val intent = ctx.packageManager | ||||
|                 .getLaunchIntentForPackage(ctx.packageName)?.apply { | ||||
|                     addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) | ||||
|                 } | ||||
|             ctx.startActivity(intent) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun doExport(ctx: Context){ | ||||
|         val fileName = "beans_backup.json" | ||||
|         val resolver = ctx.contentResolver | ||||
|         val contentValues = ContentValues().apply { | ||||
|             put(MediaStore.Downloads.DISPLAY_NAME, fileName) // "backup.json" | ||||
|             put(MediaStore.Downloads.MIME_TYPE, "text/*") | ||||
|             put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS) | ||||
|         } | ||||
|         val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { | ||||
|             resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues) | ||||
|         } else { | ||||
|             val downloadsDir = | ||||
|                 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) | ||||
|             val file = File(downloadsDir, fileName) | ||||
|             Uri.fromFile(file) | ||||
|         } | ||||
|         if(uri!=null) exportData(ctx, uri) | ||||
|     } | ||||
| } | ||||
| @@ -1,5 +0,0 @@ | ||||
| package net.helcel.beans.helper | ||||
|  | ||||
| interface DialogCloser { | ||||
|     fun onDialogDismiss(clear: Boolean) | ||||
| } | ||||
| @@ -2,15 +2,16 @@ package net.helcel.beans.helper | ||||
|  | ||||
| import android.graphics.Color | ||||
| import android.graphics.drawable.ColorDrawable | ||||
| import androidx.core.content.ContextCompat | ||||
| import kotlinx.serialization.ExperimentalSerializationApi | ||||
| import kotlinx.serialization.Serializable | ||||
| import kotlinx.serialization.Serializer | ||||
| import kotlinx.serialization.json.Json | ||||
| import net.helcel.beans.R | ||||
| import java.io.InputStream | ||||
| import kotlin.coroutines.coroutineContext | ||||
| import kotlin.random.Random | ||||
| import androidx.core.graphics.drawable.toDrawable | ||||
| import kotlinx.coroutines.flow.MutableStateFlow | ||||
| import kotlinx.coroutines.flow.StateFlow | ||||
| import kotlinx.coroutines.flow.asStateFlow | ||||
|  | ||||
|  | ||||
| private const val randSeed = 0 | ||||
| @@ -20,20 +21,23 @@ const val NO_GROUP = 0 | ||||
| const val DEFAULT_GROUP = 1 | ||||
| const val AUTO_GROUP = -1 | ||||
|  | ||||
|  | ||||
|  | ||||
| @Serializable | ||||
| class Groups(val id: Int, private val grps: HashMap<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) { | ||||
|         grps[key] = Group(key, name, col) | ||||
|         _groupsFlow.value = grps.values.toList() | ||||
|     } | ||||
|  | ||||
|     fun deleteGroup(key: Int) { | ||||
|         grps.remove(key) | ||||
|     } | ||||
|  | ||||
|     fun deleteAllExcept(grp: Int) { | ||||
|         val keysToDelete = grps.keys.filter { it != grp } | ||||
|         keysToDelete.forEach { grps.remove(it) } | ||||
|         _groupsFlow.value = grps.values.toList() | ||||
|     } | ||||
|  | ||||
|     fun getGroupFromKey(key: Int): Group { | ||||
| @@ -60,6 +64,7 @@ class Groups(val id: Int, private val grps: HashMap<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] | ||||
|         return Pair(key, getGroupFromKey(key)) | ||||
|     } | ||||
| @@ -74,9 +79,7 @@ class Groups(val id: Int, private val grps: HashMap<Int, Group>) { | ||||
|     open class Group( | ||||
|         val key: Int, | ||||
|         val name: String, | ||||
|         @Serializable(with = Theme.ColorDrawableSerializer::class) val color: ColorDrawable = ColorDrawable( | ||||
|             Color.GRAY | ||||
|         ) | ||||
|         @Serializable(with = Theme.ColorDrawableSerializer::class) val color: ColorDrawable = Color.GRAY.toDrawable() | ||||
|     ) | ||||
|  | ||||
|     @OptIn(ExperimentalSerializationApi::class) | ||||
|   | ||||
| @@ -4,19 +4,15 @@ import android.content.Context | ||||
| import android.content.SharedPreferences | ||||
| import androidx.preference.PreferenceManager | ||||
| import net.helcel.beans.R | ||||
| import net.helcel.beans.activity.MainActivity | ||||
| import net.helcel.beans.activity.fragment.SettingsFragment | ||||
| import net.helcel.beans.activity.MainScreen | ||||
|  | ||||
| object Settings { | ||||
|  | ||||
|     private lateinit var sp: SharedPreferences | ||||
|     private lateinit var mainActivity: MainActivity | ||||
|     fun start(ctx: MainActivity) { | ||||
|     private lateinit var mainActivity: MainScreen | ||||
|     fun start(ctx: MainScreen) { | ||||
|         mainActivity = ctx | ||||
|         sp = PreferenceManager.getDefaultSharedPreferences(ctx) | ||||
|         SettingsFragment.setTheme( | ||||
|             ctx, sp.getString(ctx.getString(R.string.key_theme), ctx.getString(R.string.system)) | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     fun isSingleGroup(ctx: Context): Boolean { | ||||
| @@ -41,7 +37,7 @@ object Settings { | ||||
|     } | ||||
|  | ||||
|     fun refreshProjection(): Boolean { | ||||
|         mainActivity.refreshProjection() | ||||
|         (mainActivity).refreshProjection() | ||||
|         return true | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -1,23 +1,16 @@ | ||||
| package net.helcel.beans.helper | ||||
|  | ||||
| import android.content.Context | ||||
| import android.graphics.Color | ||||
| import android.graphics.drawable.ColorDrawable | ||||
| import android.util.TypedValue | ||||
| import androidx.appcompat.app.AppCompatActivity | ||||
| import androidx.core.graphics.ColorUtils | ||||
| import kotlinx.serialization.KSerializer | ||||
| import kotlinx.serialization.descriptors.PrimitiveKind | ||||
| import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor | ||||
| import kotlinx.serialization.encoding.Decoder | ||||
| import kotlinx.serialization.encoding.Encoder | ||||
| import androidx.core.graphics.drawable.toDrawable | ||||
|  | ||||
| object Theme { | ||||
|     fun colorWrapper(ctx: Context, res: Int): ColorDrawable { | ||||
|         val colorPrimaryTyped = TypedValue() | ||||
|         ctx.theme.resolveAttribute(res, colorPrimaryTyped, true) | ||||
|         return ColorDrawable(colorPrimaryTyped.data) | ||||
|     } | ||||
|  | ||||
|     fun colorToHex6(c: ColorDrawable): String { | ||||
|         return '#' + colorToHex8(c).substring(3) | ||||
| @@ -28,11 +21,6 @@ object Theme { | ||||
|         return '#' + c.color.toHexString() | ||||
|     } | ||||
|  | ||||
|     fun createActionBar(ctx: AppCompatActivity, title: String) { | ||||
|         ctx.supportActionBar?.title = title | ||||
|         ctx.supportActionBar?.setDisplayHomeAsUpEnabled(true) | ||||
|     } | ||||
|  | ||||
|     fun getContrastColor(color: Int): Int { | ||||
|         val whiteContrast = ColorUtils.calculateContrast(Color.WHITE, color) | ||||
|         val blackContrast = ColorUtils.calculateContrast(Color.BLACK, color) | ||||
| @@ -43,7 +31,7 @@ object Theme { | ||||
|         override val descriptor = PrimitiveSerialDescriptor("ColorDrawable", PrimitiveKind.INT) | ||||
|  | ||||
|         override fun deserialize(decoder: Decoder): ColorDrawable { | ||||
|             return ColorDrawable(decoder.decodeInt()) | ||||
|             return decoder.decodeInt().toDrawable() | ||||
|         } | ||||
|  | ||||
|         override fun serialize(encoder: Encoder, value: ColorDrawable) { | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| package net.helcel.beans.helper | ||||
|  | ||||
| import kotlinx.coroutines.flow.MutableStateFlow | ||||
| import kotlinx.coroutines.flow.StateFlow | ||||
| import kotlinx.serialization.ExperimentalSerializationApi | ||||
| import kotlinx.serialization.Serializable | ||||
| import kotlinx.serialization.Serializer | ||||
| @@ -11,9 +13,17 @@ import java.io.InputStream | ||||
| @Serializable | ||||
| class Visits(val id: Int, private val locs: HashMap<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) { | ||||
|         if (key == null) | ||||
|             return | ||||
|         _visitsFlow.value = _visitsFlow.value.toMutableMap().apply { | ||||
|             this[key.code] = b | ||||
|         } | ||||
|         locs[key.code] = b | ||||
|     } | ||||
|  | ||||
| @@ -21,6 +31,9 @@ class Visits(val id: Int, private val locs: HashMap<String, Int>) { | ||||
|         val keysToDelete = locs | ||||
|             .filter { it.value == key } | ||||
|             .map { it.key } | ||||
|         _visitsFlow.value = _visitsFlow.value.toMutableMap().apply { | ||||
|             keysToDelete.forEach { this.remove(it)} | ||||
|         } | ||||
|         keysToDelete.forEach { | ||||
|             locs.remove(it) | ||||
|         } | ||||
| @@ -53,6 +66,7 @@ class Visits(val id: Int, private val locs: HashMap<String, Int>) { | ||||
|         keys.forEach { | ||||
|             locs[it] = group | ||||
|         } | ||||
|         _visitsFlow.value = locs | ||||
|     } | ||||
|  | ||||
|     @OptIn(ExperimentalSerializationApi::class) | ||||
|   | ||||
| @@ -1,6 +1,10 @@ | ||||
| package net.helcel.beans.svg | ||||
|  | ||||
| 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.helper.AUTO_GROUP | ||||
| import net.helcel.beans.helper.Data.groups | ||||
| @@ -8,15 +12,10 @@ import net.helcel.beans.helper.Data.visits | ||||
| import net.helcel.beans.helper.NO_GROUP | ||||
| import net.helcel.beans.helper.Settings | ||||
| import net.helcel.beans.helper.Theme.colorToHex6 | ||||
| import net.helcel.beans.helper.Theme.colorWrapper | ||||
|  | ||||
|  | ||||
| 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 countries: String = World.WWW.children.joinToString(",") { itt -> | ||||
|         itt.children.joinToString(",") { "#${it.code}2" } | ||||
| @@ -24,18 +23,22 @@ class CSSWrapper(private val ctx: Context) { | ||||
|     private val regional: String = World.WWW.children.joinToString(",") { itt -> | ||||
|         itt.children.joinToString(",") { "#${it.code}1" } | ||||
|     } | ||||
|     private val countryOnlyCSS: String = | ||||
|         "svg{fill:$colorForeground;stroke:$colorBackground;stroke-width:0.1;}" + | ||||
|                 "${regional}{display:none;}" | ||||
|     private val countryRegionalCSS: String = | ||||
|         "svg{fill:$colorForeground;stroke:$colorBackground;stroke-width:0.01;}" + | ||||
|                 "$continents,$countries{fill:none;stroke:$colorBackground;stroke-width:0.1;}" | ||||
|  | ||||
|     @Composable | ||||
|     fun getBaseColors() : Pair<String, String> { | ||||
|         val colorForeground = colorToHex6(MaterialTheme.colors.onBackground.toArgb().toDrawable()) | ||||
|         val colorBackground = colorToHex6(MaterialTheme.colors.background.toArgb().toDrawable()) | ||||
|  | ||||
|         return Pair(colorForeground, colorBackground) | ||||
|     } | ||||
|  | ||||
|     private var customCSS: String = "" | ||||
|  | ||||
|     init { | ||||
|         refresh() | ||||
|     } | ||||
|  | ||||
|  | ||||
|     private fun refresh() { | ||||
|         val id = if (Settings.isRegional(ctx)) "1" else "2" | ||||
|         customCSS = visits.getVisitedByValue().map { (k, v) -> | ||||
| @@ -47,20 +50,24 @@ class CSSWrapper(private val ctx: Context) { | ||||
|                 emptyList() | ||||
|             }).takeIf { it.isNotEmpty() } | ||||
|                 ?.joinToString(",") { "#${it}$id,#${it}" } + "{fill:${ | ||||
|                 colorToHex6( | ||||
|                     if (k == AUTO_GROUP) | ||||
|                         colorWrapper(ctx, android.R.attr.colorPrimary) | ||||
|                     else groups.getGroupFromKey(k).color | ||||
|                 ) | ||||
|                 if (k == AUTO_GROUP) colorToHex6(groups.getGroupFromPos(0).second.color)  | ||||
|                 else colorToHex6(groups.getGroupFromKey(k).color) | ||||
|             };}" | ||||
|         }.joinToString("") | ||||
|     } | ||||
|  | ||||
|     @Composable | ||||
|     fun get(): String { | ||||
|         val (colorForeground,colorBackground) = getBaseColors() | ||||
|         refresh() | ||||
|         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 | ||||
|         } else { | ||||
|             val countryOnlyCSS: String = | ||||
|                 "svg{fill:$colorForeground;stroke:$colorBackground;stroke-width:0.1;}" + | ||||
|                         "${regional}{display:none;}" | ||||
|             countryOnlyCSS + customCSS | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -1,13 +0,0 @@ | ||||
| <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> | ||||
| @@ -1,9 +0,0 @@ | ||||
| <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> | ||||
| @@ -1,5 +0,0 @@ | ||||
| <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> | ||||
| @@ -1,10 +0,0 @@ | ||||
| <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> | ||||
| @@ -1,41 +0,0 @@ | ||||
| <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> | ||||
| @@ -1,10 +0,0 @@ | ||||
| <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> | ||||
| @@ -1,16 +0,0 @@ | ||||
| <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> | ||||
| @@ -1,18 +0,0 @@ | ||||
| <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> | ||||
| @@ -1,16 +0,0 @@ | ||||
| <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> | ||||
| @@ -1,14 +0,0 @@ | ||||
| <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> | ||||
| @@ -1,14 +0,0 @@ | ||||
| <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> | ||||
| @@ -1,12 +0,0 @@ | ||||
| <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> | ||||
| @@ -1,21 +0,0 @@ | ||||
| <?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> | ||||
| @@ -1,14 +0,0 @@ | ||||
| <?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> | ||||
| @@ -1,15 +0,0 @@ | ||||
| <?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> | ||||
| @@ -1,66 +0,0 @@ | ||||
| <?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> | ||||
| @@ -1,67 +0,0 @@ | ||||
| <?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> | ||||
| @@ -1,137 +0,0 @@ | ||||
| <?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> | ||||
| @@ -1,26 +0,0 @@ | ||||
| <?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> | ||||
| @@ -1,59 +0,0 @@ | ||||
| <?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> | ||||
|  | ||||
| @@ -1,13 +0,0 @@ | ||||
| <?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> | ||||
| @@ -1,52 +0,0 @@ | ||||
| <?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> | ||||
| @@ -1,34 +0,0 @@ | ||||
| <?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> | ||||
| @@ -1,13 +0,0 @@ | ||||
| <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> | ||||
| @@ -1,25 +0,0 @@ | ||||
| <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> | ||||
| @@ -1,25 +0,0 @@ | ||||
| <?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"?> | ||||
| <resources> | ||||
|     <string name="app_name">Beans</string> | ||||
|     <string name="app_version">1.0a</string> | ||||
|     <string name="action_settings">Settings</string> | ||||
|     <string name="action_stat">Stats</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="key_theme">App theme</string> | ||||
|     <string name="system">System</string> | ||||
| @@ -18,16 +18,17 @@ | ||||
|     <string name="key_regional">Regional</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_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_uri">https://github.com/helcel-net/beans</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="about_beans">About the Beans application</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">Select the group to assign.</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="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="add">Add</string> | ||||
|     <string name="clear">Clear</string> | ||||
|     <string name="logo">Logo</string> | ||||
|  | ||||
|     <string name="name">Name</string> | ||||
|     <string name="rate">%1$d/%2$d</string> | ||||
|     <string name="rate_with_unit">%1$d / %2$d %3$s</string> | ||||
| @@ -39,9 +40,10 @@ | ||||
|     <string name="off">Off</string> | ||||
|     <string name="delete">Delete</string> | ||||
|     <string name="cancel">Cancel</string> | ||||
|     <string name="ok">Ok</string> | ||||
|     <string name="ok">OK</string> | ||||
|     <string name="total">Total</string> | ||||
|     <string name="uncategorized">Uncategorized</string> | ||||
|  | ||||
|     <string name="azimuthalequidistant">Azimuthal Equidistant</string> | ||||
|     <string name="mercator">Mercator</string> | ||||
|     <string name="loximuthal">Loximuthal</string> | ||||
|   | ||||
| @@ -1,32 +0,0 @@ | ||||
| <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> | ||||
| @@ -1,73 +0,0 @@ | ||||
| <?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. | ||||
| plugins { | ||||
|     id 'com.android.application' version '8.4.2' apply false | ||||
|     id 'com.android.library' version '8.4.2' apply false | ||||
|     id 'org.jetbrains.kotlin.android' version '2.0.0' apply false | ||||
|     id 'com.android.application' version '8.13.0' apply false | ||||
|     id 'com.android.library' version '8.13.0' apply false | ||||
|     id 'org.jetbrains.kotlin.android' version '2.2.20' 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,7 +1,7 @@ | ||||
| distributionBase=GRADLE_USER_HOME | ||||
| distributionPath=wrapper/dists | ||||
| distributionSha256Sum=a4b4158601f8636cdeeab09bd76afb640030bb5b144aafe261a5e8af027dc612 | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip | ||||
| distributionSha256Sum=a17ddd85a26b6a7f5ddb71ff8b05fc5104c0202c6e64782429790c933686c806 | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip | ||||
| networkTimeout=10000 | ||||
| validateDistributionUrl=true | ||||
| zipStoreBase=GRADLE_USER_HOME | ||||
|   | ||||
							
								
								
									
										13
									
								
								gradlew
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								gradlew
									
									
									
									
										vendored
									
									
								
							| @@ -1,7 +1,7 @@ | ||||
| #!/bin/sh | ||||
|  | ||||
| # | ||||
| # Copyright © 2015-2021 the original authors. | ||||
| # Copyright © 2015 the original authors. | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| @@ -15,6 +15,8 @@ | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
| # SPDX-License-Identifier: Apache-2.0 | ||||
| # | ||||
|  | ||||
| ############################################################################## | ||||
| # | ||||
| @@ -84,7 +86,7 @@ done | ||||
| # shellcheck disable=SC2034 | ||||
| APP_BASE_NAME=${0##*/} | ||||
| # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) | ||||
| APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit | ||||
| APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit | ||||
|  | ||||
| # Use the maximum available, or set MAX_FD != -1 to use that value. | ||||
| MAX_FD=maximum | ||||
| @@ -112,7 +114,6 @@ case "$( uname )" in                #( | ||||
|   NONSTOP* )        nonstop=true ;; | ||||
| esac | ||||
|  | ||||
| CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar | ||||
|  | ||||
|  | ||||
| # Determine the Java command to use to start the JVM. | ||||
| @@ -170,7 +171,6 @@ fi | ||||
| # For Cygwin or MSYS, switch paths to Windows format before running java | ||||
| if "$cygwin" || "$msys" ; then | ||||
|     APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) | ||||
|     CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) | ||||
|  | ||||
|     JAVACMD=$( cygpath --unix "$JAVACMD" ) | ||||
|  | ||||
| @@ -203,15 +203,14 @@ fi | ||||
| DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' | ||||
|  | ||||
| # Collect all arguments for the java command: | ||||
| #   * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, | ||||
| #   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, | ||||
| #     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 | ||||
| #     treated as '${Hostname}' itself on the command line. | ||||
|  | ||||
| set -- \ | ||||
|         "-Dorg.gradle.appname=$APP_BASE_NAME" \ | ||||
|         -classpath "$CLASSPATH" \ | ||||
|         org.gradle.wrapper.GradleWrapperMain \ | ||||
|         -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ | ||||
|         "$@" | ||||
|  | ||||
| # Stop when "xargs" is not available. | ||||
|   | ||||
							
								
								
									
										5
									
								
								gradlew.bat
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								gradlew.bat
									
									
									
									
										vendored
									
									
								
							| @@ -13,6 +13,8 @@ | ||||
| @rem See the License for the specific language governing permissions and | ||||
| @rem limitations under the License. | ||||
| @rem | ||||
| @rem SPDX-License-Identifier: Apache-2.0 | ||||
| @rem | ||||
|  | ||||
| @if "%DEBUG%"=="" @echo off | ||||
| @rem ########################################################################## | ||||
| @@ -68,11 +70,10 @@ goto fail | ||||
| :execute | ||||
| @rem Setup the command line | ||||
|  | ||||
| set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar | ||||
|  | ||||
|  | ||||
| @rem Execute Gradle | ||||
| "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* | ||||
| "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* | ||||
|  | ||||
| :end | ||||
| @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. | ||||
|  | ||||
| • Color a map of places based on custom labels | ||||
| • Country/State based coloring | ||||
| • Single/Multi color modes | ||||
| • Different map projections available | ||||
| • Small & Fast | ||||
| • Statistics (WIP) | ||||
| • 100% Free and Open Source software, with no proprietary dependencies | ||||
| * Color a map of places based on custom labels | ||||
| * Country/State based coloring | ||||
| * Single/Multi color modes | ||||
| * Different map projections available | ||||
| * Small & Fast | ||||
| * Statistics (WIP) | ||||
| * 100% Free and Open Source software, with no proprietary dependencies | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|   "dependencies": { | ||||
|     "@turf/area": "^7.0.0", | ||||
|     "@turf/turf": "^7.0.0", | ||||
|     "jsdom": "^24.0.0", | ||||
|     "jsdom": "^27.0.0", | ||||
|     "mapshaper": "^0.6.79" | ||||
|   }, | ||||
|   "type": "module" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user