diff --git a/app/build.gradle b/app/build.gradle index e11c4ebc..9bd0e662 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,7 +5,6 @@ apply plugin: 'com.starter.easylauncher' apply plugin: "com.github.ben-manes.versions" android { - kotlinOptions.useIR = true compileSdkVersion(Dependencies.compileSdk) defaultConfig { applicationId "xyz.hisname.fireflyiii" @@ -75,7 +74,10 @@ android { } } - configurations {all*.exclude group: 'com.android.support'} + configurations { + all*.exclude group: 'com.android.support' + all*.exclude group: 'com.google.guava', module: 'listenablefuture' + } androidResources { ignoreAssetsPattern 'NOTICE.txt' } @@ -99,7 +101,7 @@ dependencies { implementation Dependencies.googleMaterialIcons implementation Dependencies.fontAwesome - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.0' implementation Dependencies.toasty implementation Dependencies.chart @@ -117,4 +119,6 @@ dependencies { // Project Lib implementation project(path: ":languagepack") -} \ No newline at end of file +} + +build.dependsOn(['checkClasspath']) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c73d8109..bcf18224 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -23,6 +23,7 @@ + @@ -49,10 +50,14 @@ android:label="@string/app_name" android:supportsRtl="true" android:networkSecurityConfig="@xml/network_security_config" - android:theme="@style/AppTheme.StartUpTheme"> + android:theme="@style/AppTheme.StartUpTheme" + android:localeConfig="@xml/locales_config" + tools:ignore="UnusedAttribute"> - + @@ -72,7 +77,9 @@ - + @@ -81,7 +88,9 @@ android:resource="@xml/balance_widget_info" /> - + @@ -91,7 +100,9 @@ - + @@ -102,7 +113,8 @@ + android:theme="@style/AppTheme" + android:exported="true"> @@ -149,13 +161,17 @@ - + - + @@ -194,7 +210,8 @@ android:name=".service.TransactionTilesService" android:icon="@drawable/ic_refresh" android:label="@string/transaction" - android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"> + android:permission="android.permission.BIND_QUICK_SETTINGS_TILE" + android:exported="true"> @@ -233,6 +250,16 @@ android:exported="true"> --> + + + + + diff --git a/app/src/main/java/xyz/hisname/fireflyiii/CustomApp.kt b/app/src/main/java/xyz/hisname/fireflyiii/CustomApp.kt index 15cf6cde..ff7a9627 100644 --- a/app/src/main/java/xyz/hisname/fireflyiii/CustomApp.kt +++ b/app/src/main/java/xyz/hisname/fireflyiii/CustomApp.kt @@ -40,7 +40,7 @@ class CustomApp: Application() { initAcra { reportFormat = StringFormat.KEY_VALUE_LIST buildConfigClass = BuildConfig::class.java - reportContent = arrayOf(ReportField.REPORT_ID, ReportField.APP_VERSION_NAME, + reportContent = listOf(ReportField.REPORT_ID, ReportField.APP_VERSION_NAME, ReportField.PHONE_MODEL, ReportField.BRAND, ReportField.PRODUCT, ReportField.ANDROID_VERSION, ReportField.BUILD_CONFIG, ReportField.STACK_TRACE, ReportField.LOGCAT) mailSender { diff --git a/app/src/main/java/xyz/hisname/fireflyiii/data/local/pref/AppPref.kt b/app/src/main/java/xyz/hisname/fireflyiii/data/local/pref/AppPref.kt index 068d5fb4..0970d685 100644 --- a/app/src/main/java/xyz/hisname/fireflyiii/data/local/pref/AppPref.kt +++ b/app/src/main/java/xyz/hisname/fireflyiii/data/local/pref/AppPref.kt @@ -52,10 +52,6 @@ class AppPref(private val sharedPref: SharedPreferences): PreferenceHelper { get() = sharedPref.getString("cert_value", "") ?: "" set(value) = sharedPref.edit { putString("cert_value", value) } - override var languagePref: String - get() = sharedPref.getString("language_pref", "") ?: "en" - set(value) = sharedPref.edit{ putString("language_pref", value)} - override var nightModeEnabled: Boolean get() = sharedPref.getBoolean("night_mode", false) set(value) = sharedPref.edit { putBoolean("night_mode", value) } diff --git a/app/src/main/java/xyz/hisname/fireflyiii/data/local/pref/PreferenceHelper.kt b/app/src/main/java/xyz/hisname/fireflyiii/data/local/pref/PreferenceHelper.kt index b9b5089f..cf5c621f 100644 --- a/app/src/main/java/xyz/hisname/fireflyiii/data/local/pref/PreferenceHelper.kt +++ b/app/src/main/java/xyz/hisname/fireflyiii/data/local/pref/PreferenceHelper.kt @@ -29,7 +29,6 @@ interface PreferenceHelper { var serverVersion: String var userOs: String var certValue: String - var languagePref: String var nightModeEnabled: Boolean var isKeyguardEnabled: Boolean var isCurrencyThumbnailEnabled: Boolean diff --git a/app/src/main/java/xyz/hisname/fireflyiii/ui/base/BaseActivity.kt b/app/src/main/java/xyz/hisname/fireflyiii/ui/base/BaseActivity.kt index 61b672c5..84bf0bc4 100644 --- a/app/src/main/java/xyz/hisname/fireflyiii/ui/base/BaseActivity.kt +++ b/app/src/main/java/xyz/hisname/fireflyiii/ui/base/BaseActivity.kt @@ -30,7 +30,6 @@ import xyz.hisname.fireflyiii.repository.GlobalViewModel import xyz.hisname.fireflyiii.util.extension.getCompatColor import xyz.hisname.fireflyiii.util.extension.getViewModel import xyz.hisname.fireflyiii.util.getUniqueHash -import xyz.hisname.languagepack.LanguageChanger @SuppressLint("Registered") open class BaseActivity: AppCompatActivity() { @@ -55,10 +54,6 @@ open class BaseActivity: AppCompatActivity() { } } - override fun attachBaseContext(newBase: Context) { - super.attachBaseContext(LanguageChanger.init(newBase, sharedPref(newBase).languagePref)) - } - override fun applyOverrideConfiguration(overrideConfiguration: Configuration) { val uiMode = overrideConfiguration.uiMode overrideConfiguration.setTo(baseContext.resources.configuration) diff --git a/app/src/main/java/xyz/hisname/fireflyiii/ui/base/BaseAddObjectFragment.kt b/app/src/main/java/xyz/hisname/fireflyiii/ui/base/BaseAddObjectFragment.kt index 4e4c36f2..18b351ff 100644 --- a/app/src/main/java/xyz/hisname/fireflyiii/ui/base/BaseAddObjectFragment.kt +++ b/app/src/main/java/xyz/hisname/fireflyiii/ui/base/BaseAddObjectFragment.kt @@ -27,7 +27,7 @@ import androidx.core.view.isVisible import xyz.hisname.fireflyiii.util.animation.BakedBezierInterpolator import kotlin.math.max -abstract class BaseAddObjectFragment: BaseFragment() { +abstract class BaseAddObjectFragment : BaseFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -35,21 +35,21 @@ abstract class BaseAddObjectFragment: BaseFragment() { setWidgets() } - protected fun unReveal(rootView: View){ + protected fun unReveal(rootView: View) { val x = rootView.width / 2 val y = rootView.height / 2 val finalRadius = (max(rootView.width, rootView.height) * 1.1).toFloat() - val circularReveal= ViewAnimationUtils.createCircularReveal( - rootView, x, y,finalRadius, 0f) + val circularReveal = ViewAnimationUtils.createCircularReveal( + rootView, x, y, finalRadius, 0f + ) circularReveal.duration = 400 circularReveal.interpolator = BakedBezierInterpolator.FADE_OUT_CURVE - circularReveal.addListener(object : AnimatorListenerAdapter(){ - override fun onAnimationEnd(animation: Animator?) { + circularReveal.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { super.onAnimationEnd(animation) try { parentFragmentManager.popBackStack() - } catch(illegal: IllegalStateException){ - + } catch (_: IllegalStateException) { } rootView.isVisible = false fragmentContainer.isVisible = true diff --git a/app/src/main/java/xyz/hisname/fireflyiii/ui/bills/details/BillDetailsFragment.kt b/app/src/main/java/xyz/hisname/fireflyiii/ui/bills/details/BillDetailsFragment.kt index a4fc33f6..58def8fa 100644 --- a/app/src/main/java/xyz/hisname/fireflyiii/ui/bills/details/BillDetailsFragment.kt +++ b/app/src/main/java/xyz/hisname/fireflyiii/ui/bills/details/BillDetailsFragment.kt @@ -34,11 +34,11 @@ import androidx.lifecycle.asLiveData import androidx.paging.LoadState import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager -import com.kizitonwose.calendarview.CalendarView -import com.kizitonwose.calendarview.model.CalendarDay -import com.kizitonwose.calendarview.model.DayOwner -import com.kizitonwose.calendarview.ui.DayBinder -import com.kizitonwose.calendarview.ui.ViewContainer +import com.kizitonwose.calendar.core.CalendarDay +import com.kizitonwose.calendar.core.DayPosition +import com.kizitonwose.calendar.view.CalendarView +import com.kizitonwose.calendar.view.MonthDayBinder +import com.kizitonwose.calendar.view.ViewContainer import xyz.hisname.fireflyiii.R import xyz.hisname.fireflyiii.databinding.CalendarDayBinding import xyz.hisname.fireflyiii.databinding.DetailsCardBinding @@ -149,15 +149,15 @@ class BillDetailsFragment: BaseDetailFragment() { val onDayText = CalendarDayBinding.bind(view).dayText } - binding.payDatesCalendarView.dayBinder = object: DayBinder{ - override fun bind(container: DayViewContainer, day: CalendarDay) { - container.day = day + binding.payDatesCalendarView.dayBinder = object: MonthDayBinder{ + override fun bind(container: DayViewContainer, data: CalendarDay) { + container.day = data val textView = container.onDayText - textView.text = day.date.dayOfMonth.toString() - if (day.owner == DayOwner.THIS_MONTH) { + textView.text = data.date.dayOfMonth.toString() + if (data.position == DayPosition.MonthDate) { if (selectedPayDays.isNotEmpty()){ - selectedPayDays.forEach { data -> - if(data == day.date){ + selectedPayDays.forEach { day -> + if(day == data.date){ textView.setBackgroundColor(getCompatColor(R.color.md_red_400)) } else { textView.setTextColor(setDayNightTheme()) @@ -208,21 +208,21 @@ class BillDetailsFragment: BaseDetailFragment() { } } - binding.paidDatesCalendarView.dayBinder = object: DayBinder{ - override fun bind(container: DayViewContainer, day: CalendarDay) { - container.day = day + binding.paidDatesCalendarView.dayBinder = object: MonthDayBinder{ + override fun bind(container: DayViewContainer, data: CalendarDay) { + container.day = data val textView = container.onDayText val divider = container.legendDivider - textView.text = day.date.dayOfMonth.toString() - if (day.owner == DayOwner.THIS_MONTH) { + textView.text = data.date.dayOfMonth.toString() + if (data.position == DayPosition.MonthDate) { if (selectedPaidDays.isNotEmpty()){ - selectedPaidDays.forEach { data -> - if(data == day.date){ + selectedPaidDays.forEach { day -> + if(day == data.date){ divider.setBackgroundColor(getCompatColor(R.color.md_green_500)) } } } - if(selectedDate == day.date){ + if(selectedDate == data.date){ textView.setBackgroundColor(getCompatColor(R.color.md_green_500)) } textView.setTextColor(setDayNightTheme()) @@ -251,7 +251,7 @@ class BillDetailsFragment: BaseDetailFragment() { calendarView.setup(startMonth, endMonth, WeekFields.of(Locale.getDefault()).firstDayOfWeek) calendarView.scrollToMonth(currentMonth) - calendarView.updateMonthConfiguration() + calendarView.updateMonthData() } private fun progressCircle(){ diff --git a/app/src/main/java/xyz/hisname/fireflyiii/ui/settings/SettingsFragment.kt b/app/src/main/java/xyz/hisname/fireflyiii/ui/settings/SettingsFragment.kt index bf6ef505..b0742896 100644 --- a/app/src/main/java/xyz/hisname/fireflyiii/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/xyz/hisname/fireflyiii/ui/settings/SettingsFragment.kt @@ -23,11 +23,13 @@ import android.os.Build import android.os.Bundle import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.widget.Toolbar import androidx.biometric.BiometricManager import androidx.biometric.BiometricManager.BIOMETRIC_SUCCESS import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.app.ActivityCompat +import androidx.core.os.LocaleListCompat import xyz.hisname.fireflyiii.R import androidx.fragment.app.commit import androidx.preference.CheckBoxPreference @@ -40,13 +42,14 @@ import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial import com.mikepenz.iconics.utils.* import xyz.hisname.fireflyiii.data.local.pref.AppPref import xyz.hisname.fireflyiii.util.biometric.KeyguardUtil -import xyz.hisname.languagepack.LanguageChanger +import xyz.hisname.languagepack.BuildConfig.TRANSLATION_ARRAY import java.io.File +import java.util.Locale -class SettingsFragment: BaseSettings() { +class SettingsFragment : BaseSettings() { - private lateinit var chooseFolder: ActivityResultLauncher + private lateinit var chooseFolder: ActivityResultLauncher private lateinit var userDownloadDirectoryPref: Preference override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { @@ -63,18 +66,30 @@ class SettingsFragment: BaseSettings() { userDefinedDirectory() } - private fun setLanguagePref(){ + private fun setLanguagePref() { val languagePref = findPreference("language_pref") as ListPreference - languagePref.value = AppPref(sharedPref).languagePref + val availableLocales = TRANSLATION_ARRAY.map { langCode -> + val hasCountry = langCode.contains("-") + if (hasCountry) + langCode.split("-").let { Locale(it[0], it[1]) } + else + Locale.forLanguageTag(langCode) + } + languagePref.entries = availableLocales.map { locale -> + locale.getDisplayName(locale) + .replaceFirstChar { if (it.isLowerCase()) it.titlecase(locale) else it.toString() } + }.toTypedArray() + languagePref.entryValues = TRANSLATION_ARRAY + + val localesList = AppCompatDelegate.getApplicationLocales() + val locales = (0 until localesList.size()).mapNotNull { localesList.get(it) } + val currentLanguage = locales.find { availableLocales.contains(it) } ?: Locale.ENGLISH + + languagePref.value = currentLanguage.language languagePref.setOnPreferenceChangeListener { _, newValue -> - AppPref(sharedPref).languagePref = newValue.toString() - LanguageChanger.init(requireContext(), AppPref(sharedPref).languagePref) - val coordinatorLayout = requireActivity().findViewById(R.id.coordinatorlayout) - Snackbar.make(coordinatorLayout, "Restart to apply changes", Snackbar.LENGTH_INDEFINITE) - .setAction(android.R.string.ok){ - ActivityCompat.recreate(requireActivity()) - } - .show() + val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags(newValue.toString()) + AppCompatDelegate.setApplicationLocales(appLocale) + true } languagePref.icon = IconicsDrawable(requireContext()).apply { @@ -84,7 +99,7 @@ class SettingsFragment: BaseSettings() { } } - private fun setAccountSection(){ + private fun setAccountSection() { val accountOptions = findPreference("account_options") as Preference accountOptions.setOnPreferenceClickListener { parentFragmentManager.commit { @@ -101,7 +116,7 @@ class SettingsFragment: BaseSettings() { } } - private fun setTransactionSection(){ + private fun setTransactionSection() { val transactionSettings = findPreference("transaction_settings") as Preference transactionSettings.setOnPreferenceClickListener { parentFragmentManager.commit { @@ -119,17 +134,18 @@ class SettingsFragment: BaseSettings() { } } - private fun setNightModeSection(){ + private fun setNightModeSection() { val nightModePref = findPreference("night_mode") as CheckBoxPreference nightModePref.setOnPreferenceChangeListener { preference, newValue -> val nightMode = newValue as Boolean AppPref(sharedPref).nightModeEnabled = nightMode - val coordinatorLayout = requireActivity().findViewById(R.id.coordinatorlayout) + val coordinatorLayout = + requireActivity().findViewById(R.id.coordinatorlayout) Snackbar.make(coordinatorLayout, "Restart to apply changes", Snackbar.LENGTH_INDEFINITE) - .setAction(android.R.string.ok){ - ActivityCompat.recreate(requireActivity()) - } - .show() + .setAction(android.R.string.ok) { + ActivityCompat.recreate(requireActivity()) + } + .show() true } nightModePref.icon = IconicsDrawable(requireContext()).apply { @@ -139,8 +155,9 @@ class SettingsFragment: BaseSettings() { } } - private fun setThumbnail(){ - val thumbnailPref = findPreference("currencyThumbnail") as CheckBoxPreference + private fun setThumbnail() { + val thumbnailPref = + findPreference("currencyThumbnail") as CheckBoxPreference thumbnailPref.icon = IconicsDrawable(requireContext()).apply { icon = GoogleMaterial.Icon.gmd_attach_money sizeDp = 24 @@ -153,7 +170,7 @@ class SettingsFragment: BaseSettings() { } } - private fun setTutorial(){ + private fun setTutorial() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { val tutorialSetting = findPreference("tutorial_setting") as Preference tutorialSetting.isVisible = true @@ -169,7 +186,7 @@ class SettingsFragment: BaseSettings() { } } - private fun setDeveloperOption(){ + private fun setDeveloperOption() { val developerSettings = findPreference("developer_options") as Preference developerSettings.setOnPreferenceClickListener { parentFragmentManager.commit { @@ -188,23 +205,29 @@ class SettingsFragment: BaseSettings() { override fun onResume() { super.onResume() - requireActivity().findViewById(R.id.activity_toolbar).title = resources.getString(R.string.settings) + requireActivity().findViewById(R.id.activity_toolbar).title = + resources.getString(R.string.settings) } - private fun setIconColor(): Int{ - return if(globalViewModel.isDark){ + private fun setIconColor(): Int { + return if (globalViewModel.isDark) { R.color.md_white_1000 } else { R.color.md_black_1000 } } - private fun setPinCode(){ + private fun setPinCode() { val keyguardPref = findPreference("keyguard") as Preference - if(!KeyguardUtil(requireActivity()).isDeviceKeyguardEnabled() || BiometricManager.from(requireContext()).canAuthenticate( - BiometricManager.Authenticators.BIOMETRIC_WEAK or BiometricManager.Authenticators.DEVICE_CREDENTIAL) != BIOMETRIC_SUCCESS){ + if (!KeyguardUtil(requireActivity()).isDeviceKeyguardEnabled() || BiometricManager.from( + requireContext() + ).canAuthenticate( + BiometricManager.Authenticators.BIOMETRIC_WEAK or BiometricManager.Authenticators.DEVICE_CREDENTIAL + ) != BIOMETRIC_SUCCESS + ) { keyguardPref.isSelectable = false - keyguardPref.summary = "Please enable pin / password / biometrics in your device settings" + keyguardPref.summary = + "Please enable pin / password / biometrics in your device settings" } keyguardPref.icon = IconicsDrawable(requireContext()).apply { icon = GoogleMaterial.Icon.gmd_lock @@ -218,7 +241,7 @@ class SettingsFragment: BaseSettings() { } } - private fun deleteItems(){ + private fun deleteItems() { val deleteData = findPreference("delete_data") as Preference deleteData.icon = IconicsDrawable(requireContext()).apply { icon = GoogleMaterial.Icon.gmd_delete_forever @@ -235,15 +258,16 @@ class SettingsFragment: BaseSettings() { } } - private fun userDefinedDirectory(){ - userDownloadDirectoryPref = findPreference("userDefinedDownloadDirectory") as Preference + private fun userDefinedDirectory() { + userDownloadDirectoryPref = + findPreference("userDefinedDownloadDirectory") as Preference val userPref = AppPref(sharedPref).userDefinedDownloadDirectory userDownloadDirectoryPref.icon = IconicsDrawable(requireContext()).apply { icon = GoogleMaterial.Icon.gmd_file_download sizeDp = 24 colorRes = setIconColor() } - val userDirectory = if(userPref.isEmpty()){ + val userDirectory = if (userPref.isEmpty()) { File(requireContext().getExternalFilesDir(null).toString()).toString() } else { userPref @@ -257,11 +281,12 @@ class SettingsFragment: BaseSettings() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - chooseFolder = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { folderChoosen -> - if(folderChoosen != null){ - AppPref(sharedPref).userDefinedDownloadDirectory = folderChoosen.toString() - userDownloadDirectoryPref.summary = folderChoosen.toString() + chooseFolder = + registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { folderChoosen -> + if (folderChoosen != null) { + AppPref(sharedPref).userDefinedDownloadDirectory = folderChoosen.toString() + userDownloadDirectoryPref.summary = folderChoosen.toString() + } } - } } } \ No newline at end of file diff --git a/app/src/main/java/xyz/hisname/fireflyiii/ui/tags/MapsFragment.kt b/app/src/main/java/xyz/hisname/fireflyiii/ui/tags/MapsFragment.kt index 31346ca6..e5764b90 100644 --- a/app/src/main/java/xyz/hisname/fireflyiii/ui/tags/MapsFragment.kt +++ b/app/src/main/java/xyz/hisname/fireflyiii/ui/tags/MapsFragment.kt @@ -22,7 +22,6 @@ import android.Manifest import android.content.Context import android.content.Intent import android.content.pm.PackageManager -import android.location.Location import android.location.LocationListener import android.location.LocationManager import android.os.Bundle @@ -50,18 +49,22 @@ import org.osmdroid.events.MapEventsReceiver import org.osmdroid.tileprovider.tilesource.TileSourceFactory import org.osmdroid.util.GeoPoint import org.osmdroid.views.CustomZoomButtonsController -import org.osmdroid.views.overlay.* +import org.osmdroid.views.overlay.MapEventsOverlay +import org.osmdroid.views.overlay.Marker import xyz.hisname.fireflyiii.BuildConfig import xyz.hisname.fireflyiii.R import xyz.hisname.fireflyiii.databinding.FragmentMapBinding import xyz.hisname.fireflyiii.repository.models.nominatim.LocationSearchModel import xyz.hisname.fireflyiii.ui.base.BaseFragment -import xyz.hisname.fireflyiii.util.extension.* import xyz.hisname.fireflyiii.util.extension.getViewModel +import xyz.hisname.fireflyiii.util.extension.hideKeyboard +import xyz.hisname.fireflyiii.util.extension.toastInfo import xyz.hisname.fireflyiii.util.getUniqueHash +import xyz.hisname.fireflyiii.util.isPermissionGranted +import xyz.hisname.fireflyiii.util.launchLocationPermissionsRequest import java.io.File -class MapsFragment: BaseFragment() { +class MapsFragment : BaseFragment() { private val locationService by lazy { requireContext().getSystemService(Context.LOCATION_SERVICE) as LocationManager } private val mapsViewModel by lazy { getViewModel(MapsViewModel::class.java) } @@ -69,26 +72,40 @@ class MapsFragment: BaseFragment() { private val latitudeBundle by lazy { arguments?.getString("latitude") } private lateinit var startMarker: Marker private lateinit var cloneLocationList: List - private lateinit var gpsPermission: ActivityResultLauncher + private lateinit var gpsPermission: ActivityResultLauncher> private var fragmentMapBinding: FragmentMapBinding? = null private val binding get() = fragmentMapBinding!! private val mapController by lazy { binding.maps.controller } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { fragmentMapBinding = FragmentMapBinding.inflate(inflater, container, false) - val view = binding.root - return view + return binding.root } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - gpsPermission = registerForActivityResult(ActivityResultContracts.RequestPermission()) { success -> - if(success) { - if (ContextCompat.checkSelfPermission(requireContext(), - Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { + gpsPermission = registerForActivityResult( + ActivityResultContracts.RequestMultiplePermissions() + ) { success -> + if (success.any()) { + if (isPermissionGranted(Manifest.permission.ACCESS_COARSE_LOCATION)) { toastInfo("Waiting for location...") - locationService.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0L, 0f, locationListener) - locationService.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0L, 0f, locationListener) + locationService.requestLocationUpdates( + LocationManager.GPS_PROVIDER, + 0L, + 0f, + locationListener, + ) + locationService.requestLocationUpdates( + LocationManager.NETWORK_PROVIDER, + 0L, + 0f, + locationListener, + ) } } } @@ -96,15 +113,26 @@ class MapsFragment: BaseFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - Configuration.getInstance().load(requireContext(), requireContext().getSharedPreferences( - requireContext().getUniqueHash().toString() + "-user-preferences", Context.MODE_PRIVATE)) + Configuration.getInstance().load( + requireContext(), + requireContext() + .getSharedPreferences( + requireContext().getUniqueHash() + "-user-preferences", + Context.MODE_PRIVATE + ) + ) Configuration.getInstance().userAgentValue = BuildConfig.APPLICATION_ID Configuration.getInstance().osmdroidBasePath = requireContext().filesDir - Configuration.getInstance().osmdroidTileCache = File(requireContext().filesDir.toString() + "/tiles") + Configuration.getInstance().osmdroidTileCache = + File(requireContext().filesDir.toString() + "/tiles") startMarker = Marker(binding.maps) - if(!latitudeBundle.isNullOrEmpty() && !longitudeBundle.isNullOrEmpty()){ - setMap(GeoPoint(latitudeBundle?.toDouble() ?: 37.276675, - longitudeBundle?.toDouble() ?: -115.798936)) + if (!latitudeBundle.isNullOrEmpty() && !longitudeBundle.isNullOrEmpty()) { + setMap( + GeoPoint( + latitudeBundle?.toDouble() ?: 37.276675, + longitudeBundle?.toDouble() ?: -115.798936 + ) + ) } else { isGpsEnabled() } @@ -125,7 +153,7 @@ class MapsFragment: BaseFragment() { } } - private fun setMap(location: GeoPoint){ + private fun setMap(location: GeoPoint) { startMarker.position = location startMarker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM) binding.maps.setMultiTouchControls(true) @@ -141,17 +169,17 @@ class MapsFragment: BaseFragment() { mapController.setZoom(18.0) } - private fun searchLocation(){ - binding.mapSearch.setOnKeyListener { v, keyCode, event -> - if(event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER){ + private fun searchLocation() { + binding.mapSearch.setOnKeyListener { _, keyCode, event -> + if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) { location(binding.mapSearch.text.toString()) hideKeyboard() } false } - binding.mapSearch.addTextChangedListener(object : TextWatcher{ + binding.mapSearch.addTextChangedListener(object : TextWatcher { override fun afterTextChanged(editable: Editable) { - if(editable.isNotBlank()) { + if (editable.isNotBlank()) { location(editable.toString()) } } @@ -162,22 +190,23 @@ class MapsFragment: BaseFragment() { override fun onTextChanged(charSequence: CharSequence, p1: Int, p2: Int, p3: Int) {} }) - binding.mapSearch.onItemClickListener = AdapterView.OnItemClickListener { adapterView, view, i, l -> + binding.mapSearch.onItemClickListener = AdapterView.OnItemClickListener { _, _, i, _ -> setMap(GeoPoint(cloneLocationList[i].lat, cloneLocationList[i].lon)) } } - private fun location(query: String){ - mapsViewModel.getLocationFromQuery(query).observe(viewLifecycleOwner){ data -> - if(data.isNotEmpty()){ - val adapter = ArrayAdapter(requireContext(), android.R.layout.select_dialog_item, data) + private fun location(query: String) { + mapsViewModel.getLocationFromQuery(query).observe(viewLifecycleOwner) { data -> + if (data.isNotEmpty()) { + val adapter = + ArrayAdapter(requireContext(), android.R.layout.select_dialog_item, data) binding.mapSearch.setAdapter(adapter) } } } - private fun setMapClick(){ - val mapReceiver = object : MapEventsReceiver{ + private fun setMapClick() { + val mapReceiver = object : MapEventsReceiver { override fun longPressHelper(geoPoint: GeoPoint): Boolean { setMap(geoPoint) return true @@ -191,65 +220,81 @@ class MapsFragment: BaseFragment() { binding.maps.overlays.add(MapEventsOverlay(mapReceiver)) } - private fun setFab(){ + private fun setFab() { binding.fabMap.setImageDrawable(IconicsDrawable(requireContext()).apply { icon = GoogleMaterial.Icon.gmd_my_location colorRes = R.color.md_black_1000 sizeDp = 16 }) binding.fabMap.setOnClickListener { - if(ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED){ - if (ActivityCompat.shouldShowRequestPermissionRationale(requireActivity(), - Manifest.permission.ACCESS_FINE_LOCATION)){ + if (!isPermissionGranted(Manifest.permission.ACCESS_COARSE_LOCATION)) { + if (ActivityCompat.shouldShowRequestPermissionRationale( + requireActivity(), + Manifest.permission.ACCESS_COARSE_LOCATION + ) + ) { AlertDialog.Builder(requireActivity()) - .setTitle("Grant access to location data?") - .setMessage("Choosing coordinates data is simple when location data permission is granted. " + - "Otherwise you may have to manually search for your location") - .setPositiveButton("OK"){_,_ -> - gpsPermission.launch(Manifest.permission.ACCESS_FINE_LOCATION) - } - .setNegativeButton("No"){ _,_ -> - toastInfo("Alright...") - } - .show() - } else { - gpsPermission.launch(Manifest.permission.ACCESS_FINE_LOCATION) - } + .setTitle("Grant access to location data?") + .setMessage( + "Choosing coordinates data is simple when location data permission is granted. " + + "Otherwise you may have to manually search for your location" + ) + .setPositiveButton("OK") { _, _ -> + gpsPermission.launchLocationPermissionsRequest() + } + .setNegativeButton("No") { _, _ -> + toastInfo("Alright...") + } + .show() + } else + gpsPermission.launchLocationPermissionsRequest() } else { - locationService.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0L, 0f, locationListener) - locationService.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0L, 0f, locationListener) + locationService.requestLocationUpdates( + LocationManager.GPS_PROVIDER, + 0L, + 0f, + locationListener + ) + locationService.requestLocationUpdates( + LocationManager.NETWORK_PROVIDER, + 0L, + 0f, + locationListener + ) } } } - private val locationListener: LocationListener = object : LocationListener { - override fun onLocationChanged(location: Location) { - setMap(GeoPoint(location.latitude, location.longitude)) - } - override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {} - override fun onProviderEnabled(provider: String) {} - override fun onProviderDisabled(provider: String) {} + private val locationListener: LocationListener = LocationListener { location -> + setMap(GeoPoint(location.latitude, location.longitude)) } - private fun isGpsEnabled(){ - if(!locationService.isProviderEnabled(LocationManager.GPS_PROVIDER)){ + private fun isGpsEnabled() { + if (!locationService.isProviderEnabled(LocationManager.GPS_PROVIDER)) { AlertDialog.Builder(requireActivity()) - .setMessage("For a better experience turn on device's location") - .setPositiveButton("Sure"){_, _ -> - requireActivity().startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)) - } - .setNegativeButton("No"){ _, _ -> - toastInfo("Alright...Using Network data instead.") - } - .show() + .setMessage("For a better experience turn on device's location") + .setPositiveButton("Sure") { _, _ -> + requireActivity().startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)) + } + .setNegativeButton("No") { _, _ -> + toastInfo("Alright...Using Network data instead.") + } + .show() } else { - if (ContextCompat.checkSelfPermission(requireContext(), - Manifest.permission.ACCESS_FINE_LOCATION) - == PackageManager.PERMISSION_GRANTED) { - toastInfo("Acquiring current location...") - locationService.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0L, 0f, locationListener) - locationService.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0L, 0f, locationListener) + if (isPermissionGranted(Manifest.permission.ACCESS_COARSE_LOCATION)) { + toastInfo("Acquiring current location...") + locationService.requestLocationUpdates( + LocationManager.GPS_PROVIDER, + 0L, + 0f, + locationListener + ) + locationService.requestLocationUpdates( + LocationManager.NETWORK_PROVIDER, + 0L, + 0f, + locationListener + ) } else { setMap(GeoPoint(37.276675, -115.798936)) } @@ -264,9 +309,7 @@ class MapsFragment: BaseFragment() { override fun onPause() { super.onPause() binding.maps.onPause() - if (ContextCompat.checkSelfPermission(requireContext(), - Manifest.permission.ACCESS_FINE_LOCATION) - == PackageManager.PERMISSION_GRANTED) { + if (isPermissionGranted(Manifest.permission.ACCESS_COARSE_LOCATION)) { locationService.removeUpdates(locationListener) } } diff --git a/app/src/main/java/xyz/hisname/fireflyiii/ui/transaction/TransactionSeparatorAdapter.kt b/app/src/main/java/xyz/hisname/fireflyiii/ui/transaction/TransactionSeparatorAdapter.kt index b3246f97..bad96761 100644 --- a/app/src/main/java/xyz/hisname/fireflyiii/ui/transaction/TransactionSeparatorAdapter.kt +++ b/app/src/main/java/xyz/hisname/fireflyiii/ui/transaction/TransactionSeparatorAdapter.kt @@ -102,6 +102,7 @@ class TransactionSeparatorAdapter(private val clickListener:(Transactions) -> Un } binding.listItem.setOnClickListener {clickListener(separator.transaction)} } + else -> null } } } diff --git a/app/src/main/java/xyz/hisname/fireflyiii/ui/transaction/list/TransactionFragment.kt b/app/src/main/java/xyz/hisname/fireflyiii/ui/transaction/list/TransactionFragment.kt index 5992b1c9..c3789377 100644 --- a/app/src/main/java/xyz/hisname/fireflyiii/ui/transaction/list/TransactionFragment.kt +++ b/app/src/main/java/xyz/hisname/fireflyiii/ui/transaction/list/TransactionFragment.kt @@ -44,10 +44,10 @@ import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.datepicker.MaterialDatePicker -import com.kizitonwose.calendarview.model.CalendarDay -import com.kizitonwose.calendarview.model.DayOwner -import com.kizitonwose.calendarview.ui.DayBinder -import com.kizitonwose.calendarview.ui.ViewContainer +import com.kizitonwose.calendar.core.CalendarDay +import com.kizitonwose.calendar.core.DayPosition +import com.kizitonwose.calendar.view.MonthDayBinder +import com.kizitonwose.calendar.view.ViewContainer import com.mikepenz.iconics.IconicsDrawable import com.mikepenz.iconics.typeface.library.fontawesome.FontAwesome import com.mikepenz.iconics.utils.sizeDp @@ -168,7 +168,7 @@ class TransactionFragment: BaseFragment(){ view.setOnClickListener { selectedDates.clear() dateRange.clear() - if (day.owner == DayOwner.THIS_MONTH) { + if (day.position == DayPosition.MonthDate) { selectedDate = day.date selectedDates.clear() binding.transactionCalendar.notifyCalendarChanged() @@ -221,25 +221,25 @@ class TransactionFragment: BaseFragment(){ } } } - binding.transactionCalendar.dayBinder = object: DayBinder{ - override fun bind(container: DayViewContainer, day: CalendarDay) { - container.day = day + binding.transactionCalendar.dayBinder = object: MonthDayBinder{ + override fun bind(container: DayViewContainer, data: CalendarDay) { + container.day = data val textView = container.onDayText - textView.text = day.date.dayOfMonth.toString() + textView.text = data.date.dayOfMonth.toString() setShowCase(textView) - if (day.owner == DayOwner.THIS_MONTH) { + if (data.position == DayPosition.MonthDate) { when { - selectedDates.contains(day.date) -> { + selectedDates.contains(data.date) -> { if (selectedDates.size != 2){ dateRange.clear() } textView.setTextColor(setDayNightTheme()) textView.setBackgroundResource(R.drawable.today_bg) } - dateRange.contains(day.date) -> { + dateRange.contains(data.date) -> { textView.setBackgroundResource(R.drawable.today_bg) } - selectedDate == day.date -> { + selectedDate == data.date -> { if(selectedDate == today){ textView.setTextColor(getCompatColor(R.color.md_red_800)) } else { @@ -269,10 +269,9 @@ class TransactionFragment: BaseFragment(){ val currentMonth = YearMonth.now() val startMonth = currentMonth.minusYears(80) val endMonth = currentMonth.plusYears(20) - binding.transactionCalendar.setupAsync(startMonth, endMonth, DayOfWeek.SUNDAY){ - binding.transactionCalendar.scrollToMonth(currentMonth) - binding.transactionCalendar.updateMonthConfiguration() - } + binding.transactionCalendar.setup(startMonth, endMonth, DayOfWeek.SUNDAY) + binding.transactionCalendar.scrollToMonth(currentMonth) + binding.transactionCalendar.updateMonthData() } private fun dragAndDrop(){ diff --git a/app/src/main/java/xyz/hisname/fireflyiii/util/Permissions.kt b/app/src/main/java/xyz/hisname/fireflyiii/util/Permissions.kt new file mode 100644 index 00000000..97853254 --- /dev/null +++ b/app/src/main/java/xyz/hisname/fireflyiii/util/Permissions.kt @@ -0,0 +1,40 @@ +package xyz.hisname.fireflyiii.util + +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import androidx.activity.result.ActivityResultLauncher +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment + +/** + * Checks if the given permission has been granted. + * @author Arnau Mora + * @since 20221124 + */ +fun Context.isPermissionGranted(permission: String) = + ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED + +/** + * Alias for [Fragment.requireContext].[Context.isPermissionGranted]. + * Checks if the given permission has been granted. + * @author Arnau Mora + * @since 20221124 + * @throws IllegalStateException If not currently associated with a context. + */ +@Throws(IllegalStateException::class) +fun Fragment.isPermissionGranted(permission: String) = + requireContext().isPermissionGranted(permission) + +/** + * Runs [ActivityResultLauncher.launch] with the [Manifest.permission.ACCESS_COARSE_LOCATION] and + * [Manifest.permission.ACCESS_FINE_LOCATION] permissions. + * @author Arnau Mora + * @since 20221124 + */ +fun ActivityResultLauncher>.launchLocationPermissionsRequest() = launch( + arrayOf( + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION, + ) +) diff --git a/app/src/main/res/layout/fragment_bill_details.xml b/app/src/main/res/layout/fragment_bill_details.xml index b40e9866..29a3a8fe 100644 --- a/app/src/main/res/layout/fragment_bill_details.xml +++ b/app/src/main/res/layout/fragment_bill_details.xml @@ -134,14 +134,14 @@ android:layout_marginTop="10dp" android:layout_marginBottom="10dp" /> - + app:cv_scrollPaged="true" /> @@ -208,14 +208,14 @@ android:layout_marginTop="10dp" android:layout_marginBottom="10dp" /> - + app:cv_scrollPaged="true" /> - diff --git a/app/src/main/res/values/array.xml b/app/src/main/res/values/array.xml index c8f63f55..2018fcfc 100644 --- a/app/src/main/res/values/array.xml +++ b/app/src/main/res/values/array.xml @@ -40,32 +40,6 @@ 60 - - English - Deutsch - 正體中文 - 简体中文 - Français - Italiano - Nederlands - Español - Русский - Português do Brasil - - - - en - de - zh-rTW - zh-rCN - fr - it - nl - es - ru - pt-rBR - - Connected Un-Metered diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index b8dc1f12..156c6107 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -57,7 +57,7 @@ false true - @style/MaterialDrawer.DrawerArrowStyle +