summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--.gitmodules3
-rw-r--r--.idea/gradle.xml2
-rw-r--r--.idea/inspectionProfiles/Project_Default.xml6
-rw-r--r--anastasis-ui/build.gradle17
-rw-r--r--anastasis-ui/src/main/AndroidManifest.xml3
-rw-r--r--anastasis-ui/src/main/java/org/gnu/anastasis/ui/IntroFragment.kt19
-rw-r--r--anastasis-ui/src/main/java/org/gnu/anastasis/ui/MainActivity.kt8
-rw-r--r--anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/AuthenticationFragment.kt44
-rw-r--r--anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/SecurityQuestionFragment.kt27
-rw-r--r--anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/SmsFragment.kt31
-rw-r--r--anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/VideoFragment.kt34
-rw-r--r--anastasis-ui/src/main/java/org/gnu/anastasis/ui/identity/ChangeLocationFragment.kt16
-rw-r--r--anastasis-ui/src/main/java/org/gnu/anastasis/ui/identity/IdentityFragment.kt35
-rw-r--r--build.gradle15
-rw-r--r--cashier/build.gradle20
-rw-r--r--cashier/src/main/AndroidManifest.xml9
-rw-r--r--cashier/src/main/java/net/taler/cashier/AboutDialogFragment.kt18
-rw-r--r--cashier/src/main/java/net/taler/cashier/BalanceFragment.kt2
-rw-r--r--cashier/src/main/java/net/taler/cashier/MainViewModel.kt25
-rw-r--r--cashier/src/main/java/net/taler/cashier/Response.kt5
-rw-r--r--cashier/src/main/java/net/taler/cashier/SignedAmount.kt2
-rw-r--r--cashier/src/main/java/net/taler/cashier/config/ConfigFragment.kt6
-rw-r--r--cashier/src/main/java/net/taler/cashier/config/ConfigManager.kt12
-rw-r--r--cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt10
-rw-r--r--cashier/src/main/res/values-de/strings.xml18
-rw-r--r--cashier/src/main/res/values-es/strings.xml2
-rw-r--r--cashier/src/main/res/values-fr/strings.xml7
-rw-r--r--cashier/src/main/res/values-tr/strings.xml42
-rw-r--r--gradle/wrapper/gradle-wrapper.properties2
-rw-r--r--merchant-lib/build.gradle19
-rw-r--r--merchant-lib/src/main/AndroidManifest.xml3
-rw-r--r--merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt41
-rw-r--r--merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt4
-rw-r--r--merchant-lib/src/main/java/net/taler/merchantlib/Orders.kt10
-rw-r--r--merchant-lib/src/main/java/net/taler/merchantlib/Refunds.kt3
-rw-r--r--merchant-lib/src/main/java/net/taler/merchantlib/Response.kt10
-rw-r--r--merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt7
-rw-r--r--merchant-lib/src/test/java/net/taler/merchantlib/MockHttpClient.kt24
-rw-r--r--merchant-terminal/build.gradle24
-rw-r--r--merchant-terminal/src/main/AndroidManifest.xml4
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt4
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt4
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt7
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/config/PosConfig.kt2
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/order/LiveOrder.kt2
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt4
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt9
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt2
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundFragment.kt4
-rw-r--r--merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundManager.kt2
-rw-r--r--merchant-terminal/src/main/res/values-de/strings.xml29
-rw-r--r--merchant-terminal/src/main/res/values-es/strings.xml1
-rw-r--r--merchant-terminal/src/main/res/values-fr/strings.xml6
-rw-r--r--merchant-terminal/src/main/res/values-sv/strings.xml1
-rw-r--r--merchant-terminal/src/main/res/values-tr/strings.xml68
-rw-r--r--merchant-terminal/src/test/java/net/taler/merchantpos/order/OrderManagerTest.kt2
m---------multiplatform0
-rw-r--r--taler-kotlin-android/build.gradle35
-rw-r--r--taler-kotlin-android/src/main/AndroidManifest.xml6
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/common/Amount.kt210
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/common/AndroidUtils.kt15
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/common/Bech32.kt257
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt2
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/common/CyptoUtils.kt71
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/common/TalerUri.kt62
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/common/Time.kt129
-rw-r--r--taler-kotlin-android/src/main/java/net/taler/common/Version.kt70
-rw-r--r--taler-kotlin-android/src/test/java/net/taler/common/AmountTest.kt221
-rw-r--r--taler-kotlin-android/src/test/java/net/taler/common/ContractTermsTest.kt1
-rw-r--r--taler-kotlin-android/src/test/java/net/taler/common/TalerUriTest.kt65
-rw-r--r--taler-kotlin-android/src/test/java/net/taler/common/TestUtils.kt (renamed from taler-kotlin-android/src/main/java/net/taler/lib/android/Serialization.kt)25
-rw-r--r--taler-kotlin-android/src/test/java/net/taler/common/TimeTest.kt46
-rw-r--r--taler-kotlin-android/src/test/java/net/taler/common/VersionTest.kt65
-rw-r--r--wallet/build.gradle54
-rw-r--r--wallet/proguard-rules.pro2
-rw-r--r--wallet/src/main/AndroidManifest.xml6
-rw-r--r--wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt5
-rw-r--r--wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt10
-rw-r--r--wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt5
-rw-r--r--wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFeesFragment.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt10
-rw-r--r--wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt6
-rw-r--r--wallet/src/main/java/net/taler/wallet/refund/RefundManager.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/settings/SettingsFragment.kt46
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt4
-rw-r--r--wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt7
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt12
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawSuccessFragment.kt199
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt2
-rw-r--r--wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt53
-rw-r--r--wallet/src/main/res/layout/fragment_manual_withdraw.xml2
-rw-r--r--wallet/src/main/res/values-ar/strings.xml147
-rw-r--r--wallet/src/main/res/values-de/strings.xml151
-rw-r--r--wallet/src/main/res/values-es/strings.xml17
-rw-r--r--wallet/src/main/res/values-fr/strings.xml79
-rw-r--r--wallet/src/main/res/values-it/strings.xml143
-rw-r--r--wallet/src/main/res/values-ko/strings.xml142
-rw-r--r--wallet/src/main/res/values-pt/strings.xml142
-rw-r--r--wallet/src/main/res/values-ru/strings.xml145
-rw-r--r--wallet/src/main/res/values-tr/strings.xml151
-rw-r--r--wallet/src/main/res/values/strings.xml4
-rw-r--r--wallet/src/test/java/net/taler/wallet/payment/PaymentResponsesTest.kt1
-rw-r--r--wallet/src/test/java/net/taler/wallet/withdraw/WithdrawManagerKtTest.kt51
107 files changed, 3214 insertions, 411 deletions
diff --git a/.gitignore b/.gitignore
index caf9ce6..d66e8df 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,3 +24,6 @@
*/fastlane/report.xml
*/fastlane/README.md
*/fastlane/metadata/android/screenshots.html
+.classpath
+.project
+.settings
diff --git a/.gitmodules b/.gitmodules
index 29c8ad2..e69de29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +0,0 @@
-[submodule "wallet-kotlin"]
- path = multiplatform
- url = git://git.taler.net/wallet-kotlin.git
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index f7de5ff..caf1ba4 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -7,6 +7,7 @@
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
+ <option name="gradleJvm" value="Embedded JDK" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
@@ -20,7 +21,6 @@
<option value="$PROJECT_DIR$/wallet" />
</set>
</option>
- <option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..bf1d698
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+ <profile version="1.0">
+ <option name="myName" value="Project Default" />
+ <inspection_tool class="UnnecessaryVariable" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
+ </profile>
+</component> \ No newline at end of file
diff --git a/anastasis-ui/build.gradle b/anastasis-ui/build.gradle
index 4a66366..4d30ee5 100644
--- a/anastasis-ui/build.gradle
+++ b/anastasis-ui/build.gradle
@@ -16,18 +16,15 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
-apply plugin: 'kotlin-android-extensions'
android {
- compileSdkVersion 30
+ compileSdkVersion 32
//noinspection GradleDependency
buildToolsVersion "$build_tools_version"
defaultConfig {
minSdkVersion 21
- targetSdkVersion 30
- versionCode 1
- versionName "0.1"
+ targetSdkVersion 32
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
@@ -52,6 +49,12 @@ android {
kotlinOptions {
jvmTarget = "1.8"
}
+
+ buildFeatures {
+ viewBinding true
+ }
+
+ namespace 'org.gnu.anastasis.ui'
}
dependencies {
@@ -69,6 +72,6 @@ dependencies {
testImplementation "junit:junit:$junit_version"
- androidTestImplementation 'androidx.test.ext:junit:1.1.2'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
diff --git a/anastasis-ui/src/main/AndroidManifest.xml b/anastasis-ui/src/main/AndroidManifest.xml
index 9340508..2827271 100644
--- a/anastasis-ui/src/main/AndroidManifest.xml
+++ b/anastasis-ui/src/main/AndroidManifest.xml
@@ -14,8 +14,7 @@
~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="org.gnu.anastasis.ui">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
diff --git a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/IntroFragment.kt b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/IntroFragment.kt
index d8f0185..ef2e190 100644
--- a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/IntroFragment.kt
+++ b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/IntroFragment.kt
@@ -22,21 +22,34 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
-import kotlinx.android.synthetic.main.fragment_intro.*
+import org.gnu.anastasis.ui.databinding.FragmentIntroBinding
class IntroFragment : Fragment() {
+ private var _binding: FragmentIntroBinding? = null
+
+ // This property is only valid between onCreateView and
+ // onDestroyView.
+ private val binding get() = _binding!!
+
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
- return inflater.inflate(R.layout.fragment_intro, container, false)
+ _binding = FragmentIntroBinding.inflate(inflater, container, false)
+ val view = binding.root
+ return view
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- button.setOnClickListener {
+ binding.button.setOnClickListener {
findNavController().navigate(R.id.action_nav_anastasis_intro_to_nav_anastasis_identity)
}
}
diff --git a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/MainActivity.kt b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/MainActivity.kt
index ac355c1..d7870cb 100644
--- a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/MainActivity.kt
+++ b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/MainActivity.kt
@@ -22,7 +22,7 @@ import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupWithNavController
-import kotlinx.android.synthetic.main.activity.*
+import org.gnu.anastasis.ui.databinding.ActivityBinding
internal const val PERMISSION_REQUEST_CODE = 1
@@ -30,6 +30,8 @@ class MainActivity : AppCompatActivity() {
private lateinit var nav: NavController
+ private lateinit var binding: ActivityBinding
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity)
@@ -38,11 +40,11 @@ class MainActivity : AppCompatActivity() {
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
nav = navHostFragment.navController
- setSupportActionBar(toolbar)
+ setSupportActionBar(binding.toolbar)
val appBarConfiguration = AppBarConfiguration(
emptySet()
)
- toolbar.setupWithNavController(nav, appBarConfiguration)
+ binding.toolbar.setupWithNavController(nav, appBarConfiguration)
}
}
diff --git a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/AuthenticationFragment.kt b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/AuthenticationFragment.kt
index 59d0410..da947b0 100644
--- a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/AuthenticationFragment.kt
+++ b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/AuthenticationFragment.kt
@@ -29,10 +29,10 @@ import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController
import com.google.android.material.card.MaterialCardView
-import kotlinx.android.synthetic.main.fragment_authentication.*
-import net.taler.lib.common.Amount
+import net.taler.common.Amount
import org.gnu.anastasis.ui.MainViewModel
import org.gnu.anastasis.ui.R
+import org.gnu.anastasis.ui.databinding.FragmentAuthenticationBinding
class AuthenticationFragment : Fragment() {
@@ -40,6 +40,12 @@ class AuthenticationFragment : Fragment() {
private var price: Amount = Amount.zero("KUDOS")
+ private var _binding: FragmentAuthenticationBinding? = null
+
+ // This property is only valid between onCreateView and
+ // onDestroyView.
+ private val binding get() = _binding!!
+
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -50,46 +56,46 @@ class AuthenticationFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- passwordCard.setOnClickListener {
+ binding.passwordCard.setOnClickListener {
showDialog(
R.id.action_nav_anastasis_authentication_to_securityQuestionFragment,
- passwordCard,
+ binding.passwordCard,
"question_card"
)
}
- postidentCard.setOnClickListener {
+ binding.postidentCard.setOnClickListener {
toggleCard(
- postidentCard,
+ binding.postidentCard,
Amount.fromJSONString("KUDOS:3.5")
)
}
- smsCard.setOnClickListener {
+ binding.smsCard.setOnClickListener {
showDialog(
R.id.action_nav_anastasis_authentication_to_smsFragment,
- smsCard,
+ binding.smsCard,
"sms_card"
)
}
- videoCard.setOnClickListener {
+ binding.videoCard.setOnClickListener {
showDialog(
R.id.action_nav_anastasis_authentication_to_videoFragment,
- videoCard,
+ binding.videoCard,
"video_card"
)
}
viewModel.securityQuestionChecked.observe(viewLifecycleOwner, { checked ->
- passwordCard.isChecked = checked
+ binding.passwordCard.isChecked = checked
updatePrice(checked, Amount.fromJSONString("KUDOS:0.5"))
updateNextButtonState()
})
viewModel.smsChecked.observe(viewLifecycleOwner, { checked ->
- smsCard.isChecked = checked
+ binding.smsCard.isChecked = checked
updatePrice(checked, Amount.fromJSONString("KUDOS:1.0"))
updateNextButtonState()
})
viewModel.videoChecked.observe(viewLifecycleOwner, { checked ->
- videoCard.isChecked = checked
+ binding.videoCard.isChecked = checked
updatePrice(checked, Amount.fromJSONString("KUDOS:2.25"))
updateNextButtonState()
})
@@ -113,16 +119,16 @@ class AuthenticationFragment : Fragment() {
private fun updatePrice(add: Boolean, amount: Amount) {
if (add) price += amount
else price -= amount
- recoveryCostView.text = "Recovery cost: $price"
+ binding.recoveryCostView.text = "Recovery cost: $price"
}
private fun updateNextButtonState() {
var numChecked = 0
- numChecked += if (passwordCard.isChecked) 1 else 0
- numChecked += if (postidentCard.isChecked) 1 else 0
- numChecked += if (smsCard.isChecked) 1 else 0
- numChecked += if (videoCard.isChecked) 1 else 0
- nextAuthButton.isEnabled = numChecked >= 2
+ numChecked += if (binding.passwordCard.isChecked) 1 else 0
+ numChecked += if (binding.postidentCard.isChecked) 1 else 0
+ numChecked += if (binding.smsCard.isChecked) 1 else 0
+ numChecked += if (binding.videoCard.isChecked) 1 else 0
+ binding.nextAuthButton.isEnabled = numChecked >= 2
}
}
diff --git a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/SecurityQuestionFragment.kt b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/SecurityQuestionFragment.kt
index 7353174..0796610 100644
--- a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/SecurityQuestionFragment.kt
+++ b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/SecurityQuestionFragment.kt
@@ -23,30 +23,35 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
-import com.google.android.material.transition.MaterialContainerTransform
-import com.google.android.material.transition.MaterialContainerTransform.FADE_MODE_CROSS
-import kotlinx.android.synthetic.main.fragment_security_question.*
import org.gnu.anastasis.ui.MainViewModel
-import org.gnu.anastasis.ui.R
+import org.gnu.anastasis.ui.databinding.FragmentSecurityQuestionBinding
class SecurityQuestionFragment : Fragment() {
private val viewModel: MainViewModel by activityViewModels()
+ private var _binding: FragmentSecurityQuestionBinding? = null
+
+ // This property is only valid between onCreateView and
+ // onDestroyView.
+ private val binding get() = _binding!!
+
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
- sharedElementEnterTransition = MaterialContainerTransform().apply {
- fadeMode = FADE_MODE_CROSS
- }
- return inflater.inflate(R.layout.fragment_security_question, container, false).apply {
- transitionName = "question_card"
- }
+ _binding = FragmentSecurityQuestionBinding.inflate(inflater, container, false)
+ val view = binding.root
+ return view
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- saveQuestionButton.setOnClickListener {
+ binding.saveQuestionButton.setOnClickListener {
viewModel.securityQuestionChecked.value = true
findNavController().popBackStack()
}
diff --git a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/SmsFragment.kt b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/SmsFragment.kt
index a5d872d..2c35309 100644
--- a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/SmsFragment.kt
+++ b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/SmsFragment.kt
@@ -25,17 +25,17 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.RequiresPermission
-import androidx.core.content.ContextCompat
+import androidx.core.content.ContextCompat.checkSelfPermission
import androidx.core.content.getSystemService
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import com.google.android.material.transition.MaterialContainerTransform
import com.google.android.material.transition.MaterialContainerTransform.FADE_MODE_CROSS
-import kotlinx.android.synthetic.main.fragment_sms.*
import org.gnu.anastasis.ui.MainViewModel
import org.gnu.anastasis.ui.PERMISSION_REQUEST_CODE
import org.gnu.anastasis.ui.R
+import org.gnu.anastasis.ui.databinding.FragmentSmsBinding
private const val PERMISSION = Manifest.permission.READ_PHONE_STATE
@@ -43,10 +43,17 @@ class SmsFragment : Fragment() {
private val viewModel: MainViewModel by activityViewModels()
+ private var _binding: FragmentSmsBinding? = null
+
+ // This property is only valid between onCreateView and
+ // onDestroyView.
+ private val binding get() = _binding!!
+
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?
+ savedInstanceState: Bundle?,
): View? {
+ _binding = FragmentSmsBinding.inflate(inflater, container, false)
sharedElementEnterTransition = MaterialContainerTransform().apply {
fadeMode = FADE_MODE_CROSS
}
@@ -55,19 +62,23 @@ class SmsFragment : Fragment() {
}
}
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- smsView.editText?.setOnFocusChangeListener { _, hasFocus ->
+ binding.smsView.editText?.setOnFocusChangeListener { _, hasFocus ->
if (hasFocus) checkPerm()
}
- saveSmsButton.setOnClickListener {
+ binding.saveSmsButton.setOnClickListener {
viewModel.smsChecked.value = true
findNavController().popBackStack()
}
}
private fun checkPerm() = when {
- ContextCompat.checkSelfPermission(requireContext(), PERMISSION)
- == PERMISSION_GRANTED -> {
+ checkSelfPermission(requireContext(), PERMISSION) == PERMISSION_GRANTED -> {
// You can use the API that requires the permission.
fillPhoneNumber()
}
@@ -86,7 +97,7 @@ class SmsFragment : Fragment() {
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
- grantResults: IntArray
+ grantResults: IntArray,
) {
if (requestCode == PERMISSION_REQUEST_CODE && grantResults.isNotEmpty() &&
grantResults[0] == PERMISSION_GRANTED
@@ -98,8 +109,8 @@ class SmsFragment : Fragment() {
private fun fillPhoneNumber() {
val telephonyService = requireContext().getSystemService<TelephonyManager>()
telephonyService?.line1Number?.let { phoneNumber ->
- smsView.editText?.setText(phoneNumber)
- smsView.editText?.setSelection(phoneNumber.length)
+ binding.smsView.editText?.setText(phoneNumber)
+ binding.smsView.editText?.setSelection(phoneNumber.length)
}
}
diff --git a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/VideoFragment.kt b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/VideoFragment.kt
index 6cd80ce..6716f8a 100644
--- a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/VideoFragment.kt
+++ b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/authentication/VideoFragment.kt
@@ -35,9 +35,9 @@ import androidx.navigation.fragment.findNavController
import androidx.transition.TransitionManager.beginDelayedTransition
import com.google.android.material.transition.MaterialContainerTransform
import com.google.android.material.transition.MaterialContainerTransform.FADE_MODE_CROSS
-import kotlinx.android.synthetic.main.fragment_video.*
import org.gnu.anastasis.ui.MainViewModel
import org.gnu.anastasis.ui.R
+import org.gnu.anastasis.ui.databinding.FragmentVideoBinding
import java.io.FileDescriptor
private const val REQUEST_IMAGE_CAPTURE = 1
@@ -47,10 +47,17 @@ class VideoFragment : Fragment() {
private val viewModel: MainViewModel by activityViewModels()
+ private var _binding: FragmentVideoBinding? = null
+
+ // This property is only valid between onCreateView and
+ // onDestroyView.
+ private val binding get() = _binding!!
+
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
+ _binding = FragmentVideoBinding.inflate(inflater, container, false)
sharedElementEnterTransition = MaterialContainerTransform().apply {
fadeMode = FADE_MODE_CROSS
}
@@ -59,8 +66,13 @@ class VideoFragment : Fragment() {
}
}
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- takePhotoButton.setOnClickListener {
+ binding.takePhotoButton.setOnClickListener {
val pm = requireContext().packageManager
Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
takePictureIntent.resolveActivity(pm)?.also {
@@ -70,7 +82,7 @@ class VideoFragment : Fragment() {
}
}
}
- choosePhotoButton.setOnClickListener {
+ binding.choosePhotoButton.setOnClickListener {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "image/*"
@@ -80,7 +92,7 @@ class VideoFragment : Fragment() {
)
}
- saveVideoButton.setOnClickListener {
+ binding.saveVideoButton.setOnClickListener {
viewModel.videoChecked.value = true
findNavController().popBackStack()
}
@@ -99,12 +111,14 @@ class VideoFragment : Fragment() {
}
private fun showImage(bitmap: Bitmap) {
- photoView.setImageBitmap(bitmap)
- beginDelayedTransition(view as ViewGroup)
- photoView.visibility = VISIBLE
- takePhotoButton.visibility = GONE
- choosePhotoButton.visibility = GONE
- saveVideoButton.isEnabled = true
+ with (binding) {
+ photoView.setImageBitmap(bitmap)
+ beginDelayedTransition(view as ViewGroup)
+ photoView.visibility = VISIBLE
+ takePhotoButton.visibility = GONE
+ choosePhotoButton.visibility = GONE
+ saveVideoButton.isEnabled = true
+ }
}
private fun getBitmapFromUri(uri: Uri): Bitmap {
diff --git a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/identity/ChangeLocationFragment.kt b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/identity/ChangeLocationFragment.kt
index 5b68d36..00eec11 100644
--- a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/identity/ChangeLocationFragment.kt
+++ b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/identity/ChangeLocationFragment.kt
@@ -23,14 +23,20 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
-import kotlinx.android.synthetic.main.fragment_change_location.*
import org.gnu.anastasis.ui.MainViewModel
import org.gnu.anastasis.ui.R
+import org.gnu.anastasis.ui.databinding.FragmentChangeLocationBinding
class ChangeLocationFragment : Fragment() {
private val viewModel: MainViewModel by activityViewModels()
+ private var _binding: FragmentChangeLocationBinding? = null
+
+ // This property is only valid between onCreateView and
+ // onDestroyView.
+ private val binding get() = _binding!!
+
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
@@ -40,16 +46,16 @@ class ChangeLocationFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- switzerlandView.setOnClickListener {
+ binding.switzerlandView.setOnClickListener {
changeCountry(LOCATIONS[0])
}
- germanyView.setOnClickListener {
+ binding.germanyView.setOnClickListener {
changeCountry(LOCATIONS[1])
}
- usaView.setOnClickListener {
+ binding.usaView.setOnClickListener {
changeCountry(LOCATIONS[2])
}
- indiaView.setOnClickListener {
+ binding.indiaView.setOnClickListener {
changeCountry(LOCATIONS[3])
}
}
diff --git a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/identity/IdentityFragment.kt b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/identity/IdentityFragment.kt
index ad97ab6..73ae629 100644
--- a/anastasis-ui/src/main/java/org/gnu/anastasis/ui/identity/IdentityFragment.kt
+++ b/anastasis-ui/src/main/java/org/gnu/anastasis/ui/identity/IdentityFragment.kt
@@ -31,9 +31,9 @@ import androidx.core.content.getSystemService
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
-import kotlinx.android.synthetic.main.fragment_identity.*
import org.gnu.anastasis.ui.MainViewModel
import org.gnu.anastasis.ui.R
+import org.gnu.anastasis.ui.databinding.FragmentIdentityBinding
import java.util.Calendar
import java.util.Date
import java.util.Locale
@@ -45,27 +45,40 @@ class AnastasisIdentityFragment : Fragment() {
private val model: MainViewModel by activityViewModels()
+ private var _binding: FragmentIdentityBinding? = null
+
+ // This property is only valid between onCreateView and
+ // onDestroyView.
+ private val binding get() = _binding!!
+
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?
+ savedInstanceState: Bundle?,
): View? {
- return inflater.inflate(R.layout.fragment_identity, container, false)
+ _binding = FragmentIdentityBinding.inflate(inflater, container, false)
+ val view = binding.root
+ return view
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.currentCountry.observe(viewLifecycleOwner, { country ->
- countryView.text = country.name
- if (stub != null) {
- stub.layoutResource = country.layoutRes
- stub.inflate()
+ binding.countryView.text = country.name
+ if (binding.stub != null) {
+ binding.stub.layoutResource = country.layoutRes
+ binding.stub.inflate()
}
})
- changeCountryView.setOnClickListener {
+ binding.changeCountryView.setOnClickListener {
findNavController().navigate(R.id.action_nav_anastasis_identity_to_nav_change_location)
}
- birthDateInput.editText?.setOnClickListener {
+ binding.birthDateInput.editText?.setOnClickListener {
if (SDK_INT >= 24) {
val picker = DatePickerDialog(requireContext())
picker.datePicker.maxDate =
@@ -76,14 +89,14 @@ class AnastasisIdentityFragment : Fragment() {
}
val date = Date(calender.timeInMillis)
val dateStr = getDateFormat(requireContext()).format(date)
- birthDateInput.editText?.setText(dateStr)
+ binding.birthDateInput.editText?.setText(dateStr)
}
picker.show()
} else {
Toast.makeText(requireContext(), "Needs newer phone", LENGTH_LONG).show()
}
}
- createIdentifierButton.setOnClickListener {
+ binding.createIdentifierButton.setOnClickListener {
findNavController().navigate(R.id.action_nav_anastasis_intro_to_nav_anastasis_authentication)
}
}
diff --git a/build.gradle b/build.gradle
index 8967f1f..3179daa 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,10 +1,10 @@
buildscript {
- ext.kotlin_version = '1.5.31'
- ext.ktor_version = "1.6.3"
- ext.nav_version = "2.3.5"
- ext.material_version = "1.4.0"
- ext.lifecycle_version = "2.4.0"
- ext.constraintlayout_version = "2.1.2"
+ ext.kotlin_version = '1.6.10'
+ ext.ktor_version = '2.0.1'
+ ext.nav_version = '2.4.2'
+ ext.material_version = '1.6.0'
+ ext.lifecycle_version = '2.4.1'
+ ext.constraintlayout_version = '2.1.3'
ext.junit_version = "4.13.2"
// check https://android-rebuilds.beuc.net/ for availability of free build tools
ext.build_tools_version = "30.0.3"
@@ -15,7 +15,7 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:7.0.3'
+ classpath 'com.android.tools.build:gradle:7.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
@@ -25,7 +25,6 @@ buildscript {
allprojects {
repositories {
google()
- jcenter()
mavenCentral()
maven { url 'https://jitpack.io' }
}
diff --git a/cashier/build.gradle b/cashier/build.gradle
index ae7a5a9..ab495a4 100644
--- a/cashier/build.gradle
+++ b/cashier/build.gradle
@@ -22,14 +22,14 @@ plugins {
}
android {
- compileSdkVersion 31
+ compileSdkVersion 32
//noinspection GradleDependency
buildToolsVersion "$build_tools_version"
defaultConfig {
applicationId "net.taler.cashier"
minSdkVersion 23
- targetSdkVersion 30
+ targetSdkVersion 32
versionCode 1
versionName "0.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -58,18 +58,19 @@ android {
buildFeatures {
viewBinding = true
}
-
packagingOptions {
- exclude("META-INF/*.kotlin_module")
+ resources {
+ excludes += ['META-INF/*.kotlin_module']
+ }
}
- lintOptions {
+ lint {
abortOnError true
+ ignore 'WrongConstant'
ignoreWarnings false
- // TODO remove once com.google.android.material fixed this
- // https://github.com/material-components/material-components-android/issues/504
- ignore "WrongConstant"
}
+ namespace 'net.taler.cashier'
+
}
dependencies {
@@ -84,6 +85,9 @@ dependencies {
implementation "io.ktor:ktor-client:$ktor_version"
implementation "io.ktor:ktor-client-okhttp:$ktor_version"
implementation "io.ktor:ktor-client-serialization-jvm:$ktor_version"
+ implementation "io.ktor:ktor-serialization-kotlinx-json:$ktor_version"
+ implementation "io.ktor:ktor-client-content-negotiation:$ktor_version"
+ implementation "io.ktor:ktor-server-call-logging:$ktor_version"
testImplementation "junit:junit:$junit_version"
diff --git a/cashier/src/main/AndroidManifest.xml b/cashier/src/main/AndroidManifest.xml
index 1d8b810..36a3b72 100644
--- a/cashier/src/main/AndroidManifest.xml
+++ b/cashier/src/main/AndroidManifest.xml
@@ -1,7 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="net.taler.cashier">
+ xmlns:tools="http://schemas.android.com/tools">
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.NFC" />
<application
android:allowBackup="true"
@@ -11,10 +14,12 @@
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme"
+ android:usesCleartextTraffic="true"
tools:ignore="GoogleAppIndexingWarning">
<activity
android:name=".MainActivity"
+ android:exported="true"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
diff --git a/cashier/src/main/java/net/taler/cashier/AboutDialogFragment.kt b/cashier/src/main/java/net/taler/cashier/AboutDialogFragment.kt
index cdea792..90becbd 100644
--- a/cashier/src/main/java/net/taler/cashier/AboutDialogFragment.kt
+++ b/cashier/src/main/java/net/taler/cashier/AboutDialogFragment.kt
@@ -17,20 +17,14 @@
package net.taler.cashier
import android.os.Bundle
-import android.os.Handler
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import android.view.WindowManager
-import android.widget.Button
-import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.DialogFragment
-import androidx.fragment.app.Fragment
import net.taler.cashier.BuildConfig.VERSION_NAME
import net.taler.cashier.config.VERSION_BANK
import net.taler.cashier.databinding.FragmentAboutDialogBinding
-import net.taler.cashier.databinding.FragmentBalanceBinding
-import net.taler.lib.common.Version
+import net.taler.common.Version
class AboutDialogFragment : DialogFragment() {
@@ -39,8 +33,8 @@ class AboutDialogFragment : DialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
+ savedInstanceState: Bundle?,
+ ): View {
ui = FragmentAboutDialogBinding.inflate(layoutInflater, container, false)
return ui.root
}
@@ -50,8 +44,10 @@ class AboutDialogFragment : DialogFragment() {
ui.versionView.text = getString(R.string.about_version, VERSION_NAME)
ui.bankVersionView.text = getString(R.string.about_supported_bank_api, VERSION_BANK.str())
- ui.licenseView.text = getString(R.string.about_license, getString(R.string.about_license_content))
- ui.copyrightView.text = getString(R.string.about_copyright, getString(R.string.about_copyright_holder))
+ ui.licenseView.text =
+ getString(R.string.about_license, getString(R.string.about_license_content))
+ ui.copyrightView.text =
+ getString(R.string.about_copyright, getString(R.string.about_copyright_holder))
ui.button.setOnClickListener { dismiss() }
}
diff --git a/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt b/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt
index 002301c..fa9600b 100644
--- a/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt
+++ b/cashier/src/main/java/net/taler/cashier/BalanceFragment.kt
@@ -32,10 +32,10 @@ import net.taler.cashier.BalanceFragmentDirections.Companion.actionBalanceFragme
import net.taler.cashier.databinding.FragmentBalanceBinding
import net.taler.cashier.withdraw.LastTransaction
import net.taler.cashier.withdraw.WithdrawStatus
+import net.taler.common.Amount
import net.taler.common.exhaustive
import net.taler.common.fadeIn
import net.taler.common.fadeOut
-import net.taler.lib.common.Amount
sealed class BalanceResult {
class Error(val msg: String) : BalanceResult()
diff --git a/cashier/src/main/java/net/taler/cashier/MainViewModel.kt b/cashier/src/main/java/net/taler/cashier/MainViewModel.kt
index 253c7d5..21d0209 100644
--- a/cashier/src/main/java/net/taler/cashier/MainViewModel.kt
+++ b/cashier/src/main/java/net/taler/cashier/MainViewModel.kt
@@ -24,17 +24,17 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import io.ktor.client.HttpClient
import io.ktor.client.engine.okhttp.OkHttp
-import io.ktor.client.features.json.JsonFeature
-import io.ktor.client.features.json.serializer.KotlinxSerializer
+import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
+import io.ktor.serialization.kotlinx.json.json
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import net.taler.cashier.HttpHelper.makeJsonGetRequest
import net.taler.cashier.config.ConfigManager
import net.taler.cashier.withdraw.WithdrawManager
+import net.taler.common.Amount
+import net.taler.common.AmountParserException
import net.taler.common.isOnline
-import net.taler.lib.common.Amount
-import net.taler.lib.common.AmountParserException
private val TAG = MainViewModel::class.java.simpleName
@@ -46,12 +46,12 @@ class MainViewModel(private val app: Application) : AndroidViewModel(app) {
retryOnConnectionFailure(true)
}
}
- install(JsonFeature) {
- serializer = KotlinxSerializer(
- Json {
- ignoreUnknownKeys = true
- }
- )
+ expectSuccess = true
+ install(ContentNegotiation) {
+ json(Json {
+ encodeDefaults = false
+ ignoreUnknownKeys = true
+ })
}
}
val configManager = ConfigManager(app, viewModelScope, httpClient)
@@ -64,7 +64,7 @@ class MainViewModel(private val app: Application) : AndroidViewModel(app) {
fun getBalance() = viewModelScope.launch(Dispatchers.IO) {
check(configManager.hasConfig()) { "No config to get balance" }
val config = configManager.config
- val url = "${config.bankUrl}/accounts/${config.username}"
+ val url = "${config.bankUrl}/access-api/accounts/${config.username}"
Log.d(TAG, "Checking balance at $url")
val result = when (val response = makeJsonGetRequest(url, config)) {
is HttpJsonResult.Success -> {
@@ -77,7 +77,8 @@ class MainViewModel(private val app: Application) : AndroidViewModel(app) {
"debit" -> false
else -> throw AmountParserException("Unexpected credit_debit_indicator: $creditDebitIndicator")
}
- BalanceResult.Success(SignedAmount(positive, Amount.fromJSONString(balanceAmount)))
+ BalanceResult.Success(SignedAmount(positive,
+ Amount.fromJSONString(balanceAmount)))
} catch (e: Exception) {
Log.e(TAG, "Error parsing balance", e)
BalanceResult.Error("Invalid amount:\n${response.json.toString(2)}")
diff --git a/cashier/src/main/java/net/taler/cashier/Response.kt b/cashier/src/main/java/net/taler/cashier/Response.kt
index 6a72604..89b7b33 100644
--- a/cashier/src/main/java/net/taler/cashier/Response.kt
+++ b/cashier/src/main/java/net/taler/cashier/Response.kt
@@ -18,8 +18,9 @@ package net.taler.cashier
import android.content.Context
import android.util.Log
+import io.ktor.client.call.body
import io.ktor.client.call.receive
-import io.ktor.client.features.ResponseException
+import io.ktor.client.plugins.ResponseException
import io.ktor.http.HttpStatusCode
import kotlinx.serialization.Serializable
import net.taler.common.isOnline
@@ -47,7 +48,7 @@ class Response<out T> private constructor(
val response = e.response
return try {
Log.e("TEST", "TRY RECEIVE $response")
- val error: Error = response.receive()
+ val error: Error = response.body()
"Error ${error.code}: ${error.hint}"
} catch (ex: Exception) {
"Status code: ${response.status.value}"
diff --git a/cashier/src/main/java/net/taler/cashier/SignedAmount.kt b/cashier/src/main/java/net/taler/cashier/SignedAmount.kt
index e79acfd..4f624ae 100644
--- a/cashier/src/main/java/net/taler/cashier/SignedAmount.kt
+++ b/cashier/src/main/java/net/taler/cashier/SignedAmount.kt
@@ -16,7 +16,7 @@
package net.taler.cashier
-import net.taler.lib.common.Amount
+import net.taler.common.Amount
data class SignedAmount(
val positive: Boolean,
diff --git a/cashier/src/main/java/net/taler/cashier/config/ConfigFragment.kt b/cashier/src/main/java/net/taler/cashier/config/ConfigFragment.kt
index e6ac249..a595a7f 100644
--- a/cashier/src/main/java/net/taler/cashier/config/ConfigFragment.kt
+++ b/cashier/src/main/java/net/taler/cashier/config/ConfigFragment.kt
@@ -38,7 +38,8 @@ import net.taler.cashier.R
import net.taler.cashier.databinding.FragmentConfigBinding
import net.taler.common.exhaustive
-private const val URL_BANK_TEST = "https://bank.test.taler.net"
+// NOTE: HTTP should eventually be disabled.
+private const val URL_BANK_TEST = "https://int.taler.net/sandbox/demobanks/default"
private const val URL_BANK_TEST_REGISTER = "$URL_BANK_TEST/accounts/register"
class ConfigFragment : Fragment() {
@@ -116,7 +117,8 @@ class ConfigFragment : Fragment() {
}
private fun checkConfig(config: Config): Boolean {
- if (!config.bankUrl.startsWith("https://")) {
+ if (!config.bankUrl.startsWith("https://") &&
+ !config.bankUrl.startsWith("http://")) {
ui.urlView.error = getString(R.string.config_bank_url_error)
ui.urlView.requestFocus()
return false
diff --git a/cashier/src/main/java/net/taler/cashier/config/ConfigManager.kt b/cashier/src/main/java/net/taler/cashier/config/ConfigManager.kt
index 0718963..d850d27 100644
--- a/cashier/src/main/java/net/taler/cashier/config/ConfigManager.kt
+++ b/cashier/src/main/java/net/taler/cashier/config/ConfigManager.kt
@@ -29,6 +29,7 @@ import androidx.security.crypto.EncryptedSharedPreferences.PrefValueEncryptionSc
import androidx.security.crypto.MasterKeys
import androidx.security.crypto.MasterKeys.AES256_GCM_SPEC
import io.ktor.client.HttpClient
+import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.http.HttpHeaders.Authorization
@@ -39,8 +40,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.taler.cashier.Response
import net.taler.cashier.Response.Companion.response
+import net.taler.common.Version
import net.taler.common.getIncompatibleStringOrNull
-import net.taler.lib.common.Version
val VERSION_BANK = Version(0, 0, 0)
private const val PREF_NAME = "net.taler.cashier.prefs"
@@ -111,12 +112,11 @@ class ConfigManager(
mConfigResult.postValue(result)
}
}
-
private suspend fun checkConfig(config: Config) = withContext(Dispatchers.IO) {
- val url = "${config.bankUrl}/config"
+ val url = "${config.bankUrl}/integration-api/config"
Log.d(TAG, "Checking config: $url")
val configResponse = response {
- httpClient.get(url) as ConfigResponse
+ httpClient.get(url).body<ConfigResponse>()
}
if (configResponse.isFailure) {
configResponse
@@ -124,9 +124,9 @@ class ConfigManager(
// we need to check an endpoint that requires authentication as well
// to see if the credentials are valid
val balanceResponse = response {
- val authUrl = "${config.bankUrl}/accounts/${config.username}"
+ val authUrl = "${config.bankUrl}/access-api/accounts/${config.username}"
Log.d(TAG, "Checking auth: $authUrl")
- httpClient.get<Unit>(authUrl) {
+ httpClient.get(authUrl) {
header(Authorization, config.basicAuth)
}
}
diff --git a/cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt b/cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt
index 5d34bba..1b809bb 100644
--- a/cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt
+++ b/cashier/src/main/java/net/taler/cashier/withdraw/WithdrawManager.kt
@@ -34,9 +34,9 @@ import net.taler.cashier.HttpJsonResult.Error
import net.taler.cashier.HttpJsonResult.Success
import net.taler.cashier.MainViewModel
import net.taler.cashier.R
+import net.taler.common.Amount
import net.taler.common.QrCodeManager.makeQrCode
import net.taler.common.isOnline
-import net.taler.lib.common.Amount
import org.json.JSONObject
import java.util.concurrent.TimeUnit.MINUTES
import java.util.concurrent.TimeUnit.SECONDS
@@ -87,7 +87,7 @@ class WithdrawManager(
mWithdrawResult.value = null
mWithdrawAmount.value = amount
scope.launch(Dispatchers.IO) {
- val url = "${config.bankUrl}/accounts/${config.username}/withdrawals"
+ val url = "${config.bankUrl}/access-api/accounts/${config.username}/withdrawals"
Log.d(TAG, "Starting withdrawal at $url")
val map = mapOf("amount" to amount.toJSONString())
val body = JSONObject(map)
@@ -146,7 +146,7 @@ class WithdrawManager(
}
private fun checkWithdrawStatus(withdrawalId: String) = scope.launch(Dispatchers.IO) {
- val url = "${config.bankUrl}/accounts/${config.username}/withdrawals/${withdrawalId}"
+ val url = "${config.bankUrl}/access-api/accounts/${config.username}/withdrawals/${withdrawalId}"
Log.d(TAG, "Checking withdraw status at $url")
val response = makeJsonGetRequest(url, config)
if (response !is Success) return@launch // ignore errors and continue trying
@@ -197,7 +197,7 @@ class WithdrawManager(
}
private fun abort(withdrawalId: String) = scope.launch(Dispatchers.IO) {
- val url = "${config.bankUrl}/accounts/${config.username}/withdrawals/${withdrawalId}/abort"
+ val url = "${config.bankUrl}/access-api/accounts/${config.username}/withdrawals/${withdrawalId}/abort"
Log.d(TAG, "Aborting withdrawal at $url")
makeJsonPostRequest(url, JSONObject(), config)
}
@@ -207,7 +207,7 @@ class WithdrawManager(
mWithdrawStatus.value = WithdrawStatus.Confirming
scope.launch(Dispatchers.IO) {
val url =
- "${config.bankUrl}/accounts/${config.username}/withdrawals/${withdrawalId}/confirm"
+ "${config.bankUrl}/access-api/accounts/${config.username}/withdrawals/${withdrawalId}/confirm"
Log.d(TAG, "Confirming withdrawal at $url")
when (val response = makeJsonPostRequest(url, JSONObject(), config)) {
is Success -> {
diff --git a/cashier/src/main/res/values-de/strings.xml b/cashier/src/main/res/values-de/strings.xml
index d53e3d4..ef79109 100644
--- a/cashier/src/main/res/values-de/strings.xml
+++ b/cashier/src/main/res/values-de/strings.xml
@@ -2,21 +2,21 @@
<resources>
<string name="config_button_save">Speichern</string>
<string name="config_bank_url_error">Die Adresse ist ungültig.</string>
- <string name="config_username_error">Bitte geben Sie Ihren Benutzernamen ein</string>
+ <string name="config_username_error">Bitte geben Sie Ihren Benutzernamen ein!</string>
<string name="config_error">Fehler beim Abrufen der Konfiguration: %s</string>
- <string name="config_error_auth">Benutzername oder Passwort ungültig</string>
+ <string name="config_error_auth">Benutzername oder Passwort ungültig!</string>
<string name="balance_current_label">Aktueller Saldo</string>
<string name="balance_error">FEHLER: %s</string>
<string name="balance_offline">Offline. Bitte Verbindung zum Internet herstellen.</string>
<string name="action_reconfigure">Neukonfiguration</string>
<string name="action_lock">Sperren</string>
<string name="withdraw_input_amount">Betrag</string>
- <string name="withdraw_into">Wie viel E-Cash sollte abgehoben werden\?</string>
+ <string name="withdraw_into">Welcher Betrag soll abgehoben werden\?</string>
<string name="withdraw_error_zero">Geben Sie einen positiven Betrag ein!</string>
<string name="withdraw_error_insufficient_balance">Unzureichendes Guthaben</string>
- <string name="withdraw_error_fetch">Fehler bei der Kommunikation mit der Bank: %s</string>
+ <string name="withdraw_error_fetch">Fehler bei der Verbindung mit der Bank: %s</string>
<string name="withdraw_button_confirm">Abheben</string>
- <string name="transaction_intro_nfc">Scannen Sie mit der Taler Wallet App\nden Code oder benutzen Sie NFC.\nDann erhalten Sie</string>
+ <string name="transaction_intro_nfc">Scannen Sie zum Abheben den Code oder kontaktlos mit NFC:</string>
<string name="config_password">Passwort</string>
<string name="transaction_confirm">Bestätigen</string>
<string name="transaction_abort">Abbrechen</string>
@@ -27,16 +27,16 @@
<string name="app_name">Taler Kassierer</string>
<string name="config_bank_url">Bank API Adresse</string>
<string name="config_demo_hint">Zum Testen können Sie <![CDATA[<a href="%s">ein Testkonto bei der Demobank erstellen</a>]]>.</string>
- <string name="transaction_intro">Scannen Sie den Code\nmit der Taler Wallet.\nDann erhalten Sie</string>
+ <string name="transaction_intro">Scannen Sie zum Abheben den Code mit dem Taler-Wallet:</string>
<string name="transaction_intro_scanned">Bitte bestätigen Sie die Transaktion!</string>
<string name="transaction_last_aborted">Letzte Transaktion: Abgebrochen</string>
<string name="config_username">Benutzername</string>
- <string name="withdraw_error_timeout">Es wurde noch keine Abhebung ausgelöst. Bitte versuchen Sie es erneut.</string>
- <string name="action_about">Über</string>
+ <string name="withdraw_error_timeout">Bitte lassen Sie ein Wallet die Abhebung auslösen.</string>
+ <string name="action_about">Info</string>
<string name="ok">OK</string>
<string name="about_title">GNU Taler Kassierer</string>
<string name="about_license">Lizenz: %s</string>
<string name="about_version">Version: %s</string>
<string name="about_copyright">Copyright: %s</string>
<string name="about_supported_bank_api">Bank API Version: %s</string>
-</resources>
+</resources> \ No newline at end of file
diff --git a/cashier/src/main/res/values-es/strings.xml b/cashier/src/main/res/values-es/strings.xml
index af42d1e..cfbfda6 100644
--- a/cashier/src/main/res/values-es/strings.xml
+++ b/cashier/src/main/res/values-es/strings.xml
@@ -26,7 +26,7 @@
<string name="withdraw_button_confirm">Retirar</string>
<string name="transaction_intro">Escanear código con la App de cartera Taler para retirar:</string>
<string name="transaction_intro_nfc">Escanear código o usar NFC con la App de cartera Taler para retirar:</string>
- <string name="transaction_intro_scanned">Por favor confirma la transacción!</string>
+ <string name="transaction_intro_scanned">¡Por favor confirma la transacción!</string>
<string name="transaction_confirm">Confirmar</string>
<string name="transaction_abort">Abortar</string>
<string name="transaction_aborted">Transacción abortada</string>
diff --git a/cashier/src/main/res/values-fr/strings.xml b/cashier/src/main/res/values-fr/strings.xml
index ab46377..0d2fbd2 100644
--- a/cashier/src/main/res/values-fr/strings.xml
+++ b/cashier/src/main/res/values-fr/strings.xml
@@ -32,4 +32,11 @@
<string name="transaction_last_success">Dernière transaction : %s retiré</string>
<string name="transaction_last_aborted">Dernière transaction : Annulée</string>
<string name="transaction_last_error">Dernière transaction : Échec</string>
+ <string name="ok">OK</string>
+ <string name="action_about">À propos</string>
+ <string name="about_version">Version : %s</string>
+ <string name="about_license">Licence : %s</string>
+ <string name="about_copyright">Droits d\'auteur : %s</string>
+ <string name="about_supported_bank_api">Version Bank API : %s</string>
+ <string name="about_title">GNU Taler Cashier</string>
</resources> \ No newline at end of file
diff --git a/cashier/src/main/res/values-tr/strings.xml b/cashier/src/main/res/values-tr/strings.xml
new file mode 100644
index 0000000..b1afb2e
--- /dev/null
+++ b/cashier/src/main/res/values-tr/strings.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="config_bank_url">Banka API adresi</string>
+ <string name="config_username">Kullanıcı adı</string>
+ <string name="config_password">Şifre</string>
+ <string name="config_button_save">Kaydet</string>
+ <string name="config_bank_url_error">Bu adres geçersiz.</string>
+ <string name="config_username_error">Lütfen kullanıcı adınızı giriniz!</string>
+ <string name="app_name">Taler Kasiyer</string>
+ <string name="config_error">Konfigürasyon alınırken hata oluştu: %s</string>
+ <string name="config_error_auth">Geçersiz kullanıcı adı yada şifre!</string>
+ <string name="config_demo_hint">Test için &lt;a href=%s&gt;demo bankasında bir test hesabı oluşturabilirsiniz&lt;/a&gt;.</string>
+ <string name="balance_current_label">Mevcut bakiye</string>
+ <string name="balance_error">HATA:%s</string>
+ <string name="balance_offline">Çevrimdışı. Lütfen internete bağlanın.</string>
+ <string name="ok">Tamam</string>
+ <string name="action_reconfigure">Yeniden yapılandırın</string>
+ <string name="action_lock">Kilitle</string>
+ <string name="action_about">Hakkında</string>
+ <string name="withdraw_into">Ne kadar e-nakit çekilmelidir\?</string>
+ <string name="withdraw_error_zero">Pozitif tutarı girin!</string>
+ <string name="withdraw_input_amount">Tutar</string>
+ <string name="withdraw_error_insufficient_balance">Yetersiz bakiye</string>
+ <string name="withdraw_error_fetch">Banka ile iletişimde hata: %s</string>
+ <string name="withdraw_error_timeout">Hiçbir cüzdan para çekmeye çalışmadı. Lütfen tekrar deneyin.</string>
+ <string name="withdraw_button_confirm">Para çek</string>
+ <string name="transaction_intro">Para çekmek için Taler cüzdan uygulamasıyla kodu tarayın:</string>
+ <string name="transaction_intro_nfc">Para çekmek için Taler cüzdan uygulamasıyla kodu tarayın veya NFC\'yi kullanın:</string>
+ <string name="transaction_intro_scanned">Lütfen işlemi onaylayın!</string>
+ <string name="transaction_confirm">Onayla</string>
+ <string name="transaction_abort">Durdur</string>
+ <string name="transaction_aborted">İşlem durduruldu</string>
+ <string name="transaction_button_back">Geri git</string>
+ <string name="transaction_last_success">Son İşlem: %s çekildi</string>
+ <string name="transaction_last_aborted">Son İşlem: Durduruldu</string>
+ <string name="transaction_last_error">Son İşlem: Başarısız</string>
+ <string name="about_title">GNU Taler Kasiyer</string>
+ <string name="about_license">Lisans: %s</string>
+ <string name="about_copyright">Telif Hakkı: %s</string>
+ <string name="about_supported_bank_api">Banka API Sürümü: %s</string>
+ <string name="about_version">Sürüm: %s</string>
+</resources> \ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 7a0ef64..581797b 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Fri Aug 27 15:56:47 CEST 2021
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
diff --git a/merchant-lib/build.gradle b/merchant-lib/build.gradle
index f53baa2..42dc3ec 100644
--- a/merchant-lib/build.gradle
+++ b/merchant-lib/build.gradle
@@ -21,15 +21,13 @@ plugins {
}
android {
- compileSdkVersion 30
+ compileSdkVersion 32
//noinspection GradleDependency
buildToolsVersion "$build_tools_version"
defaultConfig {
minSdkVersion 21
- targetSdkVersion 30
- versionCode 1
- versionName "0.1"
+ targetSdkVersion 32
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
@@ -50,6 +48,7 @@ android {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
+ namespace 'net.taler.merchantlib'
}
dependencies {
@@ -58,9 +57,17 @@ dependencies {
api "io.ktor:ktor-client:$ktor_version"
api "io.ktor:ktor-client-okhttp:$ktor_version"
api "io.ktor:ktor-client-serialization-jvm:$ktor_version"
+ api "io.ktor:ktor-client-content-negotiation:$ktor_version"
+ implementation "io.ktor:ktor-serialization-kotlinx-json:$ktor_version"
+ implementation "io.ktor:ktor-server-call-logging:$ktor_version"
testImplementation "junit:junit:$junit_version"
testImplementation "io.ktor:ktor-client-mock-jvm:$ktor_version"
- testImplementation "io.ktor:ktor-client-logging-jvm:$ktor_version"
- testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.1'
+ testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.1'
}
+
+tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
+ kotlinOptions {
+ freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
+ }
+} \ No newline at end of file
diff --git a/merchant-lib/src/main/AndroidManifest.xml b/merchant-lib/src/main/AndroidManifest.xml
index 1408b33..40b4a25 100644
--- a/merchant-lib/src/main/AndroidManifest.xml
+++ b/merchant-lib/src/main/AndroidManifest.xml
@@ -14,8 +14,7 @@
~ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="net.taler.merchantlib">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt
index 0d22f91..c02907b 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/MerchantApi.kt
@@ -17,21 +17,24 @@
package net.taler.merchantlib
import io.ktor.client.HttpClient
+import io.ktor.client.call.body
import io.ktor.client.engine.okhttp.OkHttp
-import io.ktor.client.features.json.JsonFeature
-import io.ktor.client.features.json.serializer.KotlinxSerializer
+import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.request.delete
import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.client.request.post
+import io.ktor.client.request.setBody
import io.ktor.http.ContentType.Application.Json
import io.ktor.http.HttpHeaders.Authorization
import io.ktor.http.contentType
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
-import kotlinx.serialization.json.Json
import net.taler.merchantlib.Response.Companion.response
+import io.ktor.serialization.kotlinx.json.*
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.json.Json
class MerchantApi(
private val httpClient: HttpClient = getDefaultHttpClient(),
@@ -40,7 +43,7 @@ class MerchantApi(
suspend fun getConfig(baseUrl: String): Response<ConfigResponse> = withContext(ioDispatcher) {
response {
- httpClient.get("$baseUrl/config") as ConfigResponse
+ httpClient.get("$baseUrl/config").body()
}
}
@@ -52,8 +55,8 @@ class MerchantApi(
httpClient.post(merchantConfig.urlFor("private/orders")) {
header(Authorization, "ApiKey ${merchantConfig.apiKey}")
contentType(Json)
- body = orderRequest
- } as PostOrderResponse
+ setBody(orderRequest)
+ }.body()
}
}
@@ -64,7 +67,7 @@ class MerchantApi(
response {
httpClient.get(merchantConfig.urlFor("private/orders/$orderId")) {
header(Authorization, "ApiKey ${merchantConfig.apiKey}")
- } as CheckPaymentResponse
+ }.body()
}
}
@@ -75,7 +78,7 @@ class MerchantApi(
response {
httpClient.delete(merchantConfig.urlFor("private/orders/$orderId")) {
header(Authorization, "ApiKey ${merchantConfig.apiKey}")
- } as Unit
+ }.body()
}
}
@@ -84,7 +87,7 @@ class MerchantApi(
response {
httpClient.get(merchantConfig.urlFor("private/orders")) {
header(Authorization, "ApiKey ${merchantConfig.apiKey}")
- } as OrderHistory
+ }.body()
}
}
@@ -97,27 +100,23 @@ class MerchantApi(
httpClient.post(merchantConfig.urlFor("private/orders/$orderId/refund")) {
header(Authorization, "ApiKey ${merchantConfig.apiKey}")
contentType(Json)
- body = request
- } as RefundResponse
+ setBody(request)
+ }.body()
}
}
}
fun getDefaultHttpClient(): HttpClient = HttpClient(OkHttp) {
+ expectSuccess = true
engine {
config {
retryOnConnectionFailure(true)
}
}
- install(JsonFeature) {
- serializer = getSerializer()
+ install(ContentNegotiation) {
+ json(Json {
+ encodeDefaults = false
+ ignoreUnknownKeys = true
+ })
}
}
-
-fun getSerializer() = KotlinxSerializer(
- Json {
- encodeDefaults = false
- ignoreUnknownKeys = true
- classDiscriminator = "order_status"
- }
-)
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt b/merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt
index dfd989b..b1ff5b1 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/OrderHistory.kt
@@ -18,8 +18,8 @@ package net.taler.merchantlib
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
-import net.taler.lib.common.Amount
-import net.taler.lib.common.Timestamp
+import net.taler.common.Amount
+import net.taler.common.Timestamp
@Serializable
data class OrderHistory(
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/Orders.kt b/merchant-lib/src/main/java/net/taler/merchantlib/Orders.kt
index 166ca3c..391abf5 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/Orders.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/Orders.kt
@@ -16,11 +16,12 @@
package net.taler.merchantlib
+import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.JsonClassDiscriminator
import net.taler.common.ContractTerms
-import net.taler.lib.android.CustomClassDiscriminator
-import net.taler.lib.common.Duration
+import net.taler.common.Duration
@Serializable
data class PostOrderRequest(
@@ -36,9 +37,10 @@ data class PostOrderResponse(
val orderId: String
)
+@OptIn(ExperimentalSerializationApi::class)
@Serializable
-sealed class CheckPaymentResponse: CustomClassDiscriminator {
- override val discriminator: String = "order_status"
+@JsonClassDiscriminator("order_status")
+sealed class CheckPaymentResponse {
abstract val paid: Boolean
@Serializable
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/Refunds.kt b/merchant-lib/src/main/java/net/taler/merchantlib/Refunds.kt
index b78b571..58073fa 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/Refunds.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/Refunds.kt
@@ -18,8 +18,7 @@ package net.taler.merchantlib
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
-import net.taler.lib.common.Amount
-
+import net.taler.common.Amount
@Serializable
data class RefundRequest(
/**
diff --git a/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt b/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt
index 6fd0e37..b7ba1ac 100644
--- a/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt
+++ b/merchant-lib/src/main/java/net/taler/merchantlib/Response.kt
@@ -16,10 +16,10 @@
package net.taler.merchantlib
-import io.ktor.client.call.receive
-import io.ktor.client.features.ClientRequestException
-import io.ktor.client.features.ResponseException
-import io.ktor.client.features.ServerResponseException
+import io.ktor.client.call.body
+import io.ktor.client.plugins.ClientRequestException
+import io.ktor.client.plugins.ResponseException
+import io.ktor.client.plugins.ServerResponseException
import kotlinx.serialization.Serializable
class Response<out T> private constructor(
@@ -72,7 +72,7 @@ class Response<out T> private constructor(
private suspend fun getExceptionString(e: ResponseException): String {
val response = e.response
return try {
- val error: Error = response.receive()
+ val error: Error = response.body()
"Error ${error.code}: ${error.hint}"
} catch (ex: Exception) {
"Status code: ${response.status.value}"
diff --git a/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt b/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
index 6abacfd..a89e2d3 100644
--- a/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
+++ b/merchant-lib/src/test/java/net/taler/merchantlib/MerchantApiTest.kt
@@ -20,10 +20,11 @@ import io.ktor.http.HttpStatusCode.Companion.NotFound
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestCoroutineDispatcher
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import net.taler.common.Amount
import net.taler.common.ContractProduct
import net.taler.common.ContractTerms
-import net.taler.lib.common.Amount
-import net.taler.lib.common.Timestamp
+import net.taler.common.Timestamp
import net.taler.merchantlib.MockHttpClient.giveJsonResponse
import net.taler.merchantlib.MockHttpClient.httpClient
import org.junit.Assert.assertEquals
@@ -33,7 +34,7 @@ import org.junit.Test
@ExperimentalCoroutinesApi
class MerchantApiTest {
- private val api = MerchantApi(httpClient, TestCoroutineDispatcher())
+ private val api = MerchantApi(httpClient, UnconfinedTestDispatcher())
private val merchantConfig = MerchantConfig(
baseUrl = "http://example.net/instances/testInstance",
apiKey = "apiKeyFooBar"
diff --git a/merchant-lib/src/test/java/net/taler/merchantlib/MockHttpClient.kt b/merchant-lib/src/test/java/net/taler/merchantlib/MockHttpClient.kt
index c8e6f22..32c7ee1 100644
--- a/merchant-lib/src/test/java/net/taler/merchantlib/MockHttpClient.kt
+++ b/merchant-lib/src/test/java/net/taler/merchantlib/MockHttpClient.kt
@@ -20,11 +20,7 @@ import io.ktor.client.HttpClient
import io.ktor.client.engine.mock.MockEngine
import io.ktor.client.engine.mock.MockEngineConfig
import io.ktor.client.engine.mock.respond
-import io.ktor.client.features.json.JsonFeature
-import io.ktor.client.features.logging.LogLevel
-import io.ktor.client.features.logging.Logger
-import io.ktor.client.features.logging.Logging
-import io.ktor.client.features.logging.SIMPLE
+import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.http.ContentType.Application.Json
import io.ktor.http.HttpStatusCode
import io.ktor.http.Url
@@ -32,29 +28,31 @@ import io.ktor.http.content.TextContent
import io.ktor.http.fullPath
import io.ktor.http.headersOf
import io.ktor.http.hostWithPort
+import io.ktor.serialization.kotlinx.json.json
+import kotlinx.serialization.json.Json
import kotlinx.serialization.json.Json.Default.parseToJsonElement
import org.junit.Assert.assertEquals
object MockHttpClient {
val httpClient = HttpClient(MockEngine) {
- install(JsonFeature) {
- serializer = getSerializer()
- }
- install(Logging) {
- logger = Logger.SIMPLE
- level = LogLevel.ALL
- }
engine {
addHandler { error("No response handler set") }
}
+ expectSuccess = true
+ install(ContentNegotiation) {
+ json(Json {
+ encodeDefaults = false
+ ignoreUnknownKeys = true
+ })
+ }
}
fun HttpClient.giveJsonResponse(
url: String,
expectedBody: String? = null,
statusCode: HttpStatusCode = HttpStatusCode.OK,
- jsonProducer: () -> String
+ jsonProducer: () -> String,
) {
val httpConfig = engineConfig as MockEngineConfig
httpConfig.requestHandlers.removeAt(0)
diff --git a/merchant-terminal/build.gradle b/merchant-terminal/build.gradle
index 5354da5..00fe568 100644
--- a/merchant-terminal/build.gradle
+++ b/merchant-terminal/build.gradle
@@ -6,14 +6,14 @@ plugins {
}
android {
- compileSdkVersion 31
+ compileSdkVersion 32
//noinspection GradleDependency
buildToolsVersion "$build_tools_version"
defaultConfig {
applicationId "net.taler.merchantpos"
minSdkVersion 21
- targetSdkVersion 30
+ targetSdkVersion 32
versionCode 2
versionName "0.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -48,19 +48,19 @@ android {
includeAndroidResources = true
}
}
+ packagingOptions {
+ resources {
+ excludes += ['META-INF/common.kotlin_module', 'META-INF/*.kotlin_module']
+ }
+ }
+
- lintOptions {
+ lint {
abortOnError true
+ ignore 'WrongConstant'
ignoreWarnings false
- // TODO remove once com.google.android.material fixed this
- // https://github.com/material-components/material-components-android/issues/504
- ignore "WrongConstant"
- }
-
- packagingOptions {
- exclude 'META-INF/common.kotlin_module'
- exclude 'META-INF/*.kotlin_module'
}
+ namespace 'net.taler.merchantpos'
}
dependencies {
@@ -78,5 +78,5 @@ dependencies {
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
testImplementation 'androidx.test.ext:junit:1.1.3'
- testImplementation 'org.robolectric:robolectric:4.4'
+ testImplementation 'org.robolectric:robolectric:4.8.1'
}
diff --git a/merchant-terminal/src/main/AndroidManifest.xml b/merchant-terminal/src/main/AndroidManifest.xml
index 1518293..f7b4929 100644
--- a/merchant-terminal/src/main/AndroidManifest.xml
+++ b/merchant-terminal/src/main/AndroidManifest.xml
@@ -15,8 +15,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="net.taler.merchantpos">
+ xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.NFC" />
@@ -40,6 +39,7 @@
<activity
android:name=".MainActivity"
android:label="@string/app_name"
+ android:exported="true"
android:screenOrientation="landscape"
android:theme="@style/AppTheme.NoActionBar"
tools:ignore="LockedOrientationActivity">
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt
index 47da74e..29f5da4 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/MainActivity.kt
@@ -55,11 +55,11 @@ class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener {
ui = ActivityMainBinding.inflate(layoutInflater)
setContentView(ui.root)
- model.paymentManager.payment.observe(this, { payment ->
+ model.paymentManager.payment.observe(this) { payment ->
payment?.talerPayUri?.let {
nfcManager.setTagString(it)
}
- })
+ }
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.navHostFragment) as NavHostFragment
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt
index 87004d8..b5b7be7 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigFetcherFragment.kt
@@ -40,8 +40,8 @@ class ConfigFetcherFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
+ savedInstanceState: Bundle?,
+ ): View {
ui = FragmentConfigFetcherBinding.inflate(inflater)
return ui.root
}
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt
index 165bb8e..4327f4e 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/config/ConfigManager.kt
@@ -26,7 +26,8 @@ import androidx.annotation.WorkerThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import io.ktor.client.HttpClient
-import io.ktor.client.features.ClientRequestException
+import io.ktor.client.call.body
+import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.http.HttpHeaders.Authorization
@@ -34,8 +35,8 @@ import io.ktor.http.HttpStatusCode.Companion.Unauthorized
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+import net.taler.common.Version
import net.taler.common.getIncompatibleStringOrNull
-import net.taler.lib.common.Version
import net.taler.merchantlib.ConfigResponse
import net.taler.merchantlib.MerchantApi
import net.taler.merchantlib.MerchantConfig
@@ -105,7 +106,7 @@ class ConfigManager(
val credentials = "${config.username}:${config.password}"
val auth = ("Basic ${encodeToString(credentials.toByteArray(), NO_WRAP)}")
header(Authorization, auth)
- }
+ }.body()
val merchantConfig = posConfig.merchantConfig
// get config from merchant backend API
api.getConfig(merchantConfig.baseUrl).handleSuspend(::onNetworkError) {
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/config/PosConfig.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/config/PosConfig.kt
index 971f92c..1f1ab74 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/config/PosConfig.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/config/PosConfig.kt
@@ -19,10 +19,10 @@ package net.taler.merchantpos.config
import android.os.Build.VERSION.SDK_INT
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
+import net.taler.common.Amount
import net.taler.common.ContractProduct
import net.taler.common.Product
import net.taler.common.TalerUtils
-import net.taler.lib.common.Amount
import net.taler.merchantlib.MerchantConfig
import java.util.UUID
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/order/LiveOrder.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/order/LiveOrder.kt
index f48c1db..ad9af74 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/order/LiveOrder.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/order/LiveOrder.kt
@@ -20,8 +20,8 @@ import androidx.annotation.UiThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
+import net.taler.common.Amount
import net.taler.common.CombinedLiveData
-import net.taler.lib.common.Amount
import net.taler.merchantpos.config.Category
import net.taler.merchantpos.config.ConfigProduct
import net.taler.merchantpos.order.RestartState.DISABLED
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt
index 0bea20c..9860dbd 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/order/Order.kt
@@ -16,10 +16,10 @@
package net.taler.merchantpos.order
+import net.taler.common.Amount
import net.taler.common.ContractTerms
+import net.taler.common.Timestamp
import net.taler.common.now
-import net.taler.lib.common.Amount
-import net.taler.lib.common.Timestamp
import net.taler.merchantpos.config.Category
import net.taler.merchantpos.config.ConfigProduct
import java.net.URLEncoder
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt
index d86f504..c4a5228 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/order/OrderStateFragment.kt
@@ -26,6 +26,7 @@ import androidx.recyclerview.selection.SelectionPredicates
import androidx.recyclerview.selection.SelectionTracker
import androidx.recyclerview.selection.StorageStrategy
import androidx.recyclerview.widget.LinearLayoutManager
+import net.taler.common.Amount
import net.taler.common.fadeIn
import net.taler.common.fadeOut
import net.taler.merchantpos.MainViewModel
@@ -82,11 +83,11 @@ class OrderStateFragment : Fragment() {
liveOrder.selectOrderLine(item)
}
})
- liveOrder.order.observe(viewLifecycleOwner, { order ->
+ liveOrder.order.observe(viewLifecycleOwner) { order ->
if (order == null) return@observe
onOrderChanged(order, tracker)
- })
- liveOrder.orderTotal.observe(viewLifecycleOwner, { orderTotal ->
+ }
+ liveOrder.orderTotal.observe(viewLifecycleOwner) { orderTotal: Amount ->
if (orderTotal.isZero()) {
ui.totalView.fadeOut()
ui.totalView.text = null
@@ -94,7 +95,7 @@ class OrderStateFragment : Fragment() {
ui.totalView.text = getString(R.string.order_total, orderTotal)
ui.totalView.fadeIn()
}
- })
+ }
}
override fun onSaveInstanceState(outState: Bundle) {
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt
index 98161db..d703a31 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/payment/PaymentManager.kt
@@ -26,8 +26,8 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
+import net.taler.common.Duration
import net.taler.common.assertUiThread
-import net.taler.lib.common.Duration
import net.taler.merchantlib.CheckPaymentResponse
import net.taler.merchantlib.MerchantApi
import net.taler.merchantlib.PostOrderRequest
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundFragment.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundFragment.kt
index bb98dbd..03c786f 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundFragment.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundFragment.kt
@@ -24,12 +24,12 @@ import androidx.annotation.StringRes
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
+import net.taler.common.Amount
+import net.taler.common.AmountParserException
import net.taler.common.fadeIn
import net.taler.common.fadeOut
import net.taler.common.navigate
import net.taler.common.showError
-import net.taler.lib.common.Amount
-import net.taler.lib.common.AmountParserException
import net.taler.merchantlib.OrderHistoryEntry
import net.taler.merchantpos.MainViewModel
import net.taler.merchantpos.R
diff --git a/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundManager.kt b/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundManager.kt
index 8b3efca..849dbaa 100644
--- a/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundManager.kt
+++ b/merchant-terminal/src/main/java/net/taler/merchantpos/refund/RefundManager.kt
@@ -21,8 +21,8 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
+import net.taler.common.Amount
import net.taler.common.assertUiThread
-import net.taler.lib.common.Amount
import net.taler.merchantlib.MerchantApi
import net.taler.merchantlib.OrderHistoryEntry
import net.taler.merchantlib.RefundRequest
diff --git a/merchant-terminal/src/main/res/values-de/strings.xml b/merchant-terminal/src/main/res/values-de/strings.xml
index 6d7730a..8b2744b 100644
--- a/merchant-terminal/src/main/res/values-de/strings.xml
+++ b/merchant-terminal/src/main/res/values-de/strings.xml
@@ -18,37 +18,39 @@
<string name="config_error_network">Fehler: Es konnte keine Verbindung zum Konfigurationsserver hergestellt werden</string>
<string name="config_error_category">Fehler: Keine gültige Produktkategorie gefunden</string>
<string name="config_error_currency">Fehler: Produkt %1$s hat die Währung %2$s, aber %3$s wird erwartet</string>
- <string name="config_error_product_category_id">Fehler: Produkt %1$s verweist auf unbekannte Kategorie ID %2$d</string>
+ <string name="config_error_product_category_id">Fehler: Produkt %1$s verweist auf unbekannte Kategorie %2$d</string>
<string name="config_error_product_zero">Fehler: Keine gültigen Produkte gefunden</string>
<string name="config_fetching">Konfiguration wird abgerufen…</string>
<string name="config_save_password">Passwort merken</string>
<string name="config_forget_password">Vergessen</string>
<string name="config_fetching_label">Konfiguration wird abgerufen</string>
- <string name="payment_intro_nfc">Bitte zum Bezahlen den QR-Code scannen oder per NFC empfangen</string>
- <string name="payment_intro">Bitte scannen Sie den QR-Code um zu bezahlen</string>
+ <string name="payment_intro_nfc">Bitte zum Bezahlen den QR-Code scannen oder per NFC empfangen.</string>
+ <string name="payment_intro">Zum Bezahlen scannen Sie bitte den QR-Code.</string>
<string name="payment_cancel">Bezahlvorgang abbrechen</string>
<string name="payment_received">Zahlung erhalten</string>
<string name="payment_back_button">Weiter</string>
<string name="payment_process_label">Kundenzahlung erforderlich</string>
<string name="payment_canceled">Die Zahlung wurde abgebrochen</string>
- <string name="history_label">Zahlungsverlauf</string>
+ <string name="history_label">Bisher getätigte Zahlungen</string>
<string name="refund_amount">Betrag</string>
<string name="refund_reason">Rückerstattungsgrund</string>
<string name="refund_abort">Abbrechen</string>
- <string name="refund_confirm">Rückerstattung geben</string>
+ <string name="refund_confirm">Rückerstattung bestätigen</string>
<string name="refund_error_zero">Der Betrag muss positiv sein</string>
<string name="refund_error_deadline">Die Rückerstattungsfrist ist abgelaufen</string>
- <string name="refund_intro">Bitte scannen Sie den QR-Code, um eine Rückerstattung zu erhalten</string>
- <string name="refund_order_ref">Bestellreferenz: %1$s\n\n%2$s</string>
- <string name="toast_back_to_exit">Klicken Sie zum Beenden erneut auf «zurück»</string>
+ <string name="refund_intro">Zur Rückerstattung lassen Sie bitte den QR-Code einscannen</string>
+ <string name="refund_order_ref">Referenznummer des Kaufs: %1$s
+\n
+\n%2$s</string>
+ <string name="toast_back_to_exit">Klicken Sie zum Beenden erneut auf «Zurück»</string>
<string name="app_name">Taler Merchant Kassenterminal</string>
<string name="config_label">Händlereinstellungen</string>
<string name="config_username">Benutzername</string>
<string name="config_password">Passwort</string>
- <string name="config_error_malformed">Fehler: Die JSON Konfiguration ist fehlerhaft</string>
+ <string name="config_error_malformed">Fehler: Die JSON-Konfiguration ist fehlerhaft</string>
<string name="config_error_unknown">Fehler: Ungültige Konfiguration</string>
<string name="config_docs">Informationen zum Konfigurationsformat finden Sie in <a href="https://docs.taler.net/taler-merchant-pos-terminal.html#apis-and-data-formats">der Dokumentation</a>.</string>
- <string name="refund_error_max_amount">Größer als die Zahlung der Bestellung von %s</string>
+ <string name="refund_error_max_amount">Größer als Bestellmenge von %s</string>
<string name="refund_error_backend">Fehler bei der Verarbeitung der Rückerstattung</string>
<string name="refund_intro_nfc">Bitte scannen Sie den QR-Code oder verwenden Sie NFC, um eine Rückerstattung zu erhalten</string>
<string name="config_changed">Neuer Händler für %s aktiviert</string>
@@ -57,9 +59,10 @@
<string name="refund_error_invalid_amount">Ungültiger Betrag</string>
<string name="refund_error_already_refunded">Bereits erstattet</string>
<string name="payment_order_id">Beleg #%s</string>
- <string name="history_unpaid">Unbezahlt</string>
- <string name="error_cancelled">Zahlvorgang abgebrochen</string>
+ <string name="history_unpaid">Noch nicht bezahlt</string>
+ <string name="error_cancelled">Bezahlvorgang abgebrochen</string>
<string name="error_payment">Fehler: Keine Zahlung erhalten</string>
- <string name="error_timeout">Keine Zahlung innerhalb des Zahlungszeitraums, bitte versuchen Sie es erneut!</string>
+ <string name="error_timeout">Keine Zahlung innerhalb des vorgegebenen Zeitraums erhalten, bitte versuchen Sie es erneut!</string>
<string name="error_history">Fehler beim Abrufen des Bestellverlaufs</string>
+ <string name="payment_claimed">Warten auf Zahlungsbestätigung durch Kunden…</string>
</resources> \ No newline at end of file
diff --git a/merchant-terminal/src/main/res/values-es/strings.xml b/merchant-terminal/src/main/res/values-es/strings.xml
index 7fcd835..1ab7d92 100644
--- a/merchant-terminal/src/main/res/values-es/strings.xml
+++ b/merchant-terminal/src/main/res/values-es/strings.xml
@@ -64,4 +64,5 @@
<string name="config_docs">Por favor refiérete &lt;a href=\"https://docs.taler.net/taler-merchant-pos-terminal.html#apis-and-data-formats\"&gt;a la documentación&lt;/a&gt; para la configuración del formato.<a href="https://docs.taler.net/taler-merchant-pos-terminal.html#apis-and-data-formats">the documentation</a> for the configuration format.</string>
<string name="refund_error_max_amount">Mayor que la cantidad del pedido de %s</string>
<string name="refund_intro_nfc">Por favor permite al cliente escanear el código QR o usar NFC para ofrecer reembolso</string>
+ <string name="payment_claimed">A la espera de que el cliente confirme el pago…</string>
</resources> \ No newline at end of file
diff --git a/merchant-terminal/src/main/res/values-fr/strings.xml b/merchant-terminal/src/main/res/values-fr/strings.xml
index 10c7af5..7c1bb84 100644
--- a/merchant-terminal/src/main/res/values-fr/strings.xml
+++ b/merchant-terminal/src/main/res/values-fr/strings.xml
@@ -59,4 +59,10 @@
\n
\n%2$s</string>
<string name="toast_back_to_exit">Cliquez à nouveau sur «retour» pour quitter</string>
+ <string name="error_payment">Erreur : Aucun paiement reçu</string>
+ <string name="history_unpaid">Non payé</string>
+ <string name="payment_claimed">En attendant que le client confirme le paiement…</string>
+ <string name="error_cancelled">Paiement annulé</string>
+ <string name="error_history">Erreur d\'accès à l\'historique des commandes</string>
+ <string name="error_timeout">Aucun paiement n\'a été effectué pendant la période de paiement, veuillez réessayer !</string>
</resources> \ No newline at end of file
diff --git a/merchant-terminal/src/main/res/values-sv/strings.xml b/merchant-terminal/src/main/res/values-sv/strings.xml
index b46c560..a6a7c17 100644
--- a/merchant-terminal/src/main/res/values-sv/strings.xml
+++ b/merchant-terminal/src/main/res/values-sv/strings.xml
@@ -64,4 +64,5 @@
<string name="refund_order_ref">Inköpsreferens: %1$s
\n
\n%2$s</string>
+ <string name="payment_claimed">Väntar på att kunden ska bekräfta betalningen…</string>
</resources> \ No newline at end of file
diff --git a/merchant-terminal/src/main/res/values-tr/strings.xml b/merchant-terminal/src/main/res/values-tr/strings.xml
new file mode 100644
index 0000000..a6362ca
--- /dev/null
+++ b/merchant-terminal/src/main/res/values-tr/strings.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="config_username">Kullanıcı adı</string>
+ <string name="config_password">Şifre</string>
+ <string name="refund_amount">Miktar</string>
+ <string name="refund_reason">Geri ödeme nedeni</string>
+ <string name="refund_abort">Durdur</string>
+ <string name="refund_complete">Alındı</string>
+ <string name="refund_error_max_amount">%s sipariş miktarından büyük</string>
+ <string name="refund_error_invalid_amount">Geçersiz miktar</string>
+ <string name="refund_error_zero">Pozitif miktar olması gerekiyor</string>
+ <string name="refund_error_backend">Geri ödeme işlenirken hata oluştu</string>
+ <string name="refund_error_already_refunded">Çoktan geri ödendi</string>
+ <string name="refund_intro">Geri ödeme sunmak için lütfen müşterinin QR Kodunu taramasına izin verin</string>
+ <string name="refund_order_ref">Satın alma referansı %1$s
+\n
+\n%2$s</string>
+ <string name="error_payment">Hata: Ödeme alınmadı</string>
+ <string name="error_timeout">Ödeme süresi içinde ödeme yapılmadı, lütfen tekrar deneyin!</string>
+ <string name="error_cancelled">Ödeme iptal edildi</string>
+ <string name="error_history">Sipariş geçmişi getirilirken hata oluştu</string>
+ <string name="config_forget_password">Unut</string>
+ <string name="config_auth_error">Hata! Geçersiz kullanıcı adı veya şifre</string>
+ <string name="config_error_malformed">Hata: JSON konfigürasyonu hatalı oluşturulmuş</string>
+ <string name="config_error_unknown">Hata: Geçersiz kurulum</string>
+ <string name="history_refund">Geri ödeme</string>
+ <string name="refund_confirm">Geri ödemeyi onayla</string>
+ <string name="refund_error_deadline">Geri ödeme süresi geçti</string>
+ <string name="refund_intro_nfc">Geri ödeme sunmak için lütfen müşterinin QR Kodunu taramasına veya NFC\'yi kullanmasına izin verin</string>
+ <string name="toast_back_to_exit">Çıkmak için tekrar «geri» yi tıklayın</string>
+ <string name="config_changed">%s kullanılarak yeni satıcı olarak değiştirildi</string>
+ <string name="config_fetching_label">Kurulum getiriliyor</string>
+ <string name="config_docs">Lütfen kurulum formatı için <a href="https://docs.taler.net/taler-merchant-pos-terminal.html#apis-and-data-formats">belgelere</a> bakın.</string>
+ <string name="payment_intro_nfc">Lütfen müşterinin QR Kodunu taramasına veya ödeme yapmak için NFC\'yi kullanmasına izin verin.</string>
+ <string name="payment_intro">Lütfen müşterinin ödeme yapmak için QR Kodunu taramasına izin verin.</string>
+ <string name="payment_claimed">Müşterinin ödemeyi onaylaması bekleniyor…</string>
+ <string name="payment_cancel">Ödemeyi iptal et</string>
+ <string name="payment_received">Ödeme alındı</string>
+ <string name="payment_back_button">Devam et</string>
+ <string name="payment_order_id">Makbuz #%s</string>
+ <string name="payment_process_label">Ödeme gerekli</string>
+ <string name="payment_canceled">Ödeme iptal edildi</string>
+ <string name="history_label">Ödeme geçmişi</string>
+ <string name="history_unpaid">Ödenmemiş</string>
+ <string name="app_name">GNU Taler Satış Noktası</string>
+ <string name="app_name_short">Ticari Terminal</string>
+ <string name="project_name">GNU Taler</string>
+ <string name="menu_order">Siparişler</string>
+ <string name="menu_settings">Ayarlar</string>
+ <string name="order_label_title">Sipariş #%s</string>
+ <string name="order_total">Toplam: %s</string>
+ <string name="order_restart">Tekrar başlat</string>
+ <string name="order_undo">Geri al</string>
+ <string name="order_previous">Önceki</string>
+ <string name="order_next">Sonraki</string>
+ <string name="order_complete">Tamam</string>
+ <string name="config_label">Satıcı ayarları</string>
+ <string name="config_url">Konfigürasyon URL\'i</string>
+ <string name="config_ok">Konfigürasyonu getir</string>
+ <string name="config_error_network">Hata: Konfigürasyon sunucusuna bağlanılamadı</string>
+ <string name="config_error_category">Hata: Geçerli ürün kategorisi bulunamadı</string>
+ <string name="config_error_currency">"Hata: %1$s ürününün para birimi %2$s ama %3$s bekleniyor"</string>
+ <string name="config_error_product_category_id">Hata: Ürün %1$s, bilinmeyen kategori kimliğine referans veriyor %2$d</string>
+ <string name="config_error_product_zero">Hata: Geçerli ürün bulunamadı</string>
+ <string name="config_fetching">Kurulum getiriliyor…</string>
+ <string name="config_save_password">Şifreyi hatırla</string>
+ <string name="menu_history">Geçmiş</string>
+</resources> \ No newline at end of file
diff --git a/merchant-terminal/src/test/java/net/taler/merchantpos/order/OrderManagerTest.kt b/merchant-terminal/src/test/java/net/taler/merchantpos/order/OrderManagerTest.kt
index ca48b6e..bb8dcb7 100644
--- a/merchant-terminal/src/test/java/net/taler/merchantpos/order/OrderManagerTest.kt
+++ b/merchant-terminal/src/test/java/net/taler/merchantpos/order/OrderManagerTest.kt
@@ -20,7 +20,7 @@ import android.app.Application
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.runBlocking
-import net.taler.lib.common.Amount
+import net.taler.common.Amount
import net.taler.merchantlib.MerchantConfig
import net.taler.merchantpos.R
import net.taler.merchantpos.config.Category
diff --git a/multiplatform b/multiplatform
deleted file mode 160000
-Subproject 6f698f4d0ef580a898d3b03e9e8954af4f19403
diff --git a/taler-kotlin-android/build.gradle b/taler-kotlin-android/build.gradle
index 4661b09..bbdcd92 100644
--- a/taler-kotlin-android/build.gradle
+++ b/taler-kotlin-android/build.gradle
@@ -21,15 +21,12 @@ plugins {
}
android {
- compileSdkVersion 31
- //noinspection GradleDependency
+ compileSdkVersion 32
buildToolsVersion "$build_tools_version"
defaultConfig {
minSdkVersion 21
- targetSdkVersion 30
- versionCode 1
- versionName "0.1"
+ targetSdkVersion 32
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
@@ -49,18 +46,19 @@ android {
buildFeatures {
viewBinding = true
}
-
packagingOptions {
- exclude("META-INF/*.kotlin_module")
+ resources {
+ excludes += ['META-INF/*.kotlin_module']
+ }
}
+ namespace 'net.taler.common'
+
}
dependencies {
- api project(":multiplatform:common")
-
- implementation 'androidx.appcompat:appcompat:1.3.1'
- implementation 'androidx.core:core-ktx:1.6.0'
+ implementation 'androidx.appcompat:appcompat:1.4.1'
+ implementation 'androidx.core:core-ktx:1.7.0'
implementation "androidx.constraintlayout:constraintlayout:$constraintlayout_version"
// Navigation
@@ -71,20 +69,13 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// QR codes
- implementation 'com.google.zxing:core:3.4.0' // needs minSdkVersion 24+ or desugar
-
- // Logcat viewer
- implementation('com.github.pedrovgs:lynx:1.1.0') {
- exclude group: 'com.android.support'
- exclude group: 'com.squareup'
- }
- implementation 'com.github.pedrovgs:renderers:4.0.0'
+ implementation 'com.google.zxing:core:3.5.0' // needs minSdkVersion 24+ or desugar
// JSON parsing and serialization
- api "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.2"
+ api 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3'
- lintPublish 'com.github.thirdegg:lint-rules:0.0.6-beta'
+ api 'com.github.thirdegg:lint-rules:0.1.0'
testImplementation "junit:junit:$junit_version"
- testImplementation 'org.json:json:20200518'
+ testImplementation 'org.json:json:20220320'
}
diff --git a/taler-kotlin-android/src/main/AndroidManifest.xml b/taler-kotlin-android/src/main/AndroidManifest.xml
index f74aae6..5533913 100644
--- a/taler-kotlin-android/src/main/AndroidManifest.xml
+++ b/taler-kotlin-android/src/main/AndroidManifest.xml
@@ -15,13 +15,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
package="net.taler.common">
- <uses-sdk tools:overrideLibrary="com.google.zxing.client.android" />
-
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
- <uses-permission android:name="android.permission.INTERNET" />
- <uses-permission android:name="android.permission.NFC" />
-
</manifest>
diff --git a/taler-kotlin-android/src/main/java/net/taler/common/Amount.kt b/taler-kotlin-android/src/main/java/net/taler/common/Amount.kt
new file mode 100644
index 0000000..18fb6cb
--- /dev/null
+++ b/taler-kotlin-android/src/main/java/net/taler/common/Amount.kt
@@ -0,0 +1,210 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.common
+
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.Serializer
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import kotlin.math.floor
+import kotlin.math.pow
+import kotlin.math.roundToInt
+
+public class AmountParserException(msg: String? = null, cause: Throwable? = null) : Exception(msg, cause)
+public class AmountOverflowException(msg: String? = null, cause: Throwable? = null) : Exception(msg, cause)
+
+@Serializable(with = KotlinXAmountSerializer::class)
+public data class Amount(
+ /**
+ * name of the currency using either a three-character ISO 4217 currency code,
+ * or a regional currency identifier starting with a "*" followed by at most 10 characters.
+ * ISO 4217 exponents in the name are not supported,
+ * although the "fraction" is corresponds to an ISO 4217 exponent of 6.
+ */
+ val currency: String,
+
+ /**
+ * The integer part may be at most 2^52.
+ * Note that "1" here would correspond to 1 EUR or 1 USD, depending on currency, not 1 cent.
+ */
+ val value: Long,
+
+ /**
+ * Unsigned 32 bit fractional value to be added to value representing
+ * an additional currency fraction, in units of one hundred millionth (1e-8)
+ * of the base currency value. For example, a fraction
+ * of 50_000_000 would correspond to 50 cents.
+ */
+ val fraction: Int
+) : Comparable<Amount> {
+
+ public companion object {
+
+ private const val FRACTIONAL_BASE: Int = 100000000 // 1e8
+ public val SEGWIT_MIN = Amount("BTC", 0, 294)
+
+ private val REGEX_CURRENCY = Regex("""^[-_*A-Za-z0-9]{1,12}$""")
+ public val MAX_VALUE: Long = 2.0.pow(52).toLong()
+ private const val MAX_FRACTION_LENGTH = 8
+ public const val MAX_FRACTION: Int = 99_999_999
+
+ public fun fromDouble(currency: String, value: Double): Amount {
+ val intPart = Math.floor(value).toLong()
+ val fraPart = Math.floor((value - intPart) * FRACTIONAL_BASE).toInt()
+ return Amount(currency, intPart, fraPart)
+ }
+
+ public fun zero(currency: String): Amount {
+ return Amount(checkCurrency(currency), 0, 0)
+ }
+
+ public fun fromJSONString(str: String): Amount {
+ val split = str.split(":")
+ if (split.size != 2) throw AmountParserException("Invalid Amount Format")
+ return fromString(split[0], split[1])
+ }
+
+ public fun fromString(currency: String, str: String): Amount {
+ // value
+ val valueSplit = str.split(".")
+ val value = checkValue(valueSplit[0].toLongOrNull())
+ // fraction
+ val fraction: Int = if (valueSplit.size > 1) {
+ val fractionStr = valueSplit[1]
+ if (fractionStr.length > MAX_FRACTION_LENGTH)
+ throw AmountParserException("Fraction $fractionStr too long")
+ val fraction = "0.$fractionStr".toDoubleOrNull()
+ ?.times(FRACTIONAL_BASE)
+ ?.roundToInt()
+ checkFraction(fraction)
+ } else 0
+ return Amount(checkCurrency(currency), value, fraction)
+ }
+
+ public fun min(currency: String): Amount = Amount(currency, 0, 1)
+ public fun max(currency: String): Amount = Amount(currency, MAX_VALUE, MAX_FRACTION)
+
+
+ internal fun checkCurrency(currency: String): String {
+ if (!REGEX_CURRENCY.matches(currency))
+ throw AmountParserException("Invalid currency: $currency")
+ return currency
+ }
+
+ internal fun checkValue(value: Long?): Long {
+ if (value == null || value > MAX_VALUE)
+ throw AmountParserException("Value $value greater than $MAX_VALUE")
+ return value
+ }
+
+ internal fun checkFraction(fraction: Int?): Int {
+ if (fraction == null || fraction > MAX_FRACTION)
+ throw AmountParserException("Fraction $fraction greater than $MAX_FRACTION")
+ return fraction
+ }
+
+ }
+
+ public val amountStr: String
+ get() = if (fraction == 0) "$value" else {
+ var f = fraction
+ var fractionStr = ""
+ while (f > 0) {
+ fractionStr += f / (FRACTIONAL_BASE / 10)
+ f = (f * 10) % FRACTIONAL_BASE
+ }
+ "$value.$fractionStr"
+ }
+
+ public operator fun plus(other: Amount): Amount {
+ check(currency == other.currency) { "Can only subtract from same currency" }
+ val resultValue = value + other.value + floor((fraction + other.fraction).toDouble() / FRACTIONAL_BASE).toLong()
+ if (resultValue > MAX_VALUE)
+ throw AmountOverflowException()
+ val resultFraction = (fraction + other.fraction) % FRACTIONAL_BASE
+ return Amount(currency, resultValue, resultFraction)
+ }
+
+ public operator fun times(factor: Int): Amount {
+ // TODO consider replacing with a faster implementation
+ if (factor == 0) return zero(currency)
+ var result = this
+ for (i in 1 until factor) result += this
+ return result
+ }
+
+ public fun withCurrency(currency: String): Amount {
+ return Amount(checkCurrency(currency), this.value, this.fraction)
+ }
+
+ public operator fun minus(other: Amount): Amount {
+ check(currency == other.currency) { "Can only subtract from same currency" }
+ var resultValue = value
+ var resultFraction = fraction
+ if (resultFraction < other.fraction) {
+ if (resultValue < 1L)
+ throw AmountOverflowException()
+ resultValue--
+ resultFraction += FRACTIONAL_BASE
+ }
+ check(resultFraction >= other.fraction)
+ resultFraction -= other.fraction
+ if (resultValue < other.value)
+ throw AmountOverflowException()
+ resultValue -= other.value
+ return Amount(currency, resultValue, resultFraction)
+ }
+
+ public fun isZero(): Boolean {
+ return value == 0L && fraction == 0
+ }
+
+ public fun toJSONString(): String {
+ return "$currency:$amountStr"
+ }
+
+ override fun toString(): String {
+ return "$amountStr $currency"
+ }
+
+ override fun compareTo(other: Amount): Int {
+ check(currency == other.currency) { "Can only compare amounts with the same currency" }
+ when {
+ value == other.value -> {
+ if (fraction < other.fraction) return -1
+ if (fraction > other.fraction) return 1
+ return 0
+ }
+ value < other.value -> return -1
+ else -> return 1
+ }
+ }
+
+}
+
+@Suppress("EXPERIMENTAL_API_USAGE")
+@Serializer(forClass = Amount::class)
+internal object KotlinXAmountSerializer : KSerializer<Amount> {
+ override fun serialize(encoder: Encoder, value: Amount) {
+ encoder.encodeString(value.toJSONString())
+ }
+
+ override fun deserialize(decoder: Decoder): Amount {
+ return Amount.fromJSONString(decoder.decodeString())
+ }
+}
diff --git a/taler-kotlin-android/src/main/java/net/taler/common/AndroidUtils.kt b/taler-kotlin-android/src/main/java/net/taler/common/AndroidUtils.kt
index 4ac2e73..7dde872 100644
--- a/taler-kotlin-android/src/main/java/net/taler/common/AndroidUtils.kt
+++ b/taler-kotlin-android/src/main/java/net/taler/common/AndroidUtils.kt
@@ -16,6 +16,7 @@
package net.taler.common
+import android.Manifest.permission.ACCESS_NETWORK_STATE
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Context.CONNECTIVITY_SERVICE
@@ -40,16 +41,14 @@ import android.view.View
import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.view.inputmethod.InputMethodManager
+import androidx.annotation.RequiresPermission
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat.getSystemService
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.navigation.NavDirections
import androidx.navigation.fragment.findNavController
-import com.github.pedrovgs.lynx.LynxActivity
-import com.github.pedrovgs.lynx.LynxConfig
import net.taler.lib.android.ErrorBottomSheet
-import net.taler.lib.common.Version
fun View.fadeIn(endAction: () -> Unit = {}) {
if (visibility == VISIBLE && alpha == 1f) return
@@ -85,6 +84,7 @@ fun assertUiThread() {
val <T> T.exhaustive: T
get() = this
+@RequiresPermission(ACCESS_NETWORK_STATE)
fun Context.isOnline(): Boolean {
val cm = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
return if (SDK_INT < 29) {
@@ -96,15 +96,6 @@ fun Context.isOnline(): Boolean {
}
}
-fun Context.showLogViewer(logFilter: String? = null) {
- val lynxActivityIntent = LynxActivity.getIntent(this, LynxConfig().apply {
- maxNumberOfTracesToShow = 1500 // higher numbers seem to break share functionality
- samplingRate = Int.MAX_VALUE // no updates please
- logFilter?.let { filter = it }
- })
- startActivity(lynxActivityIntent)
-}
-
fun FragmentActivity.showError(mainText: String, detailText: String = "") = ErrorBottomSheet
.newInstance(mainText, detailText)
.show(supportFragmentManager, "ERROR_BOTTOM_SHEET")
diff --git a/taler-kotlin-android/src/main/java/net/taler/common/Bech32.kt b/taler-kotlin-android/src/main/java/net/taler/common/Bech32.kt
new file mode 100644
index 0000000..32885df
--- /dev/null
+++ b/taler-kotlin-android/src/main/java/net/taler/common/Bech32.kt
@@ -0,0 +1,257 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+// Copyright (c) 2020 Figure Technologies Inc.
+// The contents of this file were derived from an implementation
+// by the btcsuite developers https://github.com/btcsuite/btcutil.
+
+// Copyright (c) 2017 The btcsuite developers
+// Use of this source code is governed by an ISC
+// license that can be found in the LICENSE file.
+
+// modified version of https://gist.github.com/iramiller/4ebfcdfbc332a9722c4a4abeb4e16454
+
+import net.taler.common.CyptoUtils
+import kotlin.experimental.and
+import kotlin.experimental.or
+
+
+infix fun Int.min(b: Int): Int = b.takeIf { this > b } ?: this
+infix fun UByte.shl(bitCount: Int) = ((this.toInt() shl bitCount) and 0xff).toUByte()
+infix fun UByte.shr(bitCount: Int) = (this.toInt() shr bitCount).toUByte()
+
+/**
+ * Given an array of bytes, associate an HRP and return a Bech32Data instance.
+ */
+fun ByteArray.toBech32Data(hrp: String) =
+ Bech32Data(hrp, Bech32.convertBits(this, 8, 5, true))
+
+/**
+ * Using a string in bech32 encoded address format, parses out and returns a Bech32Data instance
+ */
+fun String.toBech32Data() = Bech32.decode(this)
+
+/**
+ * Bech32 Data encoding instance containing data for encoding as well as a human readable prefix
+ */
+data class Bech32Data(val hrp: String, val fiveBitData: ByteArray) {
+
+ /**
+ * The encapsulated data as typical 8bit bytes.
+ */
+ val data = Bech32.convertBits(fiveBitData, 5, 8, false)
+
+ /**
+ * Address is the Bech32 encoded value of the data prefixed with the human readable portion and
+ * protected by an appended checksum.
+ */
+ val address = Bech32.encode(hrp, fiveBitData)
+
+ /**
+ * Checksum for encapsulated data + hrp
+ */
+ val checksum = Bech32.checksum(this.hrp, this.fiveBitData.toTypedArray())
+
+ /**
+ * The Bech32 Address toString prints state information for debugging purposes.
+ * @see address() for the bech32 encoded address string output.
+ */
+ override fun toString(): String {
+ return "bech32 : ${this.address}\nhuman: ${this.hrp} \nbytes"
+ }
+}
+
+/**
+ * BIP173 compliant processing functions for handling Bech32 encoding for addresses
+ */
+class Bech32 {
+
+ companion object {
+ const val CHECKSUM_SIZE = 6
+ const val MIN_VALID_LENGTH = 8
+ const val MAX_VALID_LENGTH = 90
+ const val MIN_VALID_CODEPOINT = 33
+ const val MAX_VALID_CODEPOINT = 126
+
+ const val charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
+ val gen = intArrayOf(0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3)
+
+ fun generateFakeSegwitAddress(reservePub: String?, addr: String): List<String> {
+ if (reservePub == null || reservePub.isEmpty()) return listOf<String>()
+ val pub = CyptoUtils.decodeCrock(reservePub)
+ if (pub.size != 32) return listOf()
+
+ val first_rnd = pub.copyOfRange(0,4)
+ val second_rnd = pub.copyOfRange(0,4)
+
+ first_rnd[0] = first_rnd[0].and(0b0111_1111);
+ second_rnd[0] = second_rnd[0].or(0b1000_0000.toByte());
+
+ val first_part = ByteArray(20);
+ first_rnd.copyInto( first_part, 0, 0, 4)
+ pub.copyInto( first_part, 4, 0, 16)
+
+ val second_part = ByteArray(20);
+ second_rnd.copyInto( second_part, 0, 0, 4)
+ pub.copyInto( second_part, 4, 16, 32)
+
+ val zero = ByteArray(1)
+ zero[0] = 0
+ val hrp = when {
+ addr[0] == 'b' && addr[1] == 'c' && addr[2] == 'r' && addr[3] == 't' -> "bcrt"
+ addr[0] == 't' && addr[1] == 'b' -> "tb"
+ addr[0] == 'b' && addr[1] == 'c' -> "bc"
+ else -> throw Error("unknown bitcoin net")
+ }
+
+ return listOf(
+ Bech32Data(hrp, zero + convertBits(first_part, 8, 5, true)).address,
+ Bech32Data(hrp, zero + convertBits(second_part, 8, 5, true)).address,
+ )
+ }
+
+ /**
+ * Decodes a Bech32 String
+ */
+ fun decode(bech32: String): Bech32Data {
+ require(bech32.length >= MIN_VALID_LENGTH && bech32.length <= MAX_VALID_LENGTH) { "invalid bech32 string length" }
+ require(bech32.toCharArray().none { c -> c.toInt() < MIN_VALID_CODEPOINT || c.toInt() > MAX_VALID_CODEPOINT })
+ { "invalid character in bech32: ${bech32.toCharArray().map { c -> c.toInt() }
+ .filter { c -> c.toInt() < MIN_VALID_CODEPOINT || c.toInt() > MAX_VALID_CODEPOINT }}" }
+
+ require(bech32.equals(bech32.toLowerCase()) || bech32.equals(bech32.toUpperCase()))
+ { "bech32 must be either all upper or lower case" }
+ require(bech32.substring(1).dropLast(CHECKSUM_SIZE).contains('1')) { "invalid index of '1'" }
+
+ val hrp = bech32.substringBeforeLast('1').toLowerCase()
+ val dataString = bech32.substringAfterLast('1').toLowerCase()
+
+ require(dataString.toCharArray().all { c -> charset.contains(c) }) { "invalid data encoding character in bech32"}
+
+ val dataBytes = dataString.map { c -> charset.indexOf(c).toByte() }.toByteArray()
+ val checkBytes = dataString.takeLast(CHECKSUM_SIZE).map { c -> charset.indexOf(c).toByte() }.toByteArray()
+
+ val actualSum = checksum(hrp, dataBytes.dropLast(CHECKSUM_SIZE).toTypedArray())
+ require(1 == polymod(expandHrp(hrp).plus(dataBytes.map { d -> d.toInt() }))) { "checksum failed: $checkBytes != $actualSum" }
+
+ return Bech32Data(hrp, dataBytes.dropLast(CHECKSUM_SIZE).toByteArray())
+ }
+
+ /**
+ * ConvertBits regroups bytes with toBits set based on reading groups of bits as a continuous stream group by fromBits.
+ * This process is used to convert from base64 (from 8) to base32 (to 5) or the inverse.
+ */
+ fun convertBits(data: ByteArray, fromBits: Int, toBits: Int, pad: Boolean): ByteArray {
+ require (fromBits in 1..8 && toBits in 1..8) { "only bit groups between 1 and 8 are supported"}
+
+ // resulting bytes with each containing the toBits bits from the input set.
+ var regrouped = arrayListOf<Byte>()
+
+ var nextByte = 0.toUByte()
+ var filledBits = 0
+
+ data.forEach { d ->
+ // discard unused bits.
+ var b = (d.toUByte() shl (8 - fromBits))
+
+ // How many bits remain to extract from input data.
+ var remainFromBits = fromBits
+
+ while (remainFromBits > 0) {
+ // How many bits remain to be copied in
+ val remainToBits = toBits - filledBits
+
+ // we extract the remaining bits unless that is more than we need.
+ val toExtract = remainFromBits.takeUnless { remainToBits < remainFromBits } ?: remainToBits
+ check(toExtract >= 0) { "extract should be positive"}
+
+ // move existing bits to the left to make room for bits toExtract, copy in bits to extract
+ nextByte = (nextByte shl toExtract) or (b shr (8 - toExtract))
+
+ // discard extracted bits and update position counters
+ b = b shl toExtract
+ remainFromBits -= toExtract
+ filledBits += toExtract
+
+ // if we have a complete group then reset.
+ if (filledBits == toBits) {
+ regrouped.add(nextByte.toByte())
+ filledBits = 0
+ nextByte = 0.toUByte()
+ }
+ }
+ }
+
+ // pad any unfinished groups as required
+ if (pad && filledBits > 0) {
+ nextByte = nextByte shl (toBits - filledBits)
+ regrouped.add(nextByte.toByte())
+ filledBits = 0
+ nextByte = 0.toUByte()
+ }
+
+ return regrouped.toByteArray()
+ }
+
+ /**
+ * Encodes data 5-bit bytes (data) with a given human readable portion (hrp) into a bech32 string.
+ * @see convertBits for conversion or ideally use the Bech32Data extension functions
+ */
+ fun encode(hrp: String, fiveBitData: ByteArray): String {
+ return (fiveBitData.plus(checksum(hrp, fiveBitData.toTypedArray()))
+ .map { b -> charset[b.toInt()] }).joinToString("", hrp + "1")
+ }
+
+ /**
+ * Calculates a bech32 checksum based on BIP 173 specification
+ */
+ fun checksum(hrp: String, data: Array<Byte>): ByteArray {
+ var values = expandHrp(hrp)
+ .plus(data.map { d -> d.toInt() })
+ .plus(Array<Int>(6){ _ -> 0}.toIntArray())
+
+ var poly = polymod(values) xor 1
+
+ return (0..5).map {
+ ((poly shr (5 * (5-it))) and 31).toByte()
+ }.toByteArray()
+ }
+
+ /**
+ * Expands the human readable prefix per BIP173 for Checksum encoding
+ */
+ fun expandHrp(hrp: String) =
+ hrp.map { c -> c.toInt() shr 5 }
+ .plus(0)
+ .plus(hrp.map { c -> c.toInt() and 31 })
+ .toIntArray()
+
+ /**
+ * Polynomial division function for checksum calculation. For details see BIP173
+ */
+ fun polymod(values: IntArray): Int {
+ var chk = 1
+ return values.map {
+ var b = chk shr 25
+ chk = ((chk and 0x1ffffff) shl 5) xor it
+ (0..4).map {
+ if (((b shr it) and 1) == 1) {
+ chk = chk xor gen[it]
+ }
+ }
+ }.let { chk }
+ }
+ }
+} \ No newline at end of file
diff --git a/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt b/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt
index fb30692..910cc36 100644
--- a/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt
+++ b/taler-kotlin-android/src/main/java/net/taler/common/ContractTerms.kt
@@ -20,8 +20,6 @@ import android.os.Build
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.taler.common.TalerUtils.getLocalizedString
-import net.taler.lib.common.Amount
-import net.taler.lib.common.Timestamp
@Serializable
data class ContractTerms(
diff --git a/taler-kotlin-android/src/main/java/net/taler/common/CyptoUtils.kt b/taler-kotlin-android/src/main/java/net/taler/common/CyptoUtils.kt
new file mode 100644
index 0000000..c1fbe8c
--- /dev/null
+++ b/taler-kotlin-android/src/main/java/net/taler/common/CyptoUtils.kt
@@ -0,0 +1,71 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2022 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.common
+
+import kotlin.math.floor
+
+object CyptoUtils {
+ internal fun getValue(c: Char): Int {
+ val a = when (c) {
+ 'o','O' -> '0'
+ 'i','I','l','L' -> '1'
+ 'u','U' -> 'V'
+ else -> c
+ }
+ if (a in '0'..'9') {
+ return a - '0'
+ }
+ val A = if (a in 'a'..'z') a.uppercaseChar() else a
+ var dec = 0
+ if (A in 'A'..'Z') {
+ if ('I' < A) dec++
+ if ('L' < A) dec++
+ if ('O' < A) dec++
+ if ('U' < A) dec++
+ return A - 'A' + 10 - dec
+ }
+ throw Error("encoding error")
+ }
+
+ fun decodeCrock(e: String): ByteArray {
+ val size = e.length
+ var bitpos = 0
+ var bitbuf = 0
+ var readPosition = 0
+ val outLen = floor((size * 5f) / 8).toInt()
+ val out = ByteArray(outLen)
+ var outPos = 0
+ while (readPosition < size || bitpos > 0) {
+ if (readPosition < size) {
+ val v = getValue(e[readPosition++])
+ bitbuf = bitbuf.shl(5).or(v)
+ bitpos += 5
+ }
+ while (bitpos >= 8) {
+ val d = bitbuf.shr(bitpos -8).and(0xff).toByte()
+ out[outPos++] = d
+ bitpos -= 8
+ }
+ if (readPosition == size && bitpos > 0) {
+ bitbuf = bitbuf.shl( 8 - bitpos).and(0xff)
+ bitpos = if (bitbuf == 0) 0 else 8
+ }
+ }
+ return out
+ }
+
+} \ No newline at end of file
diff --git a/taler-kotlin-android/src/main/java/net/taler/common/TalerUri.kt b/taler-kotlin-android/src/main/java/net/taler/common/TalerUri.kt
new file mode 100644
index 0000000..a1b7225
--- /dev/null
+++ b/taler-kotlin-android/src/main/java/net/taler/common/TalerUri.kt
@@ -0,0 +1,62 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.common
+
+import java.util.Locale
+
+public object TalerUri {
+
+ private const val SCHEME = "taler://"
+ private const val SCHEME_INSECURE = "taler+http://"
+ private const val AUTHORITY_PAY = "pay"
+ private const val AUTHORITY_WITHDRAW = "withdraw"
+ private const val AUTHORITY_REFUND = "refund"
+ private const val AUTHORITY_TIP = "tip"
+
+ public data class WithdrawUriResult(
+ val bankIntegrationApiBaseUrl: String,
+ val withdrawalOperationId: String
+ )
+
+ /**
+ * Parses a withdraw URI and returns a bank status URL or null if the URI was invalid.
+ */
+ public fun parseWithdrawUri(uri: String): WithdrawUriResult? {
+ val (resultScheme, prefix) = when {
+ uri.startsWith(SCHEME, ignoreCase = true) -> {
+ Pair("https://", "${SCHEME}${AUTHORITY_WITHDRAW}/")
+ }
+ uri.startsWith(SCHEME_INSECURE, ignoreCase = true) -> {
+ Pair("http://", "${SCHEME_INSECURE}${AUTHORITY_WITHDRAW}/")
+ }
+ else -> return null
+ }
+ if (!uri.startsWith(prefix)) return null
+ val parts = uri.let {
+ (if (it.endsWith("/")) it.dropLast(1) else it).substring(prefix.length).split('/')
+ }
+ if (parts.size < 2) return null
+ val host = parts[0].lowercase(Locale.ROOT)
+ val pathSegments = parts.slice(1 until parts.size - 1).joinToString("/")
+ val withdrawId = parts.last()
+ if (withdrawId.isBlank()) return null
+ val url = "${resultScheme}${host}/${pathSegments}"
+
+ return WithdrawUriResult(url, withdrawId)
+ }
+
+}
diff --git a/taler-kotlin-android/src/main/java/net/taler/common/Time.kt b/taler-kotlin-android/src/main/java/net/taler/common/Time.kt
new file mode 100644
index 0000000..61bbce8
--- /dev/null
+++ b/taler-kotlin-android/src/main/java/net/taler/common/Time.kt
@@ -0,0 +1,129 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.common
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.builtins.serializer
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.JsonPrimitive
+import kotlinx.serialization.json.JsonTransformingSerializer
+import kotlinx.serialization.json.contentOrNull
+import kotlinx.serialization.json.jsonPrimitive
+import kotlinx.serialization.json.longOrNull
+import kotlin.math.max
+
+@Serializable
+data class Timestamp(
+ @SerialName("t_ms")
+ @Serializable(NeverSerializer::class)
+ val old_ms: Long? = null,
+ @SerialName("t_s")
+ @Serializable(NeverSerializer::class)
+ private val s: Long? = null,
+) : Comparable<Timestamp> {
+
+ constructor(ms: Long) : this(ms, null)
+
+ companion object {
+ private const val NEVER: Long = -1
+ fun now(): Timestamp = Timestamp(System.currentTimeMillis())
+ fun never(): Timestamp = Timestamp(NEVER)
+ }
+
+ val ms: Long = if (s != null) {
+ s * 1000L
+ } else if (old_ms !== null) {
+ old_ms
+ } else {
+ throw Exception("timestamp didn't have t_s or t_ms")
+ }
+
+
+ /**
+ * Returns a copy of this [Timestamp] rounded to seconds.
+ */
+ fun truncateSeconds(): Timestamp {
+ if (ms == NEVER) return Timestamp(ms)
+ return Timestamp((ms / 1000L) * 1000L)
+ }
+
+ operator fun minus(other: Timestamp): Duration = when {
+ ms == NEVER -> Duration(Duration.FOREVER)
+ other.ms == NEVER -> throw Error("Invalid argument for timestamp comparision")
+ ms < other.ms -> Duration(0)
+ else -> Duration(ms - other.ms)
+ }
+
+ operator fun minus(other: Duration): Timestamp = when {
+ ms == NEVER -> this
+ other.ms == Duration.FOREVER -> Timestamp(0)
+ else -> Timestamp(max(0, ms - other.ms))
+ }
+
+ override fun compareTo(other: Timestamp): Int {
+ return if (ms == NEVER) {
+ if (other.ms == NEVER) 0
+ else 1
+ } else {
+ if (other.ms == NEVER) -1
+ else ms.compareTo(other.ms)
+ }
+ }
+
+}
+
+@Serializable
+data class Duration(
+ @SerialName("d_ms")
+ @Serializable(ForeverSerializer::class) val old_ms: Long? = null,
+ @SerialName("d_s")
+ @Serializable(ForeverSerializer::class)
+ private val s: Long? = null,
+) {
+ val ms: Long = if (s != null) {
+ s * 1000L
+ } else if (old_ms !== null) {
+ old_ms
+ } else {
+ throw Exception("duration didn't have d_s or d_ms")
+ }
+
+ constructor(ms: Long) : this(ms, null)
+
+ companion object {
+ internal const val FOREVER: Long = -1
+ fun forever(): Duration = Duration(FOREVER)
+ }
+}
+
+internal abstract class MinusOneSerializer(private val keyword: String) :
+ JsonTransformingSerializer<Long>(Long.serializer()) {
+
+ override fun transformDeserialize(element: JsonElement): JsonElement {
+ return if (element.jsonPrimitive.contentOrNull == keyword) return JsonPrimitive(-1)
+ else super.transformDeserialize(element)
+ }
+
+ override fun transformSerialize(element: JsonElement): JsonElement {
+ return if (element.jsonPrimitive.longOrNull == -1L) return JsonPrimitive(keyword)
+ else element
+ }
+}
+
+internal object NeverSerializer : MinusOneSerializer("never")
+internal object ForeverSerializer : MinusOneSerializer("forever")
diff --git a/taler-kotlin-android/src/main/java/net/taler/common/Version.kt b/taler-kotlin-android/src/main/java/net/taler/common/Version.kt
new file mode 100644
index 0000000..f46913e
--- /dev/null
+++ b/taler-kotlin-android/src/main/java/net/taler/common/Version.kt
@@ -0,0 +1,70 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.common
+
+import kotlin.math.sign
+
+/**
+ * Semantic versioning, but libtool-style.
+ * See https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html
+ */
+public data class Version(
+ val current: Int,
+ val revision: Int,
+ val age: Int
+) {
+ public companion object {
+ public fun parse(v: String): Version? {
+ val elements = v.split(":")
+ if (elements.size != 3) return null
+ val (currentStr, revisionStr, ageStr) = elements
+ val current = currentStr.toIntOrNull()
+ val revision = revisionStr.toIntOrNull()
+ val age = ageStr.toIntOrNull()
+ if (current == null || revision == null || age == null) return null
+ return Version(current, revision, age)
+ }
+ }
+
+ /**
+ * Compare two libtool-style versions.
+ *
+ * Returns a [VersionMatchResult] or null if the given version was null.
+ */
+ public fun compare(other: Version?): VersionMatchResult? {
+ if (other == null) return null
+ val compatible = current - age <= other.current &&
+ current >= other.current - other.age
+ val currentCmp = sign((current - other.current).toDouble()).toInt()
+ return VersionMatchResult(compatible, currentCmp)
+ }
+
+ /**
+ * Result of comparing two libtool versions.
+ */
+ public data class VersionMatchResult(
+ /**
+ * Is the first version compatible with the second?
+ */
+ val compatible: Boolean,
+ /**
+ * Is the first version older (-1), newer (+1) or identical (0)?
+ */
+ val currentCmp: Int
+ )
+
+}
diff --git a/taler-kotlin-android/src/test/java/net/taler/common/AmountTest.kt b/taler-kotlin-android/src/test/java/net/taler/common/AmountTest.kt
new file mode 100644
index 0000000..3343b52
--- /dev/null
+++ b/taler-kotlin-android/src/test/java/net/taler/common/AmountTest.kt
@@ -0,0 +1,221 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.common
+
+import kotlin.random.Random
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Assert.assertFalse
+import org.junit.Test
+
+class AmountTest {
+
+ companion object {
+ fun getRandomAmount() = getRandomAmount(getRandomString(1, Random.nextInt(1, 12)))
+ fun getRandomAmount(currency: String): Amount {
+ val value = Random.nextLong(0, Amount.MAX_VALUE)
+ val fraction = Random.nextInt(0, Amount.MAX_FRACTION)
+ return Amount(currency, value, fraction)
+ }
+ }
+
+ @Test
+ fun testFromJSONString() {
+ var str = "TESTKUDOS:23.42"
+ var amount = Amount.fromJSONString(str)
+ assertEquals(str, amount.toJSONString())
+ assertEquals("TESTKUDOS", amount.currency)
+ assertEquals(23, amount.value)
+ assertEquals((0.42 * 1e8).toInt(), amount.fraction)
+ assertEquals("23.42 TESTKUDOS", amount.toString())
+
+ str = "EUR:500000000.00000001"
+ amount = Amount.fromJSONString(str)
+ assertEquals(str, amount.toJSONString())
+ assertEquals("EUR", amount.currency)
+ assertEquals(500000000, amount.value)
+ assertEquals(1, amount.fraction)
+ assertEquals("500000000.00000001 EUR", amount.toString())
+
+ str = "EUR:1500000000.00000003"
+ amount = Amount.fromJSONString(str)
+ assertEquals(str, amount.toJSONString())
+ assertEquals("EUR", amount.currency)
+ assertEquals(1500000000, amount.value)
+ assertEquals(3, amount.fraction)
+ assertEquals("1500000000.00000003 EUR", amount.toString())
+ }
+
+ @Test
+ fun testFromJSONStringAcceptsMaxValuesRejectsAbove() {
+ val maxValue = 4503599627370496
+ val str = "TESTKUDOS123:$maxValue.99999999"
+ val amount = Amount.fromJSONString(str)
+ assertEquals(str, amount.toJSONString())
+ assertEquals("TESTKUDOS123", amount.currency)
+ assertEquals(maxValue, amount.value)
+ assertEquals("$maxValue.99999999 TESTKUDOS123", amount.toString())
+
+ // longer currency not accepted
+ assertThrows<AmountParserException>("longer currency was accepted") {
+ Amount.fromJSONString("TESTKUDOS1234:$maxValue.99999999")
+ }
+
+ // max value + 1 not accepted
+ assertThrows<AmountParserException>("max value + 1 was accepted") {
+ Amount.fromJSONString("TESTKUDOS123:${maxValue + 1}.99999999")
+ }
+
+ // max fraction + 1 not accepted
+ assertThrows<AmountParserException>("max fraction + 1 was accepted") {
+ Amount.fromJSONString("TESTKUDOS123:$maxValue.999999990")
+ }
+ }
+
+ @Test
+ fun testFromJSONStringRejections() {
+ assertThrows<AmountParserException> {
+ Amount.fromJSONString("TESTKUDOS:0,5")
+ }
+ assertThrows<AmountParserException> {
+ Amount.fromJSONString("+TESTKUDOS:0.5")
+ }
+ assertThrows<AmountParserException> {
+ Amount.fromJSONString("0.5")
+ }
+ assertThrows<AmountParserException> {
+ Amount.fromJSONString(":0.5")
+ }
+ assertThrows<AmountParserException> {
+ Amount.fromJSONString("EUR::0.5")
+ }
+ assertThrows<AmountParserException> {
+ Amount.fromJSONString("EUR:.5")
+ }
+ }
+
+ @Test
+ fun testAddition() {
+ assertEquals(
+ Amount.fromJSONString("EUR:2"),
+ Amount.fromJSONString("EUR:1") + Amount.fromJSONString("EUR:1")
+ )
+ assertEquals(
+ Amount.fromJSONString("EUR:3"),
+ Amount.fromJSONString("EUR:1.5") + Amount.fromJSONString("EUR:1.5")
+ )
+ assertEquals(
+ Amount.fromJSONString("EUR:500000000.00000002"),
+ Amount.fromJSONString("EUR:500000000.00000001") + Amount.fromJSONString("EUR:0.00000001")
+ )
+ assertThrows<AmountOverflowException>("addition didn't overflow") {
+ Amount.fromJSONString("EUR:4503599627370496.99999999") + Amount.fromJSONString("EUR:0.00000001")
+ }
+ assertThrows<AmountOverflowException>("addition didn't overflow") {
+ Amount.fromJSONString("EUR:4000000000000000") + Amount.fromJSONString("EUR:4000000000000000")
+ }
+ }
+
+ @Test
+ fun testTimes() {
+ assertEquals(
+ Amount.fromJSONString("EUR:2"),
+ Amount.fromJSONString("EUR:2") * 1
+ )
+ assertEquals(
+ Amount.fromJSONString("EUR:2"),
+ Amount.fromJSONString("EUR:1") * 2
+ )
+ assertEquals(
+ Amount.fromJSONString("EUR:4.5"),
+ Amount.fromJSONString("EUR:1.5") * 3
+ )
+ assertEquals(Amount.fromJSONString("EUR:0"), Amount.fromJSONString("EUR:1.11") * 0)
+ assertEquals(Amount.fromJSONString("EUR:1.11"), Amount.fromJSONString("EUR:1.11") * 1)
+ assertEquals(Amount.fromJSONString("EUR:2.22"), Amount.fromJSONString("EUR:1.11") * 2)
+ assertEquals(Amount.fromJSONString("EUR:3.33"), Amount.fromJSONString("EUR:1.11") * 3)
+ assertEquals(Amount.fromJSONString("EUR:4.44"), Amount.fromJSONString("EUR:1.11") * 4)
+ assertEquals(Amount.fromJSONString("EUR:5.55"), Amount.fromJSONString("EUR:1.11") * 5)
+ assertEquals(
+ Amount.fromJSONString("EUR:1500000000.00000003"),
+ Amount.fromJSONString("EUR:500000000.00000001") * 3
+ )
+ assertThrows<AmountOverflowException>("times didn't overflow") {
+ Amount.fromJSONString("EUR:4000000000000000") * 2
+ }
+ }
+
+ @Test
+ fun testSubtraction() {
+ assertEquals(
+ Amount.fromJSONString("EUR:0"),
+ Amount.fromJSONString("EUR:1") - Amount.fromJSONString("EUR:1")
+ )
+ assertEquals(
+ Amount.fromJSONString("EUR:1.5"),
+ Amount.fromJSONString("EUR:3") - Amount.fromJSONString("EUR:1.5")
+ )
+ assertEquals(
+ Amount.fromJSONString("EUR:500000000.00000001"),
+ Amount.fromJSONString("EUR:500000000.00000002") - Amount.fromJSONString("EUR:0.00000001")
+ )
+ assertThrows<AmountOverflowException>("subtraction didn't underflow") {
+ Amount.fromJSONString("EUR:23.42") - Amount.fromJSONString("EUR:42.23")
+ }
+ assertThrows<AmountOverflowException>("subtraction didn't underflow") {
+ Amount.fromJSONString("EUR:0.5") - Amount.fromJSONString("EUR:0.50000001")
+ }
+ }
+
+ @Test
+ fun testIsZero() {
+ assertTrue(Amount.zero("EUR").isZero())
+ assertTrue(Amount.fromJSONString("EUR:0").isZero())
+ assertTrue(Amount.fromJSONString("EUR:0.0").isZero())
+ assertTrue(Amount.fromJSONString("EUR:0.00000").isZero())
+ assertTrue((Amount.fromJSONString("EUR:1.001") - Amount.fromJSONString("EUR:1.001")).isZero())
+
+ assertFalse(Amount.fromJSONString("EUR:0.00000001").isZero())
+ assertFalse(Amount.fromJSONString("EUR:1.0").isZero())
+ assertFalse(Amount.fromJSONString("EUR:0001.0").isZero())
+ }
+
+ @Test
+ fun testComparision() {
+ assertTrue(Amount.fromJSONString("EUR:0") <= Amount.fromJSONString("EUR:0"))
+ assertTrue(Amount.fromJSONString("EUR:0") <= Amount.fromJSONString("EUR:0.00000001"))
+ assertTrue(Amount.fromJSONString("EUR:0") < Amount.fromJSONString("EUR:0.00000001"))
+ assertTrue(Amount.fromJSONString("EUR:0") < Amount.fromJSONString("EUR:1"))
+ assertEquals(Amount.fromJSONString("EUR:0"), Amount.fromJSONString("EUR:0"))
+ assertEquals(Amount.fromJSONString("EUR:42"), Amount.fromJSONString("EUR:42"))
+ assertEquals(
+ Amount.fromJSONString("EUR:42.00000001"),
+ Amount.fromJSONString("EUR:42.00000001")
+ )
+ assertTrue(Amount.fromJSONString("EUR:42.00000001") >= Amount.fromJSONString("EUR:42.00000001"))
+ assertTrue(Amount.fromJSONString("EUR:42.00000002") >= Amount.fromJSONString("EUR:42.00000001"))
+ assertTrue(Amount.fromJSONString("EUR:42.00000002") > Amount.fromJSONString("EUR:42.00000001"))
+ assertTrue(Amount.fromJSONString("EUR:0.00000002") > Amount.fromJSONString("EUR:0.00000001"))
+ assertTrue(Amount.fromJSONString("EUR:0.00000001") > Amount.fromJSONString("EUR:0"))
+ assertTrue(Amount.fromJSONString("EUR:2") > Amount.fromJSONString("EUR:1"))
+
+ assertThrows<IllegalStateException>("could compare amounts with different currencies") {
+ Amount.fromJSONString("EUR:0.5") < Amount.fromJSONString("USD:0.50000001")
+ }
+ }
+
+}
diff --git a/taler-kotlin-android/src/test/java/net/taler/common/ContractTermsTest.kt b/taler-kotlin-android/src/test/java/net/taler/common/ContractTermsTest.kt
index 3a2cdb4..f7b83a9 100644
--- a/taler-kotlin-android/src/test/java/net/taler/common/ContractTermsTest.kt
+++ b/taler-kotlin-android/src/test/java/net/taler/common/ContractTermsTest.kt
@@ -18,7 +18,6 @@ package net.taler.common
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
-import net.taler.lib.common.Timestamp
import org.junit.Assert.assertEquals
import org.junit.Test
diff --git a/taler-kotlin-android/src/test/java/net/taler/common/TalerUriTest.kt b/taler-kotlin-android/src/test/java/net/taler/common/TalerUriTest.kt
new file mode 100644
index 0000000..128f707
--- /dev/null
+++ b/taler-kotlin-android/src/test/java/net/taler/common/TalerUriTest.kt
@@ -0,0 +1,65 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.common
+
+import net.taler.common.TalerUri.parseWithdrawUri
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Test
+
+class TalerUriTest {
+
+ @Test
+ fun testParseWithdrawUri() {
+ // correct parsing
+ var uri = "taler://withdraw/bank.example.com/12345"
+ var expected = TalerUri.WithdrawUriResult("https://bank.example.com/", "12345")
+ assertEquals(expected, parseWithdrawUri(uri))
+
+ // correct parsing with insecure http
+ uri = "taler+http://withdraw/bank.example.org/foo"
+ expected = TalerUri.WithdrawUriResult("http://bank.example.org/", "foo")
+ assertEquals(expected, parseWithdrawUri(uri))
+
+ // correct parsing with long path
+ uri = "taler://withdraw/bank.example.com/foo/bar/23/42/1337/1234567890"
+ expected =
+ TalerUri.WithdrawUriResult("https://bank.example.com/foo/bar/23/42/1337", "1234567890")
+ assertEquals(expected, parseWithdrawUri(uri))
+
+ // rejects incorrect scheme
+ uri = "talerx://withdraw/bank.example.com/12345"
+ assertNull(parseWithdrawUri(uri))
+
+ // rejects incorrect authority
+ uri = "taler://withdrawx/bank.example.com/12345"
+ assertNull(parseWithdrawUri(uri))
+
+ // rejects incorrect authority with insecure http
+ uri = "taler+http://withdrawx/bank.example.com/12345"
+ assertNull(parseWithdrawUri(uri))
+
+ // rejects empty withdrawalId
+ uri = "taler://withdraw/bank.example.com//"
+ assertNull(parseWithdrawUri(uri))
+
+ // rejects empty path and withdrawalId
+ uri = "taler://withdraw/bank.example.com////"
+ assertNull(parseWithdrawUri(uri))
+ }
+
+}
diff --git a/taler-kotlin-android/src/main/java/net/taler/lib/android/Serialization.kt b/taler-kotlin-android/src/test/java/net/taler/common/TestUtils.kt
index 7eb4480..b0f191d 100644
--- a/taler-kotlin-android/src/main/java/net/taler/lib/android/Serialization.kt
+++ b/taler-kotlin-android/src/test/java/net/taler/common/TestUtils.kt
@@ -14,8 +14,27 @@
* GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-package net.taler.lib.android
+package net.taler.common
-interface CustomClassDiscriminator {
- val discriminator: String
+import kotlin.random.Random
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+
+private val charPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9')
+fun getRandomString(minLength: Int = 1, maxLength: Int = Random.nextInt(0, 1337)) =
+ (minLength..maxLength)
+ .map { Random.nextInt(0, charPool.size) }
+ .map(charPool::get)
+ .joinToString("")
+
+inline fun <reified T : Throwable> assertThrows(
+ msg: String? = null,
+ function: () -> Any
+) {
+ try {
+ function.invoke()
+ fail(msg)
+ } catch (e: Exception) {
+ assertTrue(e is T)
+ }
}
diff --git a/taler-kotlin-android/src/test/java/net/taler/common/TimeTest.kt b/taler-kotlin-android/src/test/java/net/taler/common/TimeTest.kt
new file mode 100644
index 0000000..beda621
--- /dev/null
+++ b/taler-kotlin-android/src/test/java/net/taler/common/TimeTest.kt
@@ -0,0 +1,46 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.common
+
+import kotlinx.serialization.json.Json.Default.decodeFromString
+import kotlinx.serialization.json.Json.Default.encodeToString
+import kotlin.random.Random
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+// TODO test other functionality of Timestamp and Duration
+class TimeTest {
+
+ @Test
+ fun testSerialize() {
+ for (i in 0 until 42) {
+ val t = Random.nextLong()
+ assertEquals("""{"t_ms":$t}""", encodeToString(Timestamp.serializer(), Timestamp(t)))
+ }
+ assertEquals("""{"t_ms":"never"}""", encodeToString(Timestamp.serializer(), Timestamp.never()))
+ }
+
+ @Test
+ fun testDeserialize() {
+ for (i in 0 until 42) {
+ val t = Random.nextLong()
+ assertEquals(Timestamp(t), decodeFromString(Timestamp.serializer(), """{ "t_ms": $t }"""))
+ }
+ assertEquals(Timestamp.never(), decodeFromString(Timestamp.serializer(), """{ "t_ms": "never" }"""))
+ }
+
+}
diff --git a/taler-kotlin-android/src/test/java/net/taler/common/VersionTest.kt b/taler-kotlin-android/src/test/java/net/taler/common/VersionTest.kt
new file mode 100644
index 0000000..d8d7149
--- /dev/null
+++ b/taler-kotlin-android/src/test/java/net/taler/common/VersionTest.kt
@@ -0,0 +1,65 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2020 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.common
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Test
+
+
+class VersionTest {
+
+ @Test
+ fun testParse() {
+ assertNull(Version.parse(""))
+ assertNull(Version.parse("foo"))
+ assertNull(Version.parse("foo:bar:foo"))
+ assertNull(Version.parse("0:0:0:"))
+ assertNull(Version.parse("0:0:"))
+ assertEquals(Version(0, 0, 0), Version.parse("0:0:0"))
+ assertEquals(Version(1, 2, 3), Version.parse("1:2:3"))
+ assertEquals(Version(1337, 42, 23), Version.parse("1337:42:23"))
+ }
+
+ @Test
+ fun testComparision() {
+ assertEquals(
+ Version.VersionMatchResult(true, 0),
+ Version.parse("0:0:0")!!.compare(Version.parse("0:0:0"))
+ )
+ assertEquals(
+ Version.VersionMatchResult(true, -1),
+ Version.parse("0:0:0")!!.compare(Version.parse("1:0:1"))
+ )
+ assertEquals(
+ Version.VersionMatchResult(true, -1),
+ Version.parse("0:0:0")!!.compare(Version.parse("1:5:1"))
+ )
+ assertEquals(
+ Version.VersionMatchResult(false, -1),
+ Version.parse("0:0:0")!!.compare(Version.parse("1:5:0"))
+ )
+ assertEquals(
+ Version.VersionMatchResult(false, 1),
+ Version.parse("1:0:0")!!.compare(Version.parse("0:5:0"))
+ )
+ assertEquals(
+ Version.VersionMatchResult(true, 0),
+ Version.parse("1:0:1")!!.compare(Version.parse("1:5:1"))
+ )
+ }
+
+}
diff --git a/wallet/build.gradle b/wallet/build.gradle
index bf2b625..faebdde 100644
--- a/wallet/build.gradle
+++ b/wallet/build.gradle
@@ -23,8 +23,8 @@ plugins {
id "de.undercouch.download"
}
-def walletCoreVersion = "v0.9.0-dev.2"
-def walletCoreSha256 = "e4643927b8bc49ef1a94be2f49fb11f8a1853828cb2399470ffec37e7f8217c8"
+def walletCoreVersion = "v0.9.0-dev.11"
+def walletCoreSha256 = "0ee5eae7d0afd6f251f7d33975758055d06926ccb67ae9099d589a79d34be39d"
static def versionCodeEpoch() {
return (new Date().getTime() / 1000).toInteger()
@@ -39,16 +39,15 @@ def gitCommit = { ->
}
android {
- compileSdkVersion 31
- //noinspection GradleDependency
+ compileSdkVersion 32
buildToolsVersion "$build_tools_version"
defaultConfig {
applicationId "net.taler.wallet"
minSdkVersion 21
- targetSdkVersion 30
+ targetSdkVersion 32
versionCode 11
- versionName "v0.9.0-dev.1"
+ versionName walletCoreVersion
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField "String", "WALLET_CORE_VERSION", "\"$walletCoreVersion\""
}
@@ -92,25 +91,29 @@ android {
}
composeOptions {
- kotlinCompilerExtensionVersion '1.0.5'
+ kotlinCompilerExtensionVersion '1.1.1'
}
buildFeatures {
viewBinding true
compose true
}
-
packagingOptions {
- exclude("META-INF/*.kotlin_module")
+ jniLibs {
+ keepDebugSymbols += ['**/*.so']
+ }
+ resources {
+ excludes += ['META-INF/*.kotlin_module']
+ }
}
- lintOptions {
+ lint {
abortOnError true
+ ignore 'WrongConstant'
ignoreWarnings false
- // TODO remove once com.google.android.material fixed this
- // https://github.com/material-components/material-components-android/issues/504
- ignore "WrongConstant"
}
+ namespace 'net.taler.wallet'
+
}
dependencies {
@@ -120,18 +123,18 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
- implementation 'androidx.preference:preference-ktx:1.1.1'
+ implementation 'androidx.preference:preference-ktx:1.2.0'
implementation "com.google.android.material:material:$material_version"
implementation "androidx.constraintlayout:constraintlayout:$constraintlayout_version"
// Compose
implementation 'androidx.activity:activity-compose:1.4.0'
- implementation 'androidx.compose.material:material:1.0.5'
- implementation 'androidx.compose.animation:animation:1.0.5'
- implementation 'androidx.compose.ui:ui-tooling:1.0.5'
- implementation 'androidx.compose.material:material-icons-extended:1.0.5'
+ implementation 'androidx.compose.material:material:1.1.1'
+ implementation 'androidx.compose.animation:animation:1.1.1'
+ implementation 'androidx.compose.ui:ui-tooling:1.1.1'
+ implementation 'androidx.compose.material:material-icons-extended:1.1.1'
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"
- implementation "com.google.android.material:compose-theme-adapter:1.1.1"
+ implementation 'com.google.android.material:compose-theme-adapter:1.1.9'
// Lists and Selection
implementation "androidx.recyclerview:recyclerview:1.2.1"
@@ -145,7 +148,7 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// QR codes
- implementation 'com.journeyapps:zxing-android-embedded:4.0.2@aar'
+ implementation 'com.journeyapps:zxing-android-embedded:4.3.0@aar'
// needed to support zxing library in taler-kotlin-android on API < 24
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
@@ -153,13 +156,13 @@ dependencies {
implementation 'me.zhanghai.android.materialprogressbar:library:1.6.1'
// Markdown rendering
- final def markwon_version = '4.6.0'
+ final def markwon_version = '4.6.2'
implementation "io.noties.markwon:core:$markwon_version"
implementation "io.noties.markwon:ext-tables:$markwon_version"
implementation "io.noties.markwon:recycler:$markwon_version"
testImplementation "junit:junit:$junit_version"
- testImplementation 'org.json:json:20200518'
+ testImplementation 'org.json:json:20220320'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
@@ -185,7 +188,14 @@ task verifyWalletLibrary(type: Verify, dependsOn: downloadWalletLibrary) {
algorithm 'SHA-256'
checksum walletCoreSha256
}
+
tasks.withType(MergeResources) {
inputs.dir walletLibraryDir
dependsOn verifyWalletLibrary
}
+
+tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
+ kotlinOptions {
+ freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn"
+ }
+} \ No newline at end of file
diff --git a/wallet/proguard-rules.pro b/wallet/proguard-rules.pro
index ca6bc15..04cb4da 100644
--- a/wallet/proguard-rules.pro
+++ b/wallet/proguard-rules.pro
@@ -20,7 +20,7 @@
# hide the original source file name.
#-renamesourcefileattribute SourceFile
--keep class akono.AkonoJni {*;}
+-keep class net.taler.akono.** {*;}
#noinspection ShrinkerUnresolvedReference
-keep class net.taler.wallet.** {*;}
diff --git a/wallet/src/main/AndroidManifest.xml b/wallet/src/main/AndroidManifest.xml
index 963032c..70d2c4f 100644
--- a/wallet/src/main/AndroidManifest.xml
+++ b/wallet/src/main/AndroidManifest.xml
@@ -15,12 +15,13 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- package="net.taler.wallet">
+ xmlns:tools="http://schemas.android.com/tools">
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.NFC" />
<uses-feature
@@ -44,6 +45,7 @@
<activity
android:name=".MainActivity"
+ android:exported="true"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
diff --git a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
index a2b48b6..76aceb2 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
@@ -30,7 +30,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.Json
-import net.taler.lib.android.CustomClassDiscriminator
import net.taler.wallet.backend.WalletBackendService.Companion.MSG_COMMAND
import net.taler.wallet.backend.WalletBackendService.Companion.MSG_NOTIFY
import net.taler.wallet.backend.WalletBackendService.Companion.MSG_REPLY
@@ -42,7 +41,6 @@ import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
-import kotlin.reflect.full.companionObjectInstance
class WalletBackendApi(
private val app: Application,
@@ -152,9 +150,6 @@ class WalletBackendApi(
suspendCoroutine { cont ->
val json = Json {
ignoreUnknownKeys = true
- (T::class.companionObjectInstance as? CustomClassDiscriminator)?.let {
- classDiscriminator = it.discriminator
- }
}
sendRequest(operation, args?.invoke(JSONObject())) { isError, message ->
val response = try {
diff --git a/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt b/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt
index ab1ac80..e52fd4f 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt
@@ -32,13 +32,13 @@ sealed class WalletResponse<T> {
@Serializable
@SerialName("response")
data class Success<T>(
- val result: T
+ val result: T,
) : WalletResponse<T>()
@Serializable
@SerialName("error")
data class Error<T>(
- val error: TalerErrorInfo
+ val error: TalerErrorInfo,
) : WalletResponse<T>()
fun onSuccess(block: (result: T) -> Unit): WalletResponse<T> {
@@ -59,15 +59,15 @@ data class TalerErrorInfo(
val code: Int,
// English description of the error code.
- val hint: String?,
+ val hint: String? = null,
// English diagnostic message that can give details
// for the instance of the error.
- val message: String?,
+ val message: String? = null,
// Error details
@Serializable(JSONObjectDeserializer::class)
- val details: JSONObject?
+ val details: JSONObject? = null,
) {
val userFacingMsg: String
get() {
diff --git a/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt b/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt
index 09ae353..24ee1a1 100644
--- a/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt
+++ b/wallet/src/main/java/net/taler/wallet/balances/BalanceAdapter.kt
@@ -25,7 +25,7 @@ import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Adapter
import kotlinx.serialization.Serializable
-import net.taler.lib.common.Amount
+import net.taler.common.Amount
import net.taler.wallet.R
import net.taler.wallet.balances.BalanceAdapter.BalanceViewHolder
diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt
index 1da9b49..7824d1e 100644
--- a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFees.kt
@@ -16,8 +16,9 @@
package net.taler.wallet.exchanges
-import net.taler.lib.common.Amount
-import net.taler.lib.common.Timestamp
+import net.taler.common.Amount
+import net.taler.common.Timestamp
+
data class CoinFee(
val coin: Amount,
diff --git a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFeesFragment.kt b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFeesFragment.kt
index 1ea32dd..d8242f3 100644
--- a/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFeesFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/exchanges/ExchangeFeesFragment.kt
@@ -27,9 +27,9 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.recyclerview.widget.RecyclerView.Adapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
+import net.taler.common.Amount
import net.taler.common.toRelativeTime
import net.taler.common.toShortDate
-import net.taler.lib.common.Amount
import net.taler.wallet.MainViewModel
import net.taler.wallet.R
import net.taler.wallet.databinding.FragmentExchangeFeesBinding
diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
index 34a8023..ae091df 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentManager.kt
@@ -22,8 +22,8 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
+import net.taler.common.Amount
import net.taler.common.ContractTerms
-import net.taler.lib.common.Amount
import net.taler.wallet.TAG
import net.taler.wallet.backend.WalletBackendApi
import net.taler.wallet.backend.TalerErrorInfo
diff --git a/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt b/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt
index 4b908b5..7e03472 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PaymentResponses.kt
@@ -16,18 +16,18 @@
package net.taler.wallet.payment
+import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.JsonClassDiscriminator
+import net.taler.common.Amount
import net.taler.common.ContractTerms
-import net.taler.lib.android.CustomClassDiscriminator
-import net.taler.lib.common.Amount
import net.taler.wallet.backend.TalerErrorInfo
+@OptIn(ExperimentalSerializationApi::class)
@Serializable
+@JsonClassDiscriminator("status")
sealed class PreparePayResponse {
- companion object : CustomClassDiscriminator {
- override val discriminator: String = "status"
- }
@Serializable
@SerialName("payment-possible")
diff --git a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt
index 700e158..d5c3eaf 100644
--- a/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/payment/PromptPaymentFragment.kt
@@ -28,10 +28,10 @@ import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
+import net.taler.common.Amount
import net.taler.common.ContractTerms
import net.taler.common.fadeIn
import net.taler.common.fadeOut
-import net.taler.lib.common.Amount
import net.taler.wallet.MainViewModel
import net.taler.wallet.R
import net.taler.wallet.databinding.FragmentPromptPaymentBinding
@@ -49,7 +49,7 @@ class PromptPaymentFragment : Fragment(), ProductImageClickListener {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?
+ savedInstanceState: Bundle?,
): View {
ui = FragmentPromptPaymentBinding.inflate(inflater, container, false)
return ui.root
@@ -131,7 +131,7 @@ class PromptPaymentFragment : Fragment(), ProductImageClickListener {
}
}
- private fun showOrder(contractTerms: ContractTerms, amount:Amount, totalFees: Amount? = null) {
+ private fun showOrder(contractTerms: ContractTerms, amount: Amount, totalFees: Amount? = null) {
ui.details.orderView.text = contractTerms.summary
adapter.setItems(contractTerms.products)
ui.details.productsList.fadeIn()
diff --git a/wallet/src/main/java/net/taler/wallet/refund/RefundManager.kt b/wallet/src/main/java/net/taler/wallet/refund/RefundManager.kt
index 9c292aa..f3c41e8 100644
--- a/wallet/src/main/java/net/taler/wallet/refund/RefundManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/refund/RefundManager.kt
@@ -21,7 +21,7 @@ import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
-import net.taler.lib.common.Amount
+import net.taler.common.Amount
import net.taler.wallet.backend.WalletBackendApi
sealed class RefundStatus {
diff --git a/wallet/src/main/java/net/taler/wallet/settings/SettingsFragment.kt b/wallet/src/main/java/net/taler/wallet/settings/SettingsFragment.kt
index bd6ce1a..1a17d78 100644
--- a/wallet/src/main/java/net/taler/wallet/settings/SettingsFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/settings/SettingsFragment.kt
@@ -18,6 +18,8 @@ package net.taler.wallet.settings
import android.os.Bundle
import android.view.View
+import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.activityViewModels
import androidx.preference.Preference
@@ -26,7 +28,6 @@ import androidx.preference.SwitchPreferenceCompat
import com.google.android.material.snackbar.BaseTransientBottomBar.LENGTH_SHORT
import com.google.android.material.snackbar.Snackbar
import net.taler.common.showError
-import net.taler.common.showLogViewer
import net.taler.common.toRelativeTime
import net.taler.wallet.BuildConfig.FLAVOR
import net.taler.wallet.BuildConfig.VERSION_CODE
@@ -64,6 +65,11 @@ class SettingsFragment : PreferenceFragmentCompat() {
)
}
+ val createDocumentActivity =
+ registerForActivityResult(ActivityResultContracts.CreateDocument()) { uri ->
+
+ }
+
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.settings_main, rootKey)
prefBackup = findPreference("pref_backup")!!
@@ -80,12 +86,12 @@ class SettingsFragment : PreferenceFragmentCompat() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- model.lastBackup.observe(viewLifecycleOwner, {
+ model.lastBackup.observe(viewLifecycleOwner) {
val time = it.toRelativeTime(requireContext())
prefBackup.summary = getString(R.string.backup_last, time)
- })
+ }
- model.devMode.observe(viewLifecycleOwner, { enabled ->
+ model.devMode.observe(viewLifecycleOwner) { enabled ->
prefDevMode.isChecked = enabled
if (enabled) {
prefVersionApp.summary = "$VERSION_NAME ($FLAVOR $VERSION_CODE)"
@@ -94,13 +100,13 @@ class SettingsFragment : PreferenceFragmentCompat() {
model.merchantVersion?.let { prefVersionMerchant.summary = it }
}
devPrefs.forEach { it.isVisible = enabled }
- })
+ }
prefDevMode.setOnPreferenceChangeListener { _, newValue ->
model.devMode.value = newValue as Boolean
true
}
- withdrawManager.testWithdrawalStatus.observe(viewLifecycleOwner, { status ->
+ withdrawManager.testWithdrawalStatus.observe(viewLifecycleOwner) { status ->
if (status == null) return@observe
val loading = status is WithdrawTestStatus.Withdrawing
prefWithdrawTest.isEnabled = !loading
@@ -109,14 +115,37 @@ class SettingsFragment : PreferenceFragmentCompat() {
requireActivity().showError(R.string.withdraw_error_test, status.message)
}
withdrawManager.testWithdrawalStatus.value = null
- })
+ }
prefWithdrawTest.setOnPreferenceClickListener {
withdrawManager.withdrawTestkudos()
true
}
prefLogcat.setOnPreferenceClickListener {
- requireContext().showLogViewer("taler-wallet")
+ val toast =
+ Toast.makeText(requireActivity(), "Log export currently unavailable", Toast.LENGTH_LONG)
+ toast.show()
+
+// val myPid = android.os.Process.myPid()
+// val proc = Runtime.getRuntime()
+// .exec(arrayOf("logcat", "-d", "--pid=$myPid", "*:V"))
+// val bytes = proc.inputStream.readBytes()
+// val f = File(requireActivity().getExternalFilesDir(null),
+// "taler-wallet-log-${System.currentTimeMillis()}.txt")
+// f.writeBytes(bytes)
+// val toast = Toast.makeText(requireActivity(), "Saved to ${f.absolutePath}", Toast.LENGTH_LONG)
+// toast.show()
+// val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
+// addCategory(Intent.CATEGORY_OPENABLE)
+// type = "application/pdf"
+// putExtra(Intent.EXTRA_TITLE, "invoice.pdf")
+//
+// // Optionally, specify a URI for the directory that should be opened in
+// // the system file picker before your app creates the document.
+// putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
+// }
+// startActivityForResult(intent, CREATE_FILE)
+// ActivityResultContracts.CreateDocument
true
}
@@ -138,5 +167,4 @@ class SettingsFragment : PreferenceFragmentCompat() {
}
.show()
}
-
}
diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt
index 866b363..128ca04 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionDetailFragment.kt
@@ -25,8 +25,8 @@ import android.view.MenuItem
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
+import net.taler.common.Amount
import net.taler.common.startActivitySafe
-import net.taler.lib.common.Amount
import net.taler.wallet.MainViewModel
import net.taler.wallet.R
diff --git a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
index 50181c5..cca370e 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/Transactions.kt
@@ -23,10 +23,10 @@ import androidx.annotation.StringRes
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
+import net.taler.common.Amount
import net.taler.common.ContractMerchant
import net.taler.common.ContractProduct
-import net.taler.lib.common.Amount
-import net.taler.lib.common.Timestamp
+import net.taler.common.Timestamp
import net.taler.wallet.R
import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.cleanExchange
diff --git a/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt b/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
index 90510e6..e63d451 100644
--- a/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/transactions/TransactionsFragment.kt
@@ -36,6 +36,7 @@ import androidx.recyclerview.selection.SelectionTracker
import androidx.recyclerview.selection.StorageStrategy
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL
+import net.taler.common.Amount
import net.taler.common.fadeIn
import net.taler.common.fadeOut
import net.taler.wallet.MainViewModel
@@ -112,12 +113,12 @@ class TransactionsFragment : Fragment(), OnTransactionClickListener, ActionMode.
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
- model.balances.observe(viewLifecycleOwner, { balances ->
- balances.find { it.currency == currency }?.available?.let { amount ->
+ model.balances.observe(viewLifecycleOwner) { balances ->
+ balances.find { it.currency == currency }?.available?.let { amount: Amount ->
requireActivity().title =
getString(R.string.transactions_detail_title_balance, amount)
}
- })
+ }
}
override fun onSaveInstanceState(outState: Bundle) {
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt
index e78ff44..660fec2 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawFragment.kt
@@ -24,8 +24,8 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
+import net.taler.common.Amount
import net.taler.common.hideKeyboard
-import net.taler.lib.common.Amount
import net.taler.wallet.MainViewModel
import net.taler.wallet.R
import net.taler.wallet.databinding.FragmentManualWithdrawBinding
@@ -66,8 +66,14 @@ class ManualWithdrawFragment : Fragment() {
return
}
ui.amountLayout.error = null
- val value = ui.amountView.text.toString().toLong()
- val amount = Amount(exchangeItem.currency, value, 0)
+ var value = 0.0
+ try {
+ value = ui.amountView.text.toString().replace(',', '.').toDouble()
+ } catch (e: NumberFormatException) {
+ ui.amountLayout.error = getString(R.string.withdraw_amount_error)
+ return
+ }
+ val amount = Amount.fromDouble(exchangeItem.currency, value)
ui.amountView.hideKeyboard()
withdrawManager.getWithdrawalDetails(exchangeItem.exchangeBaseUrl, amount)
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawSuccessFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawSuccessFragment.kt
index 4ea3e73..b638627 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawSuccessFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/ManualWithdrawSuccessFragment.kt
@@ -31,6 +31,7 @@ import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
@@ -44,6 +45,7 @@ import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Alignment.Companion.End
import androidx.compose.ui.Modifier
@@ -53,15 +55,20 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
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.TextUnit
+import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.em
+import androidx.compose.ui.unit.sp
import androidx.core.content.getSystemService
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import com.google.android.material.composethemeadapter.MdcTheme
+import net.taler.common.Amount
import net.taler.common.startActivitySafe
-import net.taler.lib.common.Amount
import net.taler.wallet.MainViewModel
import net.taler.wallet.R
@@ -82,17 +89,26 @@ class ManualWithdrawSuccessFragment : Fragment() {
val onBankAppClick = if (componentName == null) null else {
{ startActivitySafe(intent) }
}
- val onCancelClick = if (status.transactionId == null) null else {
+ val tid = status.transactionId
+ val onCancelClick = if (tid == null) null else {
{
- transactionManager.deleteTransaction(status.transactionId)
+ transactionManager.deleteTransaction(tid)
findNavController().navigate(R.id.action_nav_exchange_manual_withdrawal_success_to_nav_main)
}
}
setContent {
MdcTheme {
Surface {
- Screen(status, onBankAppClick, onCancelClick)
+ when (status) {
+ is WithdrawStatus.ManualTransferRequiredBitcoin -> {
+ ScreenBitcoin(status, onBankAppClick, onCancelClick)
+ }
+ is WithdrawStatus.ManualTransferRequiredIBAN -> {
+ ScreenIBAN(status, onBankAppClick, onCancelClick)
+ }
+ }
}
+
}
}
}
@@ -104,8 +120,8 @@ class ManualWithdrawSuccessFragment : Fragment() {
}
@Composable
-private fun Screen(
- status: WithdrawStatus.ManualTransferRequired,
+private fun ScreenIBAN(
+ status: WithdrawStatus.ManualTransferRequiredIBAN,
bankAppClick: (() -> Unit)?,
onCancelClick: (() -> Unit)?,
) {
@@ -172,6 +188,154 @@ private fun Screen(
}
@Composable
+private fun ScreenBitcoin(
+ status: WithdrawStatus.ManualTransferRequiredBitcoin,
+ bankAppClick: (() -> Unit)?,
+ onCancelClick: (() -> Unit)?,
+) {
+ val scrollState = rememberScrollState()
+ Column(modifier = Modifier
+ .padding(all = 16.dp)
+ .wrapContentWidth(CenterHorizontally)
+ .verticalScroll(scrollState)
+ ) {
+ Text(
+ text = stringResource(R.string.withdraw_manual_ready_title),
+ style = MaterialTheme.typography.h5,
+ )
+ Text(
+ text = stringResource(R.string.withdraw_manual_ready_intro,
+ status.amountRaw.toString()),
+ style = MaterialTheme.typography.body1,
+ modifier = Modifier
+ .padding(vertical = 8.dp)
+ )
+ Text(
+ text = stringResource(R.string.withdraw_manual_bitcoin_ready_details_intro),
+ style = MaterialTheme.typography.body1,
+ modifier = Modifier
+ .padding(vertical = 8.dp)
+ )
+ Text(
+ text = stringResource(R.string.withdraw_manual_bitcoin_ready_details_segwit),
+ style = MaterialTheme.typography.body1,
+ modifier = Modifier
+ .padding(vertical = 8.dp)
+ )
+ DetailRow(stringResource(R.string.withdraw_manual_ready_subject), status.subject)
+ Text(
+ text = stringResource(R.string.withdraw_manual_bitcoin_ready_details_bitcoincore),
+ style = MaterialTheme.typography.body1,
+ modifier = Modifier
+ .padding(vertical = 8.dp)
+ )
+ BitcoinSegwitAddrs(
+ status.amountRaw,
+ status.account,
+ status.segwitAddrs
+ )
+ Text(
+ text = stringResource(R.string.withdraw_manual_bitcoin_ready_details_confirm,
+ status.amountRaw.withCurrency(Amount.SEGWIT_MIN.currency) + Amount.SEGWIT_MIN + Amount.SEGWIT_MIN),
+ style = MaterialTheme.typography.body1,
+ modifier = Modifier
+ .padding(vertical = 8.dp)
+ )
+ Text(
+ text = stringResource(R.string.withdraw_manual_ready_warning),
+ style = MaterialTheme.typography.body2,
+ color = colorResource(R.color.notice_text),
+ modifier = Modifier
+ .align(CenterHorizontally)
+ .padding(all = 8.dp)
+ .background(colorResource(R.color.notice_background))
+ .border(BorderStroke(2.dp, colorResource(R.color.notice_border)))
+ .padding(all = 16.dp)
+ )
+ if (bankAppClick != null) {
+ Button(
+ onClick = bankAppClick,
+ modifier = Modifier
+ .padding(vertical = 16.dp)
+ .align(CenterHorizontally),
+ ) {
+ Text(text = stringResource(R.string.withdraw_manual_ready_bank_button))
+ }
+ }
+ if (onCancelClick != null) {
+ Button(
+ onClick = onCancelClick,
+ colors = ButtonDefaults.buttonColors(backgroundColor = colorResource(R.color.red)),
+ modifier = Modifier
+ .padding(vertical = 16.dp)
+ .align(End),
+ ) {
+ Text(text = stringResource(R.string.withdraw_manual_ready_cancel))
+ }
+ }
+ }
+}
+
+@Composable
+fun BitcoinSegwitAddrs(amount: Amount, addr: String, segwitAddrs: List<String>) {
+ val context = LocalContext.current
+
+ val sr = segwitAddrs.map { s -> """
+${s} ${Amount.SEGWIT_MIN}
+ """.trimIndent()}.joinToString(separator = "\n")
+ val copyText = """
+${addr} ${amount.withCurrency("BTC")}
+${sr}
+ """.trimIndent()
+
+ Column {
+
+ Row (modifier = Modifier.padding(vertical = 8.dp)){
+ Column (modifier = Modifier.weight(0.3f)) {
+ Text(
+ text = addr,
+ style = MaterialTheme.typography.body1,
+ fontWeight = FontWeight.Normal,
+ fontSize = 3.em
+ )
+ Text(
+ text = amount.withCurrency("BTC").toString(),
+ style = MaterialTheme.typography.body1,
+ fontWeight = FontWeight.Bold,
+ )
+ }
+ }
+ for(sAddr in segwitAddrs) {
+ Row (modifier = Modifier.padding(vertical = 8.dp)){
+ Column (modifier = Modifier.weight(0.3f)) {
+ Text(
+ text = sAddr,
+ style = MaterialTheme.typography.body1,
+ fontWeight = FontWeight.Normal,
+ fontSize = 3.em
+ )
+ Text(
+ text = Amount.SEGWIT_MIN.toString(),
+ style = MaterialTheme.typography.body1,
+ fontWeight = FontWeight.Bold,
+ )
+ }
+ }
+ }
+
+ Row (verticalAlignment = Alignment.CenterVertically) {
+ IconButton(
+ onClick = { copyToClipBoard(context, "this", copyText) },
+ ) { Icon(Icons.Default.ContentCopy, stringResource(R.string.copy)) }
+ Text (
+ text = stringResource(R.string.copy),
+ style = MaterialTheme.typography.body1,
+ )
+ }
+ }
+
+}
+@Composable
fun DetailRow(label: String, content: String, copy: Boolean = true) {
val context = LocalContext.current
Row {
@@ -202,9 +366,9 @@ fun DetailRow(label: String, content: String, copy: Boolean = true) {
@Preview
@Composable
-fun PreviewScreen() {
+fun PreviewScreen2() {
Surface {
- Screen(WithdrawStatus.ManualTransferRequired(
+ ScreenIBAN(WithdrawStatus.ManualTransferRequiredIBAN(
exchangeBaseUrl = "test.exchange.taler.net",
uri = Uri.parse("https://taler.net"),
iban = "ASDQWEASDZXCASDQWE",
@@ -215,6 +379,25 @@ fun PreviewScreen() {
}
}
+@Preview
+@Composable
+fun PreviewScreenBitcoin() {
+ Surface {
+ ScreenBitcoin(WithdrawStatus.ManualTransferRequiredBitcoin(
+ exchangeBaseUrl = "bitcoin.ice.bfh.ch",
+ uri = Uri.parse("https://taler.net"),
+ account = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4",
+ segwitAddrs = listOf<String>(
+ "bc1qqleages8702xvg9qcyu02yclst24xurdrynvxq",
+ "bc1qsleagehks96u7jmqrzcf0fw80ea5g57qm3m84c"
+ ),
+ subject = "0ZSX8SH0M30KHX8K3Y1DAMVGDQV82XEF9DG1HC4QMQ3QWYT4AF00",
+ amountRaw = Amount("BITCOINBTC", 0, 14000000),
+ transactionId = "",
+ ), {}) {}
+ }
+}
+
private fun copyToClipBoard(context: Context, label: String, str: String) {
val clipboard = context.getSystemService<ClipboardManager>()
val clip = ClipData.newPlainText(label, str)
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
index 08cbc2e..dfae920 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/PromptWithdrawFragment.kt
@@ -25,10 +25,10 @@ import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.snackbar.Snackbar.LENGTH_LONG
+import net.taler.common.Amount
import net.taler.common.EventObserver
import net.taler.common.fadeIn
import net.taler.common.fadeOut
-import net.taler.lib.common.Amount
import net.taler.wallet.MainViewModel
import net.taler.wallet.R
import net.taler.wallet.cleanExchange
diff --git a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
index fbb5c18..ca6ba17 100644
--- a/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/withdraw/WithdrawManager.kt
@@ -16,6 +16,7 @@
package net.taler.wallet.withdraw
+import Bech32Data
import android.net.Uri
import android.util.Log
import androidx.annotation.UiThread
@@ -24,15 +25,21 @@ import androidx.lifecycle.MutableLiveData
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
+import net.taler.common.Amount
+import net.taler.common.CyptoUtils
import net.taler.common.Event
import net.taler.common.toEvent
-import net.taler.lib.common.Amount
import net.taler.wallet.TAG
import net.taler.wallet.backend.TalerErrorInfo
import net.taler.wallet.backend.WalletBackendApi
import net.taler.wallet.exchanges.ExchangeFees
import net.taler.wallet.exchanges.ExchangeItem
import net.taler.wallet.withdraw.WithdrawStatus.ReceivedDetails
+import toBech32Data
+import kotlin.experimental.and
+import kotlin.experimental.or
+import kotlin.math.floor
+import kotlin.reflect.KProperty
sealed class WithdrawStatus {
data class Loading(val talerWithdrawUri: String? = null) : WithdrawStatus()
@@ -57,14 +64,30 @@ sealed class WithdrawStatus {
object Withdrawing : WithdrawStatus()
data class Success(val currency: String) : WithdrawStatus()
- data class ManualTransferRequired(
+ sealed class ManualTransferRequired: WithdrawStatus() {
+ abstract val uri: Uri
+ abstract val transactionId: String?
+ }
+
+ data class ManualTransferRequiredIBAN(
val exchangeBaseUrl: String,
- val uri: Uri,
+ override val uri: Uri,
val iban: String,
val subject: String,
val amountRaw: Amount,
- val transactionId: String?,
- ) : WithdrawStatus()
+ override val transactionId: String?,
+ ) : ManualTransferRequired() {
+ }
+
+ data class ManualTransferRequiredBitcoin(
+ val exchangeBaseUrl: String,
+ override val uri: Uri,
+ val account: String,
+ val segwitAddrs: List<String>,
+ val subject: String,
+ val amountRaw: Amount,
+ override val transactionId: String?,
+ ) : ManualTransferRequired()
data class Error(val message: String?) : WithdrawStatus()
}
@@ -264,6 +287,7 @@ class WithdrawManager(
}
+
fun createManualTransferRequired(
amount: Amount,
exchangeBaseUrl: String,
@@ -271,7 +295,22 @@ fun createManualTransferRequired(
transactionId: String? = null,
): WithdrawStatus.ManualTransferRequired {
val uri = Uri.parse(uriStr)
- return WithdrawStatus.ManualTransferRequired(
+ if ("bitcoin".equals(uri.authority, true)) {
+ val msg = uri.getQueryParameter("message")
+ val reg = "\\b([A-Z0-9]{52})\\b".toRegex().find(msg.orEmpty())
+ val reserve = reg?.value ?: uri.getQueryParameter("subject")!!
+ val segwitAddrs = Bech32.generateFakeSegwitAddress(reserve, uri.pathSegments.first())
+ return WithdrawStatus.ManualTransferRequiredBitcoin(
+ exchangeBaseUrl = exchangeBaseUrl,
+ uri = uri,
+ account = uri.lastPathSegment!!,
+ segwitAddrs = segwitAddrs,
+ subject = reserve,
+ amountRaw = amount,
+ transactionId = transactionId,
+ )
+ }
+ return WithdrawStatus.ManualTransferRequiredIBAN(
exchangeBaseUrl = exchangeBaseUrl,
uri = uri,
iban = uri.lastPathSegment!!,
@@ -280,3 +319,5 @@ fun createManualTransferRequired(
transactionId = transactionId,
)
}
+
+
diff --git a/wallet/src/main/res/layout/fragment_manual_withdraw.xml b/wallet/src/main/res/layout/fragment_manual_withdraw.xml
index 724c3e2..ec176ff 100644
--- a/wallet/src/main/res/layout/fragment_manual_withdraw.xml
+++ b/wallet/src/main/res/layout/fragment_manual_withdraw.xml
@@ -76,7 +76,7 @@
android:id="@+id/amountView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:inputType="number" />
+ android:inputType="numberDecimal" />
</com.google.android.material.textfield.TextInputLayout>
diff --git a/wallet/src/main/res/values-ar/strings.xml b/wallet/src/main/res/values-ar/strings.xml
new file mode 100644
index 0000000..ef8cdde
--- /dev/null
+++ b/wallet/src/main/res/values-ar/strings.xml
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <string name="app_name">محفظة نظام Taler</string>
+ <string name="google_play_title" tools:keep="@string/google_play_title">محفظة نظام Taler (تجريبي)</string>
+ <string name="ok">موافق</string>
+ <string name="menu_settings">الإعدادات</string>
+ <string name="google_play_short_desc" tools:keep="@string/google_play_short_desc">يجعل نظام GNU Taler من معاملات الخصوصية سهلة الاستخدام عبر الإنترنت سريعة وميسرة.</string>
+ <string name="nav_header_title">نظام GNU Taler</string>
+ <string name="nav_header_subtitle">محفظة</string>
+ <string name="nav_prompt_withdraw">سحب النقود الرقمية</string>
+ <string name="nav_exchange_tos">شروط الخدمة لشركة الصرافة</string>
+ <string name="nav_exchange_select">اختر شركة الصرافة</string>
+ <string name="nav_exchange_fees">رسوم شركة الصرافة</string>
+ <string name="nav_error">خطأ</string>
+ <string name="button_back">الرجوع إلى السابق</string>
+ <string name="button_scan_qr_code">مسح رمز الاستجابة السريع لنظام Taler</string>
+ <string name="enter_uri">أدخل مُعرّف الموارد الموحد لنظام Taler</string>
+ <string name="paste">لصق</string>
+ <string name="paste_invalid">تحتوي الحافظة على نوع بيانات غير صحيحة</string>
+ <string name="uri_invalid">مُعرّف الموارد الموحد لنظام Taler غير صحيح</string>
+ <string name="cancel">إلغاء</string>
+ <string name="menu">القائمة</string>
+ <string name="search">البحث</string>
+ <string name="or">أو</string>
+ <string name="offline">تتطلب العملية الاتصال بالإنترنت. يرجى التأكد من أنّ اتصالك بالإنترنت يعمل وحاول مرة أخرى.</string>
+ <string name="error_unsupported_uri">خطأ: مُعرّف الموارد الموحد لنظام URI غير معتمد.</string>
+ <string name="menu_retry_pending_operations">إعادة العمليات المعلقة</string>
+ <string name="host_apdu_service_desc">مدفوعات الاتصال قريب المدى لنظام Taler</string>
+ <string name="balances_title">الأرصدة</string>
+ <string name="amount_positive">%s +</string>
+ <string name="amount_negative">%s -</string>
+ <string name="amount_chosen">المبلغ المختار</string>
+ <string name="balances_inbound_label">الوارد</string>
+ <string name="balances_empty_state">لا يوجد عملة رقمية في محفظتك.
+\n
+\nيمكنك فحص المال من المصرف التجريبي:
+\n
+\nhttps://bank.demo.taler.net</string>
+ <string name="transactions_title">المعاملات</string>
+ <string name="transactions_empty">ليس لديك معاملات</string>
+ <string name="google_play_full_desc" tools:keep="@string/google_play_full_desc">هذا التطبيق عبارة عن محفظة لنظام GNU Taler. وهو نظام تجريبي للغاية، وغير جاهز للاستخدام في بيئة الإنتاج لغاية الآن. ونظام GNU Taler هو نظام دفع يحافظ على الخصوصية. يمكن للعملاء عدم الكشف عن هويتهم، لكن لا يمكن للتجار إخفاء دخلهم من خلال المدفوعات باستخدام نظام GNU Taler. هذا يساعد على تجنب التهرب الضريبي وغسيل الأموال. حالة الاستخدام الرئيسية لنظام GNU Taler هي المدفوعات؛ لا يُقصد به أن يكون قيمة مختزنة. تُدعم المدفوعات دائمًا بعملة موجودة. يتم الدفع بعد تحويل الأموال الموجودة إلى نقود إلكترونية بمساعدة شركة الصرافة ، وهي مزود خدمة الدفع لنظام Taler. عند الدفع، يحتاج العملاء فقط إلى محفظة مشحونة. يمكن للتاجر قبول المدفوعات دون إرغام عملائه على التسجيل في موقع التاجر. كما أنّ نظام GNU Taler محصن من العديد من أنواع الاحتيال، مثل التصيد الاحتيالي لمعلومات بطاقة الائتمان أو الاحتيال في استرداد الأموال. وفي حالة الضياع أو السرقة ، قد تختفي فقط كمية محدودة من المال المتبقي في المحفظة.</string>
+ <string name="transactions_empty_search">لم يُعثر على معاملات. جرب بحثًا مختلفًا.</string>
+ <string name="transactions_error">تعذر تحميل المعاملات
+\n
+\n%s</string>
+ <string name="transactions_detail_title">معاملة</string>
+ <string name="transactions_detail_title_balance">الرصيد: %s</string>
+ <string name="transactions_delete">حذف</string>
+ <string name="transactions_select_all">تحديد الكل</string>
+ <string name="transaction_paid">مدفوع</string>
+ <string name="transaction_order_total">المجموع</string>
+ <string name="transaction_order">شراء</string>
+ <string name="transaction_order_id">إيصال #%1$s</string>
+ <string name="transaction_tip_from">إكرامية من %s</string>
+ <string name="transaction_refund">استرداد المال</string>
+ <string name="transaction_refund_from">استرداد الأموال من %s</string>
+ <string name="transaction_pending">معلق</string>
+ <string name="transaction_refresh">رسوم تغيير انتهاء صلاحية العملة</string>
+ <string name="payment_title">دفع</string>
+ <string name="payment_fee">رسوم الدفع +%s</string>
+ <string name="payment_label_order_summary">شراء</string>
+ <string name="payment_error">خطأ: %s</string>
+ <string name="payment_balance_insufficient">رصيد غير كافٍ!</string>
+ <string name="payment_aborted">ملغي</string>
+ <string name="payment_failed">إخفاق</string>
+ <string name="payment_initiated">بدء الدفع</string>
+ <string name="payment_already_paid_title">مدفوع الآن</string>
+ <string name="withdraw_initiated">لقد بدأ السحب</string>
+ <string name="withdraw_title">السحب</string>
+ <string name="withdraw_total">سحب النقود</string>
+ <string name="withdraw_fees">رسوم</string>
+ <string name="withdraw_exchange">شركة الصرافة</string>
+ <string name="withdraw_button_confirm">تأكيد السحب</string>
+ <string name="withdraw_button_confirm_bank">التأكيد مع البنك</string>
+ <string name="withdraw_button_tos">مراجعة الشروط</string>
+ <string name="withdraw_manual_title">حوّل تحويلاً يدويًا لشركة الصرافة</string>
+ <string name="withdraw_amount">ما مقدار المال الذي تريد سحبه؟</string>
+ <string name="withdraw_manual_payment_options">خياراتُ الدفع معتمدة من %1$s:
+\n
+\n%2$s</string>
+ <string name="withdraw_manual_check_fees">تحقق من الرسوم</string>
+ <string name="withdraw_error_title">خطأ في السحب</string>
+ <string name="withdraw_error_test">خطأ في سحب TESTKUDOS</string>
+ <string name="exchange_settings_title">شركات الصرافة</string>
+ <string name="exchange_settings_summary">أدر قائمة شركات الصرافة المعروفة لهذه المحفظة</string>
+ <string name="exchange_list_title">شركات الصرافة</string>
+ <string name="exchange_list_currency">العملة: %s</string>
+ <string name="exchange_list_add">أضف شركة صرافة</string>
+ <string name="exchange_add_url">أدخل عنوان شركة الصرافة</string>
+ <string name="exchange_add_error">تعذر إضافة شركة الصرافة</string>
+ <string name="exchange_menu_manual_withdraw">سحب</string>
+ <string name="exchange_fee_withdrawal_fee_label">رسوم السحب:</string>
+ <string name="exchange_fee_overhead_label">خسارة تقريب الأرقام:</string>
+ <string name="exchange_fee_coin_fees_label">رسوم العملة</string>
+ <string name="exchange_fee_wire_fees_label">رسوم الحوالة</string>
+ <string name="exchange_fee_withdraw_fee">رسوم السحب: %s</string>
+ <string name="exchange_fee_deposit_fee">رسوم الإيداع: %s</string>
+ <string name="exchange_fee_refresh_fee">رسوم التحويل: %s</string>
+ <string name="exchange_fee_refund_fee">رسوم استرداد الأموال: %s</string>
+ <string name="exchange_fee_wire_fee_timespan">الفترة الزمنية: %1$s - %2$s</string>
+ <string name="exchange_fee_wire_fee_wire_fee">رسوم الحوالة: %s</string>
+ <string name="exchange_fee_wire_fee_closing_fee">رسوم العملية المصرفية: %s</string>
+ <string name="exchange_tos_accept">الموافقة على شروط الخدمة</string>
+ <string name="pending_operations_title">عمليات معلقة</string>
+ <string name="pending_operations_refuse">رفض العرض</string>
+ <string name="pending_operations_no_action">(لا تفعل شيئًا)</string>
+ <string name="settings_dev_mode">وضع المُطوّر</string>
+ <string name="settings_withdraw_testkudos">سحب TESTKUDOS</string>
+ <string name="settings_withdraw_testkudos_summary">احصل على المال للاختبار</string>
+ <string name="settings_logcat">سجل تصحيح الأخطاء</string>
+ <string name="settings_version_app">نسخة التطبيق</string>
+ <string name="settings_version_core">النسخة الرئيسية للمحفظة</string>
+ <string name="settings_version_protocol_exchange">إصدارات شركات الصرافة المعتمدة</string>
+ <string name="settings_version_unknown">غير معروف</string>
+ <string name="settings_reset">إعادة تعيين المحفظة (خطر!)</string>
+ <string name="settings_reset_summary">يسحب أموالك</string>
+ <string name="nav_settings_backup">نسخ احتياطي</string>
+ <string name="backup_last">النسخة الاحتياطية الأخيرة: %s</string>
+ <string name="refund_title">استرداد الأموال</string>
+ <string name="refund_success">استُلم المبلغ المسترد: %s</string>
+ <string name="tip_title">نصيحة</string>
+ <string name="wifi_disabled_error">شغّل شبكة Wi-Fi للحصول على خدمة Wi-Fi مجانية</string>
+ <string name="payment_button_confirm">تأكيد الدفع</string>
+ <string name="payment_label_amount_total">المبلغ الكلي:</string>
+ <string name="payment_already_paid">لقد دفعت الآن لعملية الشراء.</string>
+ <string name="withdraw_waiting_confirm">بانتظار الموافقة</string>
+ <string name="withdraw_amount_error">أدخل مبلغًا صحيحًا</string>
+ <string name="withdraw_error_message">لا يمكن تنفيذ السحب الآن. يُرجى المحاولة فيما بعد!</string>
+ <string name="exchange_list_empty">لا توجد شركات صرافة معروفة
+\n
+\nأضف واحدة يدويًا أو اسحب العملة الرقمية!</string>
+ <string name="exchange_fee_coin_expiration_label">أقرب تاريخ انتهاء سريان العملة:</string>
+ <plurals name="exchange_fee_coin">
+ <item quantity="zero">العملة: %s (مستخدمة %d مرة)</item>
+ <item quantity="one">العملة: %s (مستخدمة %d مرة)</item>
+ <item quantity="two">العملتان: %s (المستخدمتين %d مرة)</item>
+ <item quantity="few">العملات: %s (مستخدمة %d مرات)</item>
+ <item quantity="many">العملات: %s (مستخدمة %d مرات)</item>
+ <item quantity="other">العملات: %s (مستخدمة %d مرات)</item>
+ </plurals>
+ <string name="exchange_tos_error">خطأ في عرض شروط الخدمة: %s</string>
+ <string name="settings_dev_mode_summary">يظهر المزيد من المعلومات المعدة لتصحيحها</string>
+ <string name="settings_logcat_summary">معاينة/إرسال السجل الداخلي</string>
+ <string name="settings_version_protocol_merchant">إصدارات التجار المعتمدين</string>
+ <string name="refund_error">خطأ في تنفيذ استرداد الأموال</string>
+ <string name="wifi_connect_error">تعذر الاتصال بشبكة Wi-Fi: %s</string>
+</resources> \ No newline at end of file
diff --git a/wallet/src/main/res/values-de/strings.xml b/wallet/src/main/res/values-de/strings.xml
index a6b3dae..8460f65 100644
--- a/wallet/src/main/res/values-de/strings.xml
+++ b/wallet/src/main/res/values-de/strings.xml
@@ -1,2 +1,151 @@
<?xml version="1.0" encoding="utf-8"?>
-<resources></resources> \ No newline at end of file
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <string name="amount_chosen">Gewählter Betrag</string>
+ <string name="settings_version_app">App-Version</string>
+ <string name="host_apdu_service_desc">Taler NFC-Zahlungen</string>
+ <string name="exchange_settings_title">Taler-Exchanges</string>
+ <string name="balances_inbound_label">Eingehend</string>
+ <string name="or">oder</string>
+ <string name="offline">Internetzugang erforderlich. Bitte stellen Sie sicher, daß Ihre Internetverbindung funktioniert und versuchen Sie es noch einmal.</string>
+ <string name="error_unsupported_uri">Fehler: Dieser Taler-URI wird nicht unterstützt.</string>
+ <string name="menu_settings">Einstellungen</string>
+ <string name="menu_retry_pending_operations">Wiederholen Sie ausstehende Vorgänge</string>
+ <string name="balances_title">Salden</string>
+ <string name="app_name">Taler-Wallet</string>
+ <string name="transactions_title">Transaktionen</string>
+ <string name="transactions_empty">Sie haben keine Transaktionen</string>
+ <string name="transactions_empty_search">Keine Transaktion gefunden. Versuchen Sie eine andere Suche.</string>
+ <string name="transactions_detail_title">Transaktion</string>
+ <string name="transactions_detail_title_balance">Saldo: %s</string>
+ <string name="transactions_delete">Löschen</string>
+ <string name="transactions_select_all">Alle auswählen</string>
+ <string name="transaction_paid">Bezahlt</string>
+ <string name="transaction_order_total">Summe</string>
+ <string name="transaction_order">Einkauf</string>
+ <string name="transaction_order_id">Beleg #%1$s</string>
+ <string name="transaction_refund">Erstattung</string>
+ <string name="transaction_refund_from">Rückerstattung von %s</string>
+ <string name="transaction_pending">AUSSTEHEND</string>
+ <string name="transaction_refresh">Änderungsgebühr für das Ablaufdatum der Münze</string>
+ <string name="payment_title">Zahlung</string>
+ <string name="payment_button_confirm">Zahlung bestätigen</string>
+ <string name="payment_label_amount_total">Gesamtbetrag:</string>
+ <string name="payment_label_order_summary">Einkauf</string>
+ <string name="payment_error">Fehler: %s</string>
+ <string name="payment_balance_insufficient">Saldo unzureichend!</string>
+ <string name="payment_aborted">Abgebrochen</string>
+ <string name="payment_failed">Fehlgeschlagen</string>
+ <string name="payment_initiated">Zahlung eingeleitet</string>
+ <string name="payment_already_paid_title">Bereits bezahlt</string>
+ <string name="payment_already_paid">Sie haben diesen Kauf bereits bezahlt.</string>
+ <string name="withdraw_initiated">Auszahlung eingeleitet</string>
+ <string name="withdraw_title">Abhebung</string>
+ <string name="withdraw_total">Abheben</string>
+ <string name="withdraw_fees">Gebühr</string>
+ <string name="withdraw_exchange">Taler-Exchange</string>
+ <string name="withdraw_button_confirm">Abhebung bestätigen</string>
+ <string name="withdraw_button_confirm_bank">Bestätigen mit</string>
+ <string name="withdraw_button_tos">Nutzungsbedingungen überprüfen</string>
+ <string name="withdraw_waiting_confirm">Warte auf Bestätigung</string>
+ <string name="withdraw_manual_title">Führen Sie eine manuelle Einzahlung an die Geldbörse durch</string>
+ <string name="withdraw_amount">Wie viel soll abgehoben werden\?</string>
+ <string name="withdraw_amount_error">Gültigen Betrag eingeben</string>
+ <string name="withdraw_manual_payment_options">Zahlungsoptionen unterstützt von %1$s:
+\n
+\n%2$s</string>
+ <string name="withdraw_manual_check_fees">Überprüfen Sie die Gebühren</string>
+ <string name="withdraw_error_title">Auszahlungsfehler</string>
+ <string name="withdraw_error_message">Ein Abheben ist derzeit nicht möglich. Bitte zu einem späteren Zeitpunkt erneut versuchen!</string>
+ <string name="withdraw_error_test">Fehler beim Abheben von TESTKUDOS aufgetreten</string>
+ <string name="exchange_settings_summary">Verwalten Sie die Liste der Exchanges, die dieser Geldbörse bekannt sind</string>
+ <string name="payment_fee">+%s Zahlungsgebühr</string>
+ <string name="exchange_list_title">Liste von Exchanges</string>
+ <string name="exchange_list_empty">Keine Exchanges bekannt
+\n
+\nFügen Sie einen Exchange manuell hinzu oder heben Sie digitales Geld ab!</string>
+ <string name="exchange_list_currency">Währung: %s</string>
+ <string name="exchange_list_add">Ein Exchange hinzufügen</string>
+ <string name="exchange_add_error">Exchange konnte nicht hinzugefügt werden</string>
+ <string name="exchange_menu_manual_withdraw">Abheben</string>
+ <string name="exchange_fee_withdrawal_fee_label">Abhebungsgebühr:</string>
+ <string name="exchange_fee_overhead_label">Rundungsverlust:</string>
+ <string name="exchange_fee_coin_expiration_label">Frühester Münzablauf:</string>
+ <string name="exchange_fee_coin_fees_label">Münzgebühren</string>
+ <string name="exchange_fee_wire_fees_label">Übermittlungsgebühren</string>
+ <plurals name="exchange_fee_coin">
+ <item quantity="one">Münze: %s (verwendet %d Mal)</item>
+ <item quantity="other">Münze: %s (verwendet %d Male)</item>
+ </plurals>
+ <string name="exchange_fee_withdraw_fee">Abhebungsgebühr: %s</string>
+ <string name="exchange_fee_deposit_fee">Einzahlungsgebühr: %s</string>
+ <string name="exchange_fee_refresh_fee">Änderungsgebühr: %s</string>
+ <string name="exchange_fee_refund_fee">Rückerstattungsgebühr: %s</string>
+ <string name="exchange_fee_wire_fee_timespan">Zeitspanne: %1$s - %2$s</string>
+ <string name="exchange_fee_wire_fee_wire_fee">Übermittlungsgebühren: %s</string>
+ <string name="exchange_fee_wire_fee_closing_fee">Abschlussgebühr: %s</string>
+ <string name="exchange_tos_accept">Nutzungsbedingungen akzeptieren</string>
+ <string name="pending_operations_title">Schwebende Vorgänge</string>
+ <string name="pending_operations_refuse">Vorschlag ablehnen</string>
+ <string name="pending_operations_no_action">(keine Aktion)</string>
+ <string name="settings_dev_mode">Entwicklermodus</string>
+ <string name="settings_dev_mode_summary">Zeigt weitere Informationen zum Debuggen an</string>
+ <string name="settings_withdraw_testkudos">TESTKUDOS abheben</string>
+ <string name="settings_withdraw_testkudos_summary">Geld für Testen bereitstellen</string>
+ <string name="settings_logcat">Fehlerprotokoll</string>
+ <string name="settings_logcat_summary">Internes Protokoll anzeigen / senden</string>
+ <string name="settings_version_core">Wallet Core-Version</string>
+ <string name="settings_version_protocol_merchant">Unterstützte Händler-Versionen</string>
+ <string name="google_play_short_desc" tools:keep="@string/google_play_short_desc">GNU Taler ermöglicht schnelle und einfache Online-Transaktionen mit Datenschutz.</string>
+ <string name="google_play_title" tools:keep="@string/google_play_title">Taler-Wallet (experimental)</string>
+ <string name="settings_version_unknown">Unbekannt</string>
+ <string name="settings_reset">Brieftasche zurücksetzen (gefährlich!)</string>
+ <string name="settings_reset_summary">Wirft dein Geld weg</string>
+ <string name="nav_settings_backup">Sicherung (Backup)</string>
+ <string name="backup_last">Letzte Sicherung: %s</string>
+ <string name="refund_title">Erstattung</string>
+ <string name="refund_error">Fehler bei der Verarbeitung der Rückerstattung</string>
+ <string name="refund_success">Rückerstattung erhalten: %s</string>
+ <string name="tip_title">Trinkgeld</string>
+ <string name="wifi_disabled_error">Schalten Sie Wi-Fi ein, um kostenloses Wi-Fi zu erhalten</string>
+ <string name="wifi_connect_error">Es konnte keine Verbindung zu kostenlosem WLAN hergestellt werden: %s</string>
+ <string name="google_play_full_desc" tools:keep="@string/google_play_full_desc">Diese App ist eine Geldbörse für GNU Taler (Wallet). Die Anwendung läuft zurzeit in experimentellem Stadium und dient nur zu Demonstrationszwecken. Taler bieten allen Zahlenden ein Bezahlsystem mit Datenschutz. Die Käufer bleiben anonym. Händler müssen dagegen ihr Einkommen vor Auditoren offenlegen. Dadurch werden Steuerhinterziehung und Geldwäsche vermieden. Die Hauptfunktion von GNU Taler ist die Abwicklung von Zahlungsvorgängen; es ist kein Mittel zur Geldaufbewahrung. Alle Zahlungen werden durch eine existierende Währung gedeckt. Zahlungen werden mit Hilfe eines Taler-Exchange durchgeführt, indem die Käufer Geld an den Exchange überweisen und dieser elektronische Zahlungsmittel (Coins) in die Wallets der Käufer überträgt. Um eine Zahlung durchzuführen benötigen Kunden nur ausreichende Coin-Werte im Wallet. Verkäufer können Zahlungen akzeptieren ohne Kunden zur Registrierung auf Webseiten zu zwingen. GNU Taler ist immun gegen viele Formen von Betrug wie z.B. Phishing von Kreditkartendaten oder Betrug durch Rückbuchungen. Im Fall von Verlust oder Diebstahl von Wallets verliert man höchstens den Geldbetrag, welcher sich noch im Wallet befindet.</string>
+ <string name="nav_header_title">GNU Taler</string>
+ <string name="nav_header_subtitle">Wallet</string>
+ <string name="nav_prompt_withdraw">Digitales Bargeld abheben</string>
+ <string name="nav_exchange_tos">Nutzungsbedingungen des Exchange</string>
+ <string name="nav_exchange_select">Exchange auswählen</string>
+ <string name="nav_exchange_fees">Exchange Gebühren</string>
+ <string name="enter_uri">Geben Sie die Taler-URI ein</string>
+ <string name="paste">Einfügen</string>
+ <string name="paste_invalid">Die Zwischenablage enthält einen ungültigen Datentyp</string>
+ <string name="uri_invalid">Keine gültige Taler-URI</string>
+ <string name="ok">OK</string>
+ <string name="cancel">Abbrechen</string>
+ <string name="search">Suche</string>
+ <string name="menu">Menü</string>
+ <string name="nav_error">Fehler</string>
+ <string name="balances_empty_state">In Ihrem Wallet befindet sich kein digitales Geld.
+\n
+\nSie können Testgeld von der Demo-Bank erhalten:
+\n
+\nhttps://bank.demo.taler.net</string>
+ <string name="transactions_error">Transaktionen konnten nicht geladen werden
+\n
+\n%s</string>
+ <string name="exchange_tos_error">Fehler beim Anzeigen der Nutzungsbedingungen: %s</string>
+ <string name="settings_version_protocol_exchange">Unterstützte Exchange-Versionen</string>
+ <string name="amount_negative">- %s</string>
+ <string name="amount_positive">+ %s</string>
+ <string name="transaction_tip_from">Trinkgeld von %s</string>
+ <string name="exchange_add_url">Adresse des Exchange eingeben</string>
+ <string name="button_back">Zurück</string>
+ <string name="button_scan_qr_code">Taler QR Code Einscannen</string>
+ <string name="withdraw_manual_ready_iban">IBAN</string>
+ <string name="withdraw_manual_ready_title">Der Exchange ist für den Abhebevorgang eingerichtet!</string>
+ <string name="withdraw_manual_ready_intro">Um den Abhebevorgang durchführen zu lassen, müssen Sie %s auf das Bankkonto des Exchange überweisen</string>
+ <string name="withdraw_manual_ready_details_intro">Details der Überweisung</string>
+ <string name="withdraw_manual_ready_subject">Überweisungsvermerk</string>
+ <string name="withdraw_manual_ready_bank_button">In Banking-App öffnen</string>
+ <string name="withdraw_manual_ready_cancel">Abhebevorgang abbrechen</string>
+ <string name="withdraw_manual_ready_warning">Bitte vergewissern Sie sich, dass Sie den Überweisungsvermerk richtig angeben, da sonst der überwiesene Betrag nicht im Wallet ankommt.</string>
+</resources> \ No newline at end of file
diff --git a/wallet/src/main/res/values-es/strings.xml b/wallet/src/main/res/values-es/strings.xml
index 078183c..5f68132 100644
--- a/wallet/src/main/res/values-es/strings.xml
+++ b/wallet/src/main/res/values-es/strings.xml
@@ -15,7 +15,7 @@
<string name="or">o</string>
<string name="error_unsupported_uri">Error: Esta URI de Taler no está soportada.</string>
<string name="menu_settings">Ajustes</string>
- <string name="balances_title">Balances</string>
+ <string name="balances_title">Saldo</string>
<string name="amount_positive">+ %s</string>
<string name="amount_chosen">Elegir Cantidad</string>
<string name="balances_inbound_label">entrada</string>
@@ -54,9 +54,9 @@
<string name="withdraw_manual_check_fees">Comisiones por cheque</string>
<string name="withdraw_error_title">Error en la retirada</string>
<string name="withdraw_error_test">Error retirando TESTKUDOS</string>
- <string name="exchange_settings_title">Exchanges</string>
+ <string name="exchange_settings_title">Intercambios</string>
<string name="exchange_settings_summary">Gestionar la lista de exchanges conocidos en esta cartera</string>
- <string name="exchange_list_title">Exchanges</string>
+ <string name="exchange_list_title">Intercambios</string>
<string name="exchange_list_empty">Sin exchanges conocidos
\n
\nAñade uno manualmente para retirar efectivo digital!</string>
@@ -110,7 +110,7 @@
<string name="nav_error">Error</string>
<string name="button_scan_qr_code">Escanear código QR Taler</string>
<string name="uri_invalid">Taler URI no válida</string>
- <string name="offline">La operación requiere acceso a Internet. Por favor asegúrate que tú conexión a Internet funciona y prueba de nuevo.</string>
+ <string name="offline">La operación requiere acceso a Internet. Por favor asegúrate que tu conexión a Internet funcione y prueba de nuevo.</string>
<string name="menu_retry_pending_operations">Reintentar Operaciones Pendientes</string>
<string name="amount_negative">- %s</string>
<string name="balances_empty_state">No hay efectivo digital en tu cartera.
@@ -129,14 +129,15 @@
<string name="payment_initiated">Pago iniciado</string>
<string name="withdraw_button_confirm_bank">Confirmar con el banco</string>
<string name="withdraw_amount_error">Inserta una cantidad válida</string>
- <string name="withdraw_manual_payment_options">"Opciones de pago soportadas por %1$s:
+ <string name="withdraw_manual_payment_options">Opciones de pago soportadas por %1$s:
\n
-\n%2$s"</string>
+\n%2$s</string>
<string name="withdraw_error_message">La retirada ahora mismo no es posible. Por favor prueba de nuevo más tarde!</string>
<string name="exchange_menu_manual_withdraw">Retirar</string>
<plurals name="exchange_fee_coin">
<item quantity="one">Moneda: %s (usada %d vez)</item>
<item quantity="other">Moneda: %s (usadas %d veces)</item>
</plurals>
- <string name="exchange_fee_wire_fee_timespan">Intervalo de tiempo:%1$s - %2$s</string>
-</resources>
+ <string name="exchange_fee_wire_fee_timespan">Intervalo de tiempo: %1$s - %2$s</string>
+ <string name="host_apdu_service_desc">Pagos Taler con NFC</string>
+</resources> \ No newline at end of file
diff --git a/wallet/src/main/res/values-fr/strings.xml b/wallet/src/main/res/values-fr/strings.xml
index 00c97fc..7d260a6 100644
--- a/wallet/src/main/res/values-fr/strings.xml
+++ b/wallet/src/main/res/values-fr/strings.xml
@@ -7,10 +7,6 @@
\n
\nhttps://bank.demo.taler.net</string>
<string name="withdraw_error_message">Pour l\'instant il n\'est pas possible de retirer. Veuillez réessayer plus tard !</string>
- <plurals name="exchange_fee_coin">
- <item quantity="one">Pièce : %s (utilisée %d fois)</item>
- <item quantity="other">Pièce : %s (utilisée %d fois)</item>
- </plurals>
<string name="settings_dev_mode">Mode développeur</string>
<string name="menu_settings">Réglages</string>
<string name="settings_reset">Réinitialiser le portefeuille (dangereux!)</string>
@@ -22,10 +18,10 @@
<string name="amount_positive">+ %s</string>
<string name="amount_chosen">Montant choisi</string>
<string name="balances_inbound_label">entrant</string>
- <string name="transactions_title">Transactions</string>
+ <string name="transactions_title">Opérations de transaction</string>
<string name="transactions_empty">Vous n\'avez aucune transaction</string>
<string name="transactions_error">Impossible de charger les transactions\n\n%s</string>
- <string name="transactions_detail_title">Transaction</string>
+ <string name="transactions_detail_title">Opération de transaction</string>
<string name="transactions_detail_title_balance">Solde : %s</string>
<string name="transactions_delete">Supprimer</string>
<string name="transactions_select_all">Tout sélectionner</string>
@@ -34,7 +30,7 @@
<string name="transaction_order">Acheter</string>
<string name="transaction_order_id">Reçu numéro %1$s</string>
<string name="transaction_refund">Remboursement</string>
- <string name="transaction_refresh">Monnaie obtenue</string>
+ <string name="transaction_refresh">Frais d\'expiration de taux de change</string>
<string name="payment_fee">+%s frais de transaction</string>
<string name="payment_button_confirm">Confirmer le paiement</string>
<string name="nav_exchange_fees">Frais de change</string>
@@ -49,7 +45,7 @@
<string name="payment_balance_insufficient">Solde insuffisant !</string>
<string name="payment_already_paid_title">Déjà payé</string>
<string name="payment_already_paid">Vous avez déjà payé cet achat.</string>
- <string name="withdraw_total">Retrait</string>
+ <string name="withdraw_total">Retirer</string>
<string name="withdraw_fees">Frais</string>
<string name="withdraw_exchange">Change</string>
<string name="withdraw_button_confirm">Confirmer le retrait</string>
@@ -57,7 +53,7 @@
<string name="withdraw_error_title">Erreur de retrait</string>
<string name="exchange_fee_withdrawal_fee_label">Frais de retrait :</string>
<string name="exchange_fee_overhead_label">Erreur d\'arrondi :</string>
- <string name="exchange_fee_coin_fees_label">Frais de pièces</string>
+ <string name="exchange_fee_coin_fees_label">Frais de monnaie</string>
<string name="exchange_fee_wire_fees_label">Frais de virement</string>
<string name="exchange_fee_withdraw_fee">Frais de retrait : %s</string>
<string name="exchange_fee_deposit_fee">Frais de dépôt : %s</string>
@@ -76,9 +72,66 @@
<string name="settings_withdraw_testkudos_summary">Obtenez de l\'argent pour les tests</string>
<string name="settings_reset_summary">Détruit votre argent</string>
<string name="refund_error">Erreur lors du traitement du remboursement</string>
- <string name="refund_success">Remboursement reçu: %s</string>
+ <string name="refund_success">Remboursement reçu : %s</string>
<string name="payment_error">Erreur : %s</string>
- <string name="google_play_full_desc" tools:keep="@string/google_play_full_desc">Cette application est un portefeuille pour GNU Taler. Elle est très expérimentale et n\'est pas encore prête à être utilisée en production. GNU Taler est un système de paiement préservant la vie privée. Les clients peuvent rester anonymes, mais les commerçants ne peuvent pas cacher leurs revenus par des paiements avec GNU Taler. Cela permet d\'éviter l\'évasion fiscale et le blanchiment d\'argent. Le principal cas d\'utilisation de GNU Taler est celui des paiements ; il ne s\'agit pas d\'une réserve de valeur. Les paiements sont toujours garantis par une devise existante. Les paiements sont effectués après avoir échangé l\'argent existant en monnaie électronique avec l\'aide d\'un service d\'échange, c\'est-à-dire un fournisseur de services de paiement pour le Taler. Pour effectuer un paiement, les clients n\'ont besoin que d\'un portefeuille chargé. Un commerçant peut accepter des paiements sans que ses clients ne s\'inscrivent sur son site web. GNU Taler est immunisé contre de nombreux types de fraude, tels que l\'hameçonnage des informations relatives aux cartes de crédit ou la fraude par rétrofacturation. En cas de perte ou de vol, seule la quantité limitée d\'argent restant dans le portefeuille peut disparaître.</string>
+ <string name="google_play_full_desc" tools:keep="@string/google_play_full_desc">Cette application est un portefeuille pour GNU Taler. Elle est très expérimentale et n\'est pas encore prête à être utilisée en production. GNU Taler est un système de paiement préservant la vie privée. Les clients peuvent rester anonymes, mais les commerçants ne peuvent pas cacher leurs revenus avec GNU Taler. Cela permet d\'éviter l\'évasion fiscale et le blanchiment d\'argent. Le principal cas d\'utilisation de GNU Taler est celui des paiements ; il ne s\'agit pas d\'une réserve de valeur. Les paiements sont toujours garantis par une devise existante. Les paiements sont effectués après avoir échangé l\'argent existant en monnaie électronique avec l\'aide d\'un service d\'échange, c\'est-à-dire un fournisseur de services de paiement pour Taler. Pour effectuer un paiement, les clients n\'ont besoin que d\'un portefeuille chargé. Un commerçant peut accepter des paiements sans que ses clients ne s\'inscrivent sur son site web. GNU Taler est immunisé contre de nombreux types de fraude, tels que le hameçonnage des informations relatives aux cartes de crédit ou la fraude par rétro-facturation. En cas de perte ou de vol, seule la quantité limitée d\'argent restant dans le portefeuille peut disparaître.</string>
<string name="payment_label_amount_total">Montant total :</string>
- <string name="exchange_fee_coin_expiration_label">Expiration de la pièce au plus tôt :</string>
-</resources>
+ <string name="exchange_fee_coin_expiration_label">Expiration de monnaie prochaine :</string>
+ <string name="ok">OK</string>
+ <string name="cancel">Annuler</string>
+ <string name="offline">L\'opération nécessite un accès Internet. Veuillez vérifier que votre connection internet fonctionne et réessayez.</string>
+ <string name="error_unsupported_uri">Erreur : Cet URI Taler n\'est pas pris en charge.</string>
+ <string name="transactions_empty_search">Aucune transaction trouvée. Essayez une recherche différente.</string>
+ <string name="transaction_refund_from">Remboursement %s</string>
+ <string name="transaction_pending">EN ATTENTE</string>
+ <string name="payment_title">Paiement</string>
+ <string name="payment_aborted">Abandonné</string>
+ <string name="payment_initiated">Paiement initié</string>
+ <string name="nav_exchange_select">Sélectionnez Change</string>
+ <string name="enter_uri">Entrez l\'URI de Taler</string>
+ <string name="paste">Coller</string>
+ <string name="paste_invalid">Le presse-papiers contient un type de données non valide</string>
+ <string name="uri_invalid">Pas un URI Taler valide</string>
+ <string name="search">Rechercher</string>
+ <string name="menu">Menu</string>
+ <string name="or">ou</string>
+ <string name="transaction_tip_from">Pourboire de %s</string>
+ <string name="payment_failed">Échec</string>
+ <string name="withdraw_initiated">Retrait initié</string>
+ <string name="withdraw_title">Retrait</string>
+ <string name="withdraw_button_confirm_bank">Confirmer avec la banque</string>
+ <string name="withdraw_waiting_confirm">En attente de confirmation</string>
+ <string name="withdraw_amount">Retirer quel montant \?</string>
+ <string name="withdraw_amount_error">Indiquer un montant valide</string>
+ <string name="withdraw_manual_payment_options">Modes de paiement fournis par %1$s :
+\n
+\n%2$s</string>
+ <string name="withdraw_manual_check_fees">Vérifier les frais</string>
+ <string name="host_apdu_service_desc">Paiements Taler CCP</string>
+ <string name="withdraw_manual_title">Effectuer un transfert manuel vers la bourse</string>
+ <string name="exchange_settings_title">Échanges</string>
+ <string name="exchange_settings_summary">Gérer la liste des échanges connus par ce portefeuille</string>
+ <string name="exchange_list_title">Échanges</string>
+ <string name="exchange_list_currency">Devise : %s</string>
+ <string name="exchange_menu_manual_withdraw">Retirer</string>
+ <string name="settings_logcat">Historique des bogues</string>
+ <string name="settings_logcat_summary">Voir/Envoyer un historique interne</string>
+ <string name="settings_version_app">Version mobile</string>
+ <string name="settings_version_unknown">Inconnu</string>
+ <string name="nav_settings_backup">Sauvegarde</string>
+ <string name="backup_last">Dernière sauvegarde : %s</string>
+ <string name="refund_title">Remboursement</string>
+ <string name="tip_title">Pourboire</string>
+ <string name="wifi_disabled_error">Activer le Wi-Fi pour profiter du Wi-Fi gratuit</string>
+ <string name="wifi_connect_error">Connection au Wi-Fi impossible : %s</string>
+ <string name="exchange_list_add">Ajoutez un échange</string>
+ <string name="exchange_add_error">L\'échange n\'a pas pu être ajouté</string>
+ <string name="withdraw_error_test">Erreur de retrait TESTKUDOS</string>
+ <string name="exchange_list_empty">Aucun échange connu
+\n
+\nAjoutez-en un manuellement ou retirer de l\'argent électronique !</string>
+ <string name="exchange_add_url">Entrez l\'adresse de l\'échange</string>
+ <string name="settings_version_protocol_merchant">Versions commerçantes compatibles</string>
+ <string name="settings_version_core">Version Wallet Core</string>
+ <string name="settings_version_protocol_exchange">Versions Echange compatibles</string>
+</resources> \ No newline at end of file
diff --git a/wallet/src/main/res/values-it/strings.xml b/wallet/src/main/res/values-it/strings.xml
new file mode 100644
index 0000000..7f43827
--- /dev/null
+++ b/wallet/src/main/res/values-it/strings.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <string name="app_name">Wallet Taler</string>
+ <string name="google_play_title" tools:keep="@string/google_play_title">Wallet Taler (sperimentale)</string>
+ <string name="google_play_short_desc" tools:keep="@string/google_play_short_desc">GNU Taler rende veloci e agevoli le transazioni online rispettose della privacy.</string>
+ <string name="nav_header_title">GNU Taler</string>
+ <string name="nav_header_subtitle">Wallet</string>
+ <string name="paste_invalid">Gli appunti contengono un tipo di dato non valido</string>
+ <string name="offline">L\'operazione richiede l\'accesso a Internet. Si prega di assicurarsi che la connessione Internet funzioni e riprovare.</string>
+ <string name="balances_empty_state">Non è presente denaro elettronico nel tuo wallet.
+\n
+\nPuoi ottenere denaro di prova dalla banca demo:
+\n
+\nhttps://bank.demo.taler.net</string>
+ <string name="nav_prompt_withdraw">Prelievo di Denaro Elettronico</string>
+ <string name="nav_exchange_tos">Termini di Servizio dello Scambio</string>
+ <string name="nav_exchange_select">Seleziona Scambio</string>
+ <string name="nav_exchange_fees">Tariffe di Scambio</string>
+ <string name="nav_error">Errore</string>
+ <string name="button_back">Indietro</string>
+ <string name="enter_uri">Inserisci URI Taler</string>
+ <string name="uri_invalid">URI Taler non valido</string>
+ <string name="ok">OK</string>
+ <string name="cancel">Cancella</string>
+ <string name="search">Cerca</string>
+ <string name="menu">Menu</string>
+ <string name="or">oppure</string>
+ <string name="error_unsupported_uri">Errore: Questo URI Taler non è supportato.</string>
+ <string name="menu_settings">Impostazioni</string>
+ <string name="menu_retry_pending_operations">Ritentare Operazioni in Corso</string>
+ <string name="host_apdu_service_desc">Pagamenti Taler NFC</string>
+ <string name="balances_title">Saldi</string>
+ <string name="amount_positive">+ %s</string>
+ <string name="amount_negative">- %s</string>
+ <string name="amount_chosen">Quantità Scelta</string>
+ <string name="balances_inbound_label">in arrivo</string>
+ <string name="transactions_title">Transazioni</string>
+ <string name="transactions_empty">Non hai nessuna transazione</string>
+ <string name="paste">Incolla</string>
+ <string name="google_play_full_desc" tools:keep="@string/google_play_full_desc">Quest\'app è un wallet per GNU Taler. È altamente sperimentale, e non è ancora pronta per l\'uso nella produzione. GNU Taler è un sistema di pagamento che preserva la privacy. Gli utenti possono rimanere anonimi, ma i venditori non possono nascondere i loro guadagni tramite i pagamenti con GNU Taler. Questo aiuta ad evitare l\'evasione fiscale e il riciclaggio di denaro sporco. Il caso d\'uso principale di GNU Taler sono i pagamenti; esso non è inteso come un deposito di valuta. I pagamenti sono sempre finanziati da una valuta esistente. I pagamenti sono effettuati dopo aver scambiato del denaro esistente con del denaro elettronico con l\'aiuto di un servizio di Scambio, vale a dire, di un provider di servizi di pagamento per Taler. Quando effettuano un pagamento, gli utenti hanno bisogno soltanto di un wallet carico. Un venditore può accettare pagamenti senza che i suoi clienti si registrino sul suo sito web. GNU Taler è immune da molti tipi di frode, come ad esempio il phishing delle informazioni sulle carte di credito o la frode di storno di addebito. In caso di smarrimento o furto, soltanto la limitata quantità di denaro lasciata nel wallet potrebbe andare perduta.</string>
+ <string name="button_scan_qr_code">Scansiona il Codice QR Taler</string>
+ <string name="transactions_error">Impossibile caricare transazioni
+\n
+\n%s</string>
+ <string name="transactions_detail_title">Transazioni</string>
+ <string name="transactions_detail_title_balance">Saldo: %s</string>
+ <string name="transactions_delete">Elimina</string>
+ <string name="transactions_select_all">Seleziona tutto</string>
+ <string name="transaction_paid">Pagato</string>
+ <string name="transaction_order_total">Totale</string>
+ <string name="transaction_order">Acquisto</string>
+ <string name="transaction_tip_from">Suggerimento di %s</string>
+ <string name="transaction_refund">Rimborso</string>
+ <string name="transaction_refund_from">Rimborso di %s</string>
+ <string name="transaction_pending">In sospeso</string>
+ <string name="payment_title">Pagamento</string>
+ <string name="transactions_empty_search">Nessun transazione trovata. Prova una ricerca diversa.</string>
+ <string name="transaction_order_id">Ricevuta #%1$s</string>
+ <string name="transaction_refresh">Commissione per cambio scadenza moneta</string>
+ <string name="payment_fee">+%s tassa di commissione</string>
+ <string name="payment_button_confirm">Confermare pagamento</string>
+ <string name="payment_already_paid">Hai già pagato questo ordine.</string>
+ <string name="exchange_settings_summary">Gestire la lista di cambi conosciuti in questo portafoglio</string>
+ <string name="exchange_add_error">Non è stato possibile aggiungere il cambio</string>
+ <plurals name="exchange_fee_coin">
+ <item quantity="one">Moneta: %s (usata %d volte)</item>
+ <item quantity="other">Moneta: %s (usate %d volte)</item>
+ </plurals>
+ <string name="settings_withdraw_testkudos">Ritirare TESTKUDOS</string>
+ <string name="settings_logcat_summary">Vedere/Inviare log interno</string>
+ <string name="settings_reset_summary">Scarta il tuo denaro</string>
+ <string name="wifi_disabled_error">Accendi il Wi-Fi per avere Wi-Fi gratis</string>
+ <string name="payment_label_amount_total">Quantità totale:</string>
+ <string name="payment_label_order_summary">Ordine</string>
+ <string name="payment_error">Errore: %s</string>
+ <string name="payment_balance_insufficient">Credito insufficiente!</string>
+ <string name="payment_aborted">Interrotto</string>
+ <string name="payment_failed">Fallito</string>
+ <string name="payment_initiated">Pagamento iniziato</string>
+ <string name="payment_already_paid_title">Pagato già</string>
+ <string name="withdraw_initiated">Ritiro iniziato</string>
+ <string name="withdraw_title">Ritiro</string>
+ <string name="withdraw_total">Ritirare</string>
+ <string name="withdraw_fees">Tassa</string>
+ <string name="withdraw_exchange">Cambio</string>
+ <string name="withdraw_button_confirm">Confermare ritiro</string>
+ <string name="withdraw_button_confirm_bank">Confermare con la banca</string>
+ <string name="withdraw_button_tos">Rivedere i termini</string>
+ <string name="withdraw_waiting_confirm">In attesa di conferma</string>
+ <string name="withdraw_amount">Quanto desideri ritirare\?</string>
+ <string name="withdraw_amount_error">Inserisci una quantità valida</string>
+ <string name="withdraw_manual_payment_options">Opzioni di pagamento sostenute da %1$s
+\n
+\n%2$s</string>
+ <string name="withdraw_manual_check_fees">Tassa di controllo</string>
+ <string name="withdraw_error_title">Errore nel ritiro</string>
+ <string name="withdraw_error_message">Il ritiro non è attualmente possibile. Per piacere riprova più tardi!</string>
+ <string name="withdraw_error_test">Errore nel ritiro TESTKUDOS</string>
+ <string name="exchange_settings_title">Cambi</string>
+ <string name="exchange_list_title">Cambi</string>
+ <string name="exchange_list_empty">Nessun cambio riconosciuto
+\n
+\nInseriscilo manualmente o ritira dei contanti digitali!</string>
+ <string name="exchange_list_currency">Valuta: %s</string>
+ <string name="exchange_list_add">Aggiungere cambio</string>
+ <string name="exchange_add_url">Inserire l\'indirizzo del cambio</string>
+ <string name="exchange_menu_manual_withdraw">Ritirare</string>
+ <string name="exchange_fee_withdrawal_fee_label">Tassa di commissione:</string>
+ <string name="exchange_fee_overhead_label">Perdita arrotondata:</string>
+ <string name="exchange_fee_coin_expiration_label">Prima scadenza monetaria:</string>
+ <string name="exchange_fee_coin_fees_label">Tassa monetaria</string>
+ <string name="exchange_fee_wire_fees_label">Commissione di bonifico</string>
+ <string name="exchange_fee_withdraw_fee">Tassa di commissione: %s</string>
+ <string name="exchange_fee_deposit_fee">Tassa di deposito: %s</string>
+ <string name="exchange_fee_refresh_fee">Tassa di cambio: %s</string>
+ <string name="exchange_fee_refund_fee">Tassa di rimborso: %s</string>
+ <string name="exchange_fee_wire_fee_timespan">Intervallo di tempo: %1$s - %2$s</string>
+ <string name="exchange_fee_wire_fee_wire_fee">Tassa di bonifico: %s</string>
+ <string name="exchange_fee_wire_fee_closing_fee">Tassa di chiusura: %s</string>
+ <string name="exchange_tos_accept">Accettare i termini di servizio</string>
+ <string name="exchange_tos_error">Errore nel mostrare i Termini di servizio: %s</string>
+ <string name="pending_operations_title">Operazioni in attesa</string>
+ <string name="pending_operations_refuse">Rifiutare la richiesta</string>
+ <string name="pending_operations_no_action">(nessuna azione)</string>
+ <string name="settings_dev_mode">Modo Sviluppatore</string>
+ <string name="settings_dev_mode_summary">Mostra più informazioni riguardo la correzione</string>
+ <string name="settings_withdraw_testkudos_summary">Ottieni denaro per provare</string>
+ <string name="settings_logcat">Log di debug</string>
+ <string name="settings_version_app">Versione App</string>
+ <string name="settings_version_core">Portafoglio Versione Principale</string>
+ <string name="settings_version_protocol_exchange">Supportate Versioni Exchange</string>
+ <string name="settings_version_protocol_merchant">Supportate Versioni Commerciali</string>
+ <string name="settings_version_unknown">Sconosciuto</string>
+ <string name="settings_reset">Resettare il portafoglio (Pericoloso!)</string>
+ <string name="nav_settings_backup">Backup</string>
+ <string name="backup_last">Ultimo Backup: %s</string>
+ <string name="refund_title">Rimborso</string>
+ <string name="refund_error">Errore nel processare il rimborso</string>
+ <string name="refund_success">Rimborso ricevuto: %s</string>
+ <string name="tip_title">Consiglio</string>
+ <string name="wifi_connect_error">Impossibile connettersi alla Wi-Fi gratuita: %s</string>
+ <string name="withdraw_manual_title">Effettuare un trasferimento manuale per lo scambio</string>
+</resources> \ No newline at end of file
diff --git a/wallet/src/main/res/values-ko/strings.xml b/wallet/src/main/res/values-ko/strings.xml
new file mode 100644
index 0000000..e050aba
--- /dev/null
+++ b/wallet/src/main/res/values-ko/strings.xml
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <string name="app_name">탈러 지갑</string>
+ <string name="google_play_title" tools:keep="@string/google_play_title">탈러 지갑 (실험판)</string>
+ <string name="google_play_short_desc" tools:keep="@string/google_play_short_desc">GNU 탈러는 사생활 보호가 되는 온라인 거래를 빠르고 쉽게 만듭니다.</string>
+ <string name="google_play_full_desc" tools:keep="@string/google_play_full_desc">이 어플리케이션은 GNU 탈러를 위한 지갑입니다. 실험적이며 아직 상용화로는 준비되어 있지 않습니다. GNU 탈러는 사생활 보호를 해주는 결제 시스템입니다. 고객은 익명성을 유지할 수 있지만, 상인은 GNU 탈러를 통한 결제로 얻은 수익을 숨길 수 없습니다. 이는 탈세와 돈세탁을 예방해줍니다. GNU 탈러의 주 용도는 결제입니다; 가치를 저장하는 용도가 아닙니다. 결제는 언제나 현존하는 통화로 뒷받침되어 있습니다. 결제는 거래소, 즉 탈러를 위한 결제 서비스 제공자의 도움으로 실존하는 화폐에서 전자화폐로 교환한 이후 결제가 진행됩니다. 결제 시, 고객은 충전된 지갑만 있으면 됩니다. 상인은 고객이 상인의 사이트에 등록하지 않고도 결제를 하도록 승인할 수 있습니다. GNU 탈러는 신용카드 정보 피싱 혹은 지불거절 사기 등 여러 종류의 사기에 면역이 되어 있습니다. 분실 혹은 도난의 경우, 지갑에 남아있던 제한적인 금액의 돈만 잃게 됩니다.</string>
+ <string name="payment_button_confirm">결제 확인</string>
+ <string name="payment_balance_insufficient">잔액 부족!</string>
+ <string name="withdraw_exchange">교환</string>
+ <string name="withdraw_manual_title">거래소에 수동으로 이체</string>
+ <string name="nav_header_title">GNU 탈러</string>
+ <string name="nav_header_subtitle">지갑</string>
+ <string name="nav_prompt_withdraw">디지털 현금 인출</string>
+ <string name="nav_exchange_tos">거래소의 서비스 약관</string>
+ <string name="nav_exchange_select">거래소 선택</string>
+ <string name="nav_exchange_fees">교환 수수료</string>
+ <string name="nav_error">오류</string>
+ <string name="button_back">뒤로 가기</string>
+ <string name="button_scan_qr_code">탈러 QR 코드를 스캔합니다</string>
+ <string name="enter_uri">탈러 URI 입력</string>
+ <string name="paste">붙여넣기</string>
+ <string name="paste_invalid">클립 보드에 잘못된 데이터 유형이 있습니다</string>
+ <string name="uri_invalid">유효한 탈러 URI가 아닙니다</string>
+ <string name="ok">네</string>
+ <string name="cancel">취소</string>
+ <string name="search">검색</string>
+ <string name="menu">메뉴</string>
+ <string name="or">혹은</string>
+ <string name="offline">작동하려면 인터넷 연결이 필요합니다. 인터넷이 연결되어 있는지 확인 후 다시 시도하세요.</string>
+ <string name="error_unsupported_uri">오류: 이 탈러 URI는 지원되지 않습니다.</string>
+ <string name="menu_settings">설정</string>
+ <string name="menu_retry_pending_operations">보류중인 작업 재시도</string>
+ <string name="host_apdu_service_desc">탈러 NFC 결제</string>
+ <string name="balances_title">잔고</string>
+ <string name="amount_positive">+ %s</string>
+ <string name="amount_negative">- %s</string>
+ <string name="amount_chosen">선택한 금액</string>
+ <string name="balances_inbound_label">인바운드(도착)</string>
+ <string name="balances_empty_state">지갑에 디지털 현금이 없습니다.
+\n
+\n데모 은행에서 테스트 금액을 받을 수 있습니다.
+\n
+\nhttps://bank.demo.taler.net</string>
+ <string name="transactions_title">거래</string>
+ <string name="transactions_empty">거래가 없습니다</string>
+ <string name="transactions_empty_search">거래를 찾을 수 없습니다. 다르게 검색해보세요.</string>
+ <string name="transactions_error">거래를 불러올 수 없습니다
+\n
+\n%s</string>
+ <string name="transactions_detail_title">거래</string>
+ <string name="transactions_detail_title_balance">잔액 : %s</string>
+ <string name="transactions_delete">삭제</string>
+ <string name="transactions_select_all">모두 선택</string>
+ <string name="transaction_paid">결제완료</string>
+ <string name="transaction_order_total">합계</string>
+ <string name="transaction_order">구매</string>
+ <string name="transaction_order_id">영수증 #%1$s</string>
+ <string name="transaction_tip_from">%s의 팁</string>
+ <string name="transaction_refund">환불</string>
+ <string name="transaction_refund_from">%s로부터 환불</string>
+ <string name="transaction_pending">대기중</string>
+ <string name="transaction_refresh">코인 만료 변경 수수료</string>
+ <string name="payment_title">결제</string>
+ <string name="payment_fee">+%s 결제 수수료</string>
+ <string name="payment_label_amount_total">총 금액:</string>
+ <string name="payment_label_order_summary">구매</string>
+ <string name="payment_error">오류: %s</string>
+ <string name="payment_aborted">중단</string>
+ <string name="payment_failed">실패</string>
+ <string name="payment_initiated">결제 시작</string>
+ <string name="payment_already_paid_title">이미 지불</string>
+ <string name="payment_already_paid">이 구매에 대해 이미 결제하셨습니다.</string>
+ <string name="withdraw_initiated">출금 시작됨</string>
+ <string name="withdraw_title">출금</string>
+ <string name="withdraw_total">출금</string>
+ <string name="withdraw_fees">수수료</string>
+ <string name="withdraw_button_confirm">취소 확인</string>
+ <string name="withdraw_button_confirm_bank">은행과 확인</string>
+ <string name="withdraw_button_tos">조건 살펴보기</string>
+ <string name="withdraw_waiting_confirm">승인 대기 중</string>
+ <string name="withdraw_amount">얼마를 출금할까요\?</string>
+ <string name="withdraw_amount_error">유효한 금액을 입력하세요</string>
+ <string name="withdraw_manual_payment_options">%1$s이(가) 지원하는 결제 옵션:
+\n
+\n%2$s</string>
+ <string name="withdraw_manual_check_fees">수수료 확인</string>
+ <string name="withdraw_error_title">출금 오류</string>
+ <string name="withdraw_error_message">현재 인출이 불가능합니다. 다시 시도해주세요!</string>
+ <string name="withdraw_error_test">TESTKUDOS 인출 오류</string>
+ <string name="exchange_settings_title">거래소</string>
+ <string name="exchange_settings_summary">이 지갑이 알고 있는 거래소 목록 관리</string>
+ <string name="exchange_list_title">거래소</string>
+ <string name="exchange_list_empty">인식된 거래소 없음
+\n
+\n수동으로 추가하거나 디지털 현금을 인출하세요!</string>
+ <string name="exchange_list_currency">통화: %s</string>
+ <string name="exchange_list_add">거래소 추가</string>
+ <string name="exchange_add_url">거래소 주소 입력</string>
+ <string name="exchange_add_error">거래소를 추가할 수 없음</string>
+ <string name="exchange_menu_manual_withdraw">인출</string>
+ <string name="exchange_fee_withdrawal_fee_label">인출 수수료:</string>
+ <string name="exchange_fee_overhead_label">반올림으로 인한 손실:</string>
+ <string name="exchange_fee_coin_expiration_label">가장 이른 코인 만료일:</string>
+ <string name="exchange_fee_coin_fees_label">코인 수수료</string>
+ <string name="exchange_fee_wire_fees_label">송금 수수료</string>
+ <plurals name="exchange_fee_coin">
+ <item quantity="other">코인: %s (%d번 사용됨)</item>
+ </plurals>
+ <string name="exchange_fee_withdraw_fee">인출 수수료: %s</string>
+ <string name="exchange_fee_deposit_fee">입금 수수료: %s</string>
+ <string name="exchange_fee_refresh_fee">변경 수수료: %s</string>
+ <string name="exchange_fee_refund_fee">환불 수수료: %s</string>
+ <string name="exchange_fee_wire_fee_timespan">기간: %1$s - %2$s</string>
+ <string name="exchange_fee_wire_fee_wire_fee">송금 수수료: %s</string>
+ <string name="exchange_fee_wire_fee_closing_fee">마감 수수료: %s</string>
+ <string name="exchange_tos_accept">서비스 약관 동의</string>
+ <string name="exchange_tos_error">서비스 약관 표시 오류: %s</string>
+ <string name="pending_operations_title">보류중인 작업</string>
+ <string name="pending_operations_refuse">제안 거부</string>
+ <string name="pending_operations_no_action">(작업 없음)</string>
+ <string name="settings_dev_mode">개발자 모드</string>
+ <string name="settings_withdraw_testkudos">TESTKUDOS 출금</string>
+ <string name="settings_withdraw_testkudos_summary">테스트를 위한 자금 얻기</string>
+ <string name="settings_logcat">디버그 로그</string>
+ <string name="settings_logcat_summary">내부 로그 보기/보내기</string>
+ <string name="settings_version_app">앱 버전</string>
+ <string name="settings_version_core">지갑 코어 버전</string>
+ <string name="settings_version_protocol_merchant">지원되는 상인 버전</string>
+ <string name="settings_version_unknown">알 수 없음</string>
+ <string name="settings_reset">지갑 재설정 (위험!)</string>
+ <string name="settings_reset_summary">당신의 돈을 버립니다</string>
+ <string name="nav_settings_backup">백업</string>
+ <string name="backup_last">마지막 백업: %s</string>
+ <string name="refund_title">환불</string>
+ <string name="refund_success">받은 환불: %s</string>
+ <string name="tip_title">팁</string>
+ <string name="wifi_connect_error">무료 Wi-Fi에 연결할 수 없습니다: %s</string>
+ <string name="settings_dev_mode_summary">디버깅을 위한 추가 정보가 표시됩니다</string>
+ <string name="settings_version_protocol_exchange">지원되는 거래소 버전</string>
+ <string name="refund_error">환불 처리 오류</string>
+ <string name="wifi_disabled_error">무료 Wi-Fi를 사용하기 위해 Wi-Fi를 켜세요</string>
+</resources> \ No newline at end of file
diff --git a/wallet/src/main/res/values-pt/strings.xml b/wallet/src/main/res/values-pt/strings.xml
new file mode 100644
index 0000000..8bb29bf
--- /dev/null
+++ b/wallet/src/main/res/values-pt/strings.xml
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <string name="tip_title">Gorjeta</string>
+ <string name="google_play_short_desc" tools:keep="@string/google_play_short_desc">A GNU Taler facilita e acelera transações online que respeitem a privacidade.</string>
+ <string name="nav_header_title">GNU Taler</string>
+ <string name="nav_header_subtitle">Wallet</string>
+ <string name="nav_prompt_withdraw">Sacar Dinheiro Eletrónico</string>
+ <string name="nav_exchange_tos">Termos de Serviço do Câmbio</string>
+ <string name="nav_exchange_select">Selecionar Câmbio</string>
+ <string name="nav_exchange_fees">Taxas de Câmbio</string>
+ <string name="nav_error">Erro</string>
+ <string name="button_back">Voltar</string>
+ <string name="button_scan_qr_code">Digitalizar o Código QR da Taler</string>
+ <string name="enter_uri">Inserir URI Taler</string>
+ <string name="paste">Colar</string>
+ <string name="paste_invalid">A Área de Transferência contém um tipo de dados inválido</string>
+ <string name="uri_invalid">URI Taler inválido</string>
+ <string name="ok">OK</string>
+ <string name="cancel">Cancelar</string>
+ <string name="search">Pesquisar</string>
+ <string name="or">ou</string>
+ <string name="error_unsupported_uri">Erro: Este URI Taler não é suportado.</string>
+ <string name="menu_settings">Configurações</string>
+ <string name="menu_retry_pending_operations">Repetir Operações em Espera</string>
+ <string name="host_apdu_service_desc">Pagamentos Taler NFC</string>
+ <string name="balances_title">Saldos</string>
+ <string name="amount_positive">+ %s</string>
+ <string name="amount_negative">- %s</string>
+ <string name="amount_chosen">Quantidade Selecionada</string>
+ <string name="balances_inbound_label">recebido</string>
+ <string name="transactions_title">Transações</string>
+ <string name="transactions_empty">Não tem quaisquer transações</string>
+ <string name="transactions_empty_search">Não foram encontradas transações. Tente outra pesquisa.</string>
+ <string name="transactions_error">Não foi possível carregar as transações
+\n
+\n%s</string>
+ <string name="transactions_detail_title">Transação</string>
+ <string name="transactions_detail_title_balance">Saldo: %s</string>
+ <string name="transactions_delete">Eliminar</string>
+ <string name="transactions_select_all">Selecionar Tudo</string>
+ <string name="transaction_paid">Pagas</string>
+ <string name="transaction_order_total">Total</string>
+ <string name="transaction_order">Compra</string>
+ <string name="transaction_order_id">Recibo #%1$s</string>
+ <string name="transaction_refund">Reembolso</string>
+ <string name="transaction_refund_from">Reembolso de %s</string>
+ <string name="transaction_refresh">Taxa de mudança de validade da moeda</string>
+ <string name="payment_title">Pagamento</string>
+ <string name="payment_button_confirm">Confirmar Pagamento</string>
+ <string name="payment_label_amount_total">Valor Total:</string>
+ <string name="payment_label_order_summary">Compra</string>
+ <string name="payment_error">Erro: %s</string>
+ <string name="payment_balance_insufficient">Saldo insuficiente!</string>
+ <string name="payment_aborted">Anulado</string>
+ <string name="payment_failed">Falhou</string>
+ <string name="payment_initiated">Pagamento iniciado</string>
+ <string name="payment_already_paid">Já pagou esta compra.</string>
+ <string name="withdraw_initiated">Saque iniciado</string>
+ <string name="withdraw_title">Saque</string>
+ <string name="withdraw_total">Sacar</string>
+ <string name="withdraw_fees">Taxa</string>
+ <string name="withdraw_exchange">Câmbio</string>
+ <string name="withdraw_button_confirm">Confirmar Saque</string>
+ <string name="withdraw_button_confirm_bank">Confirmar com o banco</string>
+ <string name="withdraw_button_tos">Rever Termos</string>
+ <string name="withdraw_amount_error">Insira uma quantia válida</string>
+ <string name="withdraw_manual_check_fees">Verificar taxas</string>
+ <string name="withdraw_error_title">Erro ao Sacar</string>
+ <string name="withdraw_error_test">Erro ao sacar TESTKUDOS</string>
+ <string name="exchange_list_title">Câmbios</string>
+ <string name="exchange_list_empty">Não há câmbios associados
+\n
+\nAdicione um manualmente ou saque dinheiro virtual!</string>
+ <string name="exchange_list_currency">Moeda: %s</string>
+ <string name="exchange_list_add">Adicionar câmbio</string>
+ <string name="exchange_add_url">Inserir endereço do câmbio</string>
+ <string name="exchange_menu_manual_withdraw">Sacar</string>
+ <string name="exchange_fee_withdrawal_fee_label">Taxa de Saque:</string>
+ <string name="exchange_fee_overhead_label">Perda de Arredondamento:</string>
+ <string name="exchange_fee_coin_expiration_label">Primeira Moeda a Expirar:</string>
+ <string name="exchange_fee_coin_fees_label">Taxas de Moeda</string>
+ <string name="exchange_fee_wire_fees_label">Taxas de Transferência</string>
+ <string name="exchange_fee_withdraw_fee">Taxa de Saque: %s</string>
+ <string name="exchange_fee_deposit_fee">Taxa de Depósito: %s</string>
+ <string name="exchange_fee_refresh_fee">Taxa de Alteração: %s</string>
+ <string name="exchange_fee_refund_fee">Taxa de Reembolso: %s</string>
+ <string name="exchange_fee_wire_fee_timespan">Prazo de vigência: %1$s - %2$s</string>
+ <string name="exchange_fee_wire_fee_wire_fee">Taxa de Transferência: %s</string>
+ <string name="exchange_fee_wire_fee_closing_fee">Taxa de Fechamento: %s</string>
+ <string name="exchange_tos_error">Erro ao mostrar os Termos de Serviço: %s</string>
+ <string name="pending_operations_refuse">Recusar Proposta</string>
+ <string name="settings_logcat_summary">Ver/enviar registro interno</string>
+ <string name="settings_version_app">Versão da Aplicação</string>
+ <string name="settings_version_protocol_exchange">Versões Suportadas de Câmbio</string>
+ <string name="settings_version_protocol_merchant">Versões Suportadas do Merchant</string>
+ <string name="settings_version_unknown">Desconhecido</string>
+ <string name="nav_settings_backup">Cópia de Segurança</string>
+ <string name="backup_last">Última Cópia de Segurança: %s</string>
+ <string name="refund_title">Reembolso</string>
+ <string name="refund_success">Reembolso recebido: %s</string>
+ <string name="menu">Menu</string>
+ <string name="pending_operations_no_action">(sem ação)</string>
+ <string name="settings_dev_mode">Modo de Programador</string>
+ <string name="settings_dev_mode_summary">Exibe mais informação destinada a depuração</string>
+ <string name="settings_withdraw_testkudos">Sacar TESTKUDOS</string>
+ <string name="settings_withdraw_testkudos_summary">Receber dinheiro para testes</string>
+ <string name="settings_logcat">Registro de Depuração</string>
+ <string name="settings_version_core">Versão Base do Wallet</string>
+ <string name="settings_reset">Repor Wallet (perigoso!)</string>
+ <string name="app_name">Taler Wallet</string>
+ <string name="google_play_title" tools:keep="@string/google_play_title">Taler Wallet (experimental)</string>
+ <string name="google_play_full_desc" tools:keep="@string/google_play_full_desc">Esta aplicação é uma carteira para a GNU Taler e está numa fase altamente experimental, não estando pronta para o uso geral. A GNU Taler é um sistema de pagamentos que preserva a privacidade. Os Clientes podem manter o seu anonimato, mas os comerciantes não podem esconder o seu rendimento através de pagamentos com a GNU Taler. Isto ajuda a prevenir a evasão fiscal e a lavagem de dinheiro. A GNU Taler é para ser usada principalmente para pagamentos e não como uma reserva de valor. Pagamentos são sempre garantidos por uma moeda existente e feitos após a troca de dinheiro existente por dinheiro eletrónico através de um serviço de Câmbio, ou seja, um prestador de serviços de pagamento para a Taler. Quando fazem um pagamento, os clientes apenas precisam de uma carteira com dinheiro. Um comerciante pode aceitar pagamentos sem obrigar os seus clientes a se inscreverem no seu Website. A GNU Taler é imune contra diversos tipos de fraude, como phishing de dados de cartões de crédito ou fraude de estorno. Em casos de perda ou roubo, apenas há a possibilidade de perda da pequena quantidade de dinheiro que ainda estiver na carteira.</string>
+ <string name="offline">Operação requer acesso à internet. Por favor verifique a sua ligação à internet e tente novamente.</string>
+ <string name="balances_empty_state">Não há dinheiro virtual na sua carteira.
+\n
+\nPode adquirir dinheiro de teste do banco de demonstração:
+\n
+\nhttps://bank.demo.taler.net</string>
+ <string name="transaction_tip_from">Gorjeta de %s</string>
+ <string name="transaction_pending">PENDENTE</string>
+ <string name="payment_fee">+%s taxa de pagamento</string>
+ <string name="payment_already_paid_title">Pagamento já realizado</string>
+ <string name="withdraw_waiting_confirm">Aguardando confirmação</string>
+ <string name="withdraw_amount">Quanto quer sacar\?</string>
+ <string name="withdraw_manual_payment_options">Opções de pagamento suportados por %1$s:
+\n
+\n%2$s</string>
+ <string name="withdraw_error_message">De momento não é possível sacar a quantia. Por favor tente novamente mais tarde!</string>
+ <string name="exchange_settings_title">Câmbios</string>
+ <string name="exchange_settings_summary">Gerir lista de câmbios associados a esta carteira</string>
+ <string name="exchange_add_error">Não foi possível adicionar câmbio</string>
+ <plurals name="exchange_fee_coin">
+ <item quantity="one">Moeda: %s (usada %d vez)</item>
+ <item quantity="other">Moeda: %s (usada %d vezes)</item>
+ </plurals>
+ <string name="exchange_tos_accept">Aceitar os Termos de Serviço</string>
+ <string name="pending_operations_title">Operações em Espera</string>
+ <string name="refund_error">Erro ao processar reembolso</string>
+ <string name="settings_reset_summary">Perde o seu dinheiro</string>
+ <string name="wifi_disabled_error">Ligue a Wi-Fi para receber Wi-fi gratuita</string>
+ <string name="wifi_connect_error">Não foi possível ligar a Wi-Fi gratuita: %s</string>
+</resources> \ No newline at end of file
diff --git a/wallet/src/main/res/values-ru/strings.xml b/wallet/src/main/res/values-ru/strings.xml
new file mode 100644
index 0000000..c9a82ff
--- /dev/null
+++ b/wallet/src/main/res/values-ru/strings.xml
@@ -0,0 +1,145 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <string name="ok">OK</string>
+ <string name="menu_settings">Настройки</string>
+ <string name="nav_header_subtitle">Кошелёк</string>
+ <string name="nav_prompt_withdraw">Вывести электронные наличные деньги</string>
+ <string name="nav_error">Ошибка</string>
+ <string name="button_back">Вернитесь назад</string>
+ <string name="button_scan_qr_code">Сканировать Taler QR-код</string>
+ <string name="enter_uri">Введите Taler URI</string>
+ <string name="paste">Вставить</string>
+ <string name="uri_invalid">Неверный Taler URI</string>
+ <string name="cancel">Отменить</string>
+ <string name="search">Поиск</string>
+ <string name="menu">Меню</string>
+ <string name="or">или</string>
+ <string name="offline">Для работы системы необходимо подключиться к Интернету. Пожалуйста, убедитесь, что у вас есть соединение с Интернетом, и попробуйте еще раз.</string>
+ <string name="error_unsupported_uri">Ошибка: данный Taler URI не поддерживается.</string>
+ <string name="menu_retry_pending_operations">Повторить незавершённые операции</string>
+ <string name="host_apdu_service_desc">Taler NFC-платежи</string>
+ <string name="amount_positive">+ %s</string>
+ <string name="balances_title">Балансы</string>
+ <string name="amount_chosen">Выбранная сумма</string>
+ <string name="balances_inbound_label">входящая</string>
+ <string name="app_name">Taler Кошелёк</string>
+ <string name="transactions_title">Транзакции</string>
+ <string name="transactions_empty">У вас нет транзакций</string>
+ <string name="transactions_empty_search">Не удалось найти транзакции. Попробуйте новый поиск.</string>
+ <string name="transactions_error">Не удалось загрузить транзакции
+\n
+\n%s</string>
+ <string name="transactions_detail_title">Транзакция</string>
+ <string name="google_play_title" tools:keep="@string/google_play_title">Taler Кошелёк (экспериментальное приложение)</string>
+ <string name="nav_header_title">GNU Taler</string>
+ <string name="transactions_detail_title_balance">Баланс: %s</string>
+ <string name="transactions_select_all">Выбрать все</string>
+ <string name="transaction_paid">Оплачено</string>
+ <string name="balances_empty_state">В вашем кошельке нет электронных наличных денег.
+\n
+\nВы можете получить тестовые деньги в нашем демо-банке:
+\n
+\nhttps://bank.demo.taler.net</string>
+ <string name="transaction_order_id">Чек #%1$s</string>
+ <string name="transaction_order">Покупка</string>
+ <string name="transaction_order_total">Итого</string>
+ <string name="transactions_delete">Удалить</string>
+ <string name="transaction_refund">Возврат</string>
+ <string name="payment_error">Ошибка: %s</string>
+ <string name="payment_title">Платёж</string>
+ <string name="payment_button_confirm">Подтвердить платёж</string>
+ <string name="payment_label_amount_total">Общая сумма:</string>
+ <string name="payment_balance_insufficient">Недостаточно средств на балансе!</string>
+ <string name="nav_exchange_tos">Условия оказания услуг биржей</string>
+ <string name="nav_exchange_select">Выбрать биржу</string>
+ <string name="nav_exchange_fees">Комиссионные сборы биржи</string>
+ <string name="paste_invalid">Неверный тип данных в буфере обмена</string>
+ <string name="amount_negative">- %s</string>
+ <string name="transaction_tip_from">Вознаграждение от %s</string>
+ <string name="transaction_refund_from">Возврат от %s</string>
+ <string name="payment_fee">+%s комиссия за платёж</string>
+ <string name="transaction_refresh">Комиссия за обмен монет с истёкшим сроком годности</string>
+ <string name="google_play_short_desc" tools:keep="@string/google_play_short_desc">GNU Taler помогает осуществлять удобные и безопасные онлайн-транзакции быстро и просто.</string>
+ <string name="exchange_add_error">Не удалось добавить биржу</string>
+ <string name="exchange_fee_coin_fees_label">Комиссионные сборы за монеты</string>
+ <string name="payment_already_paid">Вы уже оплатили эту покупку.</string>
+ <string name="withdraw_exchange">Биржа</string>
+ <string name="withdraw_fees">Комиссия</string>
+ <string name="payment_aborted">Платёж отозван</string>
+ <string name="payment_failed">Платёж не прошёл</string>
+ <string name="payment_initiated">Платёж инициирован</string>
+ <string name="payment_already_paid_title">Уже оплачено</string>
+ <string name="withdraw_initiated">Вывод средств инициирован</string>
+ <string name="withdraw_title">Вывод средств</string>
+ <string name="withdraw_button_confirm">Подтвердить вывод средств</string>
+ <string name="withdraw_total">Вывести средства</string>
+ <string name="withdraw_manual_check_fees">Проверить комиссионные сборы</string>
+ <string name="exchange_fee_wire_fees_label">Комиссионные сборы за перевод</string>
+ <string name="google_play_full_desc" tools:keep="@string/google_play_full_desc">Это приложение для кошелька GNU Taler. Его можно протестировать, но оно пока не готово к использованию. GNU Taler - платёжная система, обеспечивающая сохранность конфиденциальности персональных данных. Покупатели могут оставаться анонимными, а продавцы не могут скрывать информацию о своих доходах посредством платежей через GNU Taler. Это помогает избежать уклонения от уплаты налогов и отмывания денег. Основное назначение GNU Taler - это работа с платежами; система не предназначена для накопления средств. Система поддерживает платежи в любой существующей валюте. Платежи осуществляются после того, как текущие деньги обменены на электронные через биржу, - поставщика услуг по обмену электронной валюты для Taler. Покупателям всего лишь нужно пополнить свой кошелёк для осуществления платежа. Покупателям не нужно регистрироваться на сайте продавца для того, чтобы производить оплату. GNU Taler защищена от многих видов интернет-мошенничества, таких как фишинг, цель которого является получение доступа к данным банковских карт пользователей, или чарджбэк. В случае утери или кражи конфиденциальных данных лишь ограниченная денежная сумма может быть выведена с электронного кошелька.</string>
+ <string name="transaction_pending">ОЖИДАЕТ ПОДТВЕРЖДЕНИЯ</string>
+ <string name="withdraw_button_confirm_bank">Подтвердить с банком</string>
+ <string name="payment_label_order_summary">Покупка</string>
+ <string name="withdraw_button_tos">Проверить условия оказания услуг</string>
+ <string name="withdraw_waiting_confirm">Ожидает подтверждения</string>
+ <string name="withdraw_amount">Какую сумму вы хотели бы вывести\?</string>
+ <string name="withdraw_manual_title">Осуществить перевод на биржу вручную</string>
+ <string name="withdraw_manual_payment_options">Способы оплаты поддерживаются %1$s:
+\n
+\n%2$s</string>
+ <string name="withdraw_error_title">Ошибка при выводе средств</string>
+ <string name="withdraw_error_message">Вывод средств в данный момент не возможен. Пожалуйста, попробуйте ещё раз позже!</string>
+ <string name="withdraw_error_test">Ошибка при выводе TESTKUDOS</string>
+ <string name="exchange_settings_title">Биржи</string>
+ <string name="exchange_list_title">Биржи</string>
+ <string name="exchange_list_currency">Валюта: %s</string>
+ <string name="exchange_list_add">Добавить биржу</string>
+ <string name="exchange_add_url">Введите адрес биржи</string>
+ <string name="exchange_menu_manual_withdraw">Вывести средства</string>
+ <string name="exchange_fee_withdrawal_fee_label">Комиссия за вывод средств:</string>
+ <string name="exchange_fee_withdraw_fee">Комиссия за вывод средств: %s</string>
+ <string name="exchange_fee_wire_fee_timespan">Временной диапазон: %1$s - %2$s</string>
+ <string name="exchange_fee_wire_fee_wire_fee">Комиссия за перевод: %s</string>
+ <string name="exchange_tos_accept">Принять условия оказания услуг</string>
+ <string name="pending_operations_title">Незавершенные операции</string>
+ <string name="pending_operations_refuse">Отклонить предложение</string>
+ <string name="pending_operations_no_action">(нет действий)</string>
+ <string name="settings_dev_mode">Режим разработчика</string>
+ <string name="settings_withdraw_testkudos">Вывести TESTKUDOS</string>
+ <string name="settings_withdraw_testkudos_summary">Получить деньги для тестирования</string>
+ <string name="settings_logcat">Журнал отладки</string>
+ <string name="settings_version_app">Версия приложения</string>
+ <string name="settings_reset">Переустановить кошелёк (Опасно!)</string>
+ <string name="nav_settings_backup">Резервная копия</string>
+ <string name="exchange_fee_refresh_fee">Комиссия за обмен: %s</string>
+ <string name="exchange_fee_overhead_label">Округленные убытки:</string>
+ <string name="exchange_fee_refund_fee">Комиссия за возврат: %s</string>
+ <string name="exchange_fee_wire_fee_closing_fee">Комиссия за закрытие: %s</string>
+ <string name="settings_logcat_summary">Просмотреть/отправить внутренний журнал</string>
+ <string name="settings_version_core">Базовая версия кошелька</string>
+ <string name="settings_version_protocol_exchange">Поддерживаемые версии биржи</string>
+ <string name="exchange_fee_coin_expiration_label">Самый ранний срок годности монеты:</string>
+ <string name="backup_last">Последняя резервная копия: %s</string>
+ <string name="refund_title">Возврат</string>
+ <string name="refund_error">Ошибка при обработке возврата</string>
+ <string name="refund_success">Возврат получен: %s</string>
+ <string name="tip_title">Вознаграждение</string>
+ <string name="wifi_connect_error">Не удалось подключиться к бесплатному Wi-Fi: %s</string>
+ <string name="wifi_disabled_error">Включите Wi-Fi, чтобы получить бесплатный Wi-Fi</string>
+ <string name="withdraw_amount_error">Введите допустимую сумму</string>
+ <string name="exchange_settings_summary">Управление каталогом бирж, известных этому кошельку</string>
+ <string name="exchange_list_empty">Неизвестные биржи
+\n
+\nДобавить биржу вручную или вывести электронные наличные деньги!</string>
+ <plurals name="exchange_fee_coin">
+ <item quantity="one">Монета: %s (использована %d раз)</item>
+ <item quantity="few">Монет: %s (использовано %d раз)</item>
+ <item quantity="many">Монеты: %s (использованы %d раз)</item>
+ <item quantity="other">Монеты: %s (использованы %d раз)</item>
+ </plurals>
+ <string name="exchange_tos_error">Ошибка при отображении условий оказания услуг: %s</string>
+ <string name="settings_dev_mode_summary">Показывает информацию, необходимую для отладки программы</string>
+ <string name="settings_version_protocol_merchant">Поддерживаемые версии мерчанта</string>
+ <string name="exchange_fee_deposit_fee">Комиссия за пополнение: %s</string>
+ <string name="settings_version_unknown">Неизвестна</string>
+ <string name="settings_reset_summary">Обнуляет ваши средства</string>
+</resources> \ No newline at end of file
diff --git a/wallet/src/main/res/values-tr/strings.xml b/wallet/src/main/res/values-tr/strings.xml
new file mode 100644
index 0000000..6360667
--- /dev/null
+++ b/wallet/src/main/res/values-tr/strings.xml
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <string name="app_name">Taler Cüzdan</string>
+ <string name="search">Ara</string>
+ <string name="menu">Menü</string>
+ <string name="or">veya</string>
+ <string name="offline">İşlem internet erişimi gerektirir. Lütfen internet bağlantınızın çalıştığından emin olun ve tekrar deneyin.</string>
+ <string name="nav_exchange_select">Exchange\'i seç</string>
+ <string name="nav_exchange_fees">Exchange Ücretleri</string>
+ <string name="nav_error">Hata</string>
+ <string name="button_back">Geri git</string>
+ <string name="button_scan_qr_code">Taler QR Kodunu Tara</string>
+ <string name="enter_uri">Taler URI\'ni girin</string>
+ <string name="paste">Yapıştır</string>
+ <string name="ok">Tamam</string>
+ <string name="paste_invalid">Pano geçersiz bir veri türü içeriyor</string>
+ <string name="uri_invalid">Geçerli bir Taler URI\'i değil</string>
+ <string name="cancel">İptal</string>
+ <string name="google_play_title" tools:keep="@string/google_play_title">Taler Cüzdan (deneysel)</string>
+ <string name="google_play_short_desc" tools:keep="@string/google_play_short_desc">GNU Taler, gizlilik dostu çevrimiçi işlemleri hızlı ve kolay hale getirir.</string>
+ <string name="nav_header_title">GNU Taler</string>
+ <string name="nav_header_subtitle">Cüzdan</string>
+ <string name="nav_prompt_withdraw">Dijital Nakit Çek</string>
+ <string name="nav_exchange_tos">Exchange\'in Hizmet Şartları</string>
+ <string name="menu_settings">Ayarlar</string>
+ <string name="error_unsupported_uri">Hata: Bu Taler URI\'i desteklenmiyor.</string>
+ <string name="menu_retry_pending_operations">Bekleyen İşlemleri Yeniden Deneyin</string>
+ <string name="host_apdu_service_desc">Taler NFC Ödemeleri</string>
+ <string name="balances_title">Bakiyeler</string>
+ <string name="amount_positive">+ %s</string>
+ <string name="amount_negative">- %s</string>
+ <string name="amount_chosen">Seçilen Miktar</string>
+ <string name="balances_inbound_label">gelen</string>
+ <string name="transactions_title">İşlemler</string>
+ <string name="transactions_empty">Herhangi bir işleminiz bulunmamaktadır</string>
+ <string name="transactions_empty_search">İşlem bulunamadı. Farklı bir arama deneyin.</string>
+ <string name="transactions_error">İşlemler yüklenemedi
+\n
+\n%s</string>
+ <string name="transactions_detail_title">İşlem</string>
+ <string name="transactions_detail_title_balance">Bakiye: %s</string>
+ <string name="transactions_delete">Sil</string>
+ <string name="transactions_select_all">Hepsini seç</string>
+ <string name="transaction_paid">Ödendi</string>
+ <string name="transaction_order_total">Toplam</string>
+ <string name="transaction_order">Satın al</string>
+ <string name="transaction_order_id">Fiş #%1$s</string>
+ <string name="transaction_tip_from">%s dan İkramiye</string>
+ <string name="transaction_refund">Para iadesi</string>
+ <string name="payment_title">Ödeme</string>
+ <string name="payment_fee">+%s ödeme ücreti</string>
+ <string name="payment_button_confirm">Ödemeyi Onayla</string>
+ <string name="payment_label_amount_total">Toplam Miktar:</string>
+ <string name="payment_label_order_summary">Satın alma</string>
+ <string name="payment_error">Hata: %s</string>
+ <string name="payment_balance_insufficient">Yetersiz bakiye!</string>
+ <string name="payment_aborted">Durduruldu</string>
+ <string name="payment_failed">Başarısız</string>
+ <string name="payment_already_paid">Bu satın alma işlemi için zaten ödeme yaptınız.</string>
+ <string name="payment_already_paid_title">Zaten ödendi</string>
+ <string name="withdraw_initiated">Para çekme işlemi başlatıldı</string>
+ <string name="withdraw_title">Para çekme</string>
+ <string name="withdraw_total">Para çek</string>
+ <string name="withdraw_fees">Ücret</string>
+ <string name="withdraw_exchange">Exchange</string>
+ <string name="withdraw_button_confirm_bank">Banka ile Onayla</string>
+ <string name="withdraw_button_tos">Şartları İncele</string>
+ <string name="withdraw_waiting_confirm">Onay bekliyor</string>
+ <string name="withdraw_manual_title">Exchange\'e manuel transfer yapın</string>
+ <string name="withdraw_amount">Ne kadar para çekilmeli\?</string>
+ <string name="withdraw_manual_payment_options">%1$s tarafından desteklenen ödeme seçenekleri:
+\n
+\n%2$s</string>
+ <string name="withdraw_manual_check_fees">Ücretleri kontrol edin</string>
+ <string name="transaction_refund_from">%s dan para iadesi</string>
+ <string name="transaction_pending">BEKLEYEN</string>
+ <string name="settings_dev_mode_summary">Hata gidermeye yönelik daha fazla bilgi gösterir</string>
+ <string name="settings_withdraw_testkudos">TESTKUDOS\'u çek</string>
+ <string name="settings_withdraw_testkudos_summary">Test için para al</string>
+ <string name="google_play_full_desc" tools:keep="@string/google_play_full_desc">Bu uygulama GNU Taler için bir cüzdandır. Oldukça deneyseldir ve henüz üretimde kullanıma hazır değildir. GNU Taler, gizliliği koruyan bir ödeme sistemidir. Müşteriler anonim kalabilir, ancak satıcılar GNU Taler ile yapılan ödemeler yoluyla gelirlerini gizleyemezler. Bu, vergi kaçakçılığının ve kara para aklamanın önlenmesine yardımcı olur. GNU Taler\'in birincil kullanım durumu ödemelerdir; bir değer saklama aracı olarak tasarlanmamıştır. Ödemeler her zaman mevcut bir para birimi tarafından desteklenir. Ödeme, bir Exchange hizmeti, yani Taler için bir ödeme hizmeti sağlayıcısı yardımıyla mevcut parayı elektronik paraya çevirdikten sonra yapılır. Müşteriler ödeme yaparken yalnızca ücretli bir cüzdana ihtiyaç duyar. Bir tüccar, müşterilerini tüccarın Web Sitesine kaydettirmeden ödemeleri kabul edebilir. GNU Taler, kredi kartı bilgilerinin kimlik avı veya ters ibraz sahtekarlığı gibi birçok dolandırıcılık türüne karşı bağışıktır. Kaybolma veya çalınma durumunda cüzdanda yalnızca sınırlı miktarda kalan para gitmiş olabilir.</string>
+ <string name="balances_empty_state">Cüzdanınızda dijital para bulunmamaktadır.
+\n
+\nDemo bankasından test parası alabilirsiniz:
+\n
+\nhttps://bank.demo.taler.net</string>
+ <string name="transaction_refresh">Demir para bitiş tarihi değişim ücreti</string>
+ <string name="payment_initiated">Ödeme başlatıldı</string>
+ <string name="withdraw_button_confirm">Para Çekmeyi Onayla</string>
+ <string name="withdraw_amount_error">Geçerli tutarı girin</string>
+ <string name="withdraw_error_message">Para çekme şu anda mümkün değil. Lütfen daha sonra tekrar deneyiniz!</string>
+ <string name="exchange_list_empty">Bilinen exchange yok
+\n
+\nManuel olarak bir tane ekleyin veya dijital parayı çekin!</string>
+ <string name="withdraw_manual_ready_title">Exchange para çekmek için hazır!</string>
+ <string name="withdraw_manual_ready_intro">İşlemi tamamlamak için exchange banka hesabına %s\'i havale etmeniz gerekmektedir</string>
+ <string name="withdraw_manual_ready_details_intro">Banka transfer detayları</string>
+ <string name="withdraw_manual_ready_iban">IBAN</string>
+ <string name="withdraw_manual_ready_subject">Konu</string>
+ <string name="withdraw_manual_ready_bank_button">Bankacılık uygulamasında aç</string>
+ <string name="withdraw_manual_ready_cancel">Para çekme işlemini iptal et</string>
+ <string name="withdraw_manual_ready_warning">Doğru konuyu kullandığınızdan emin olun, aksi takdirde para bu cüzdana ulaşmayacaktır.</string>
+ <string name="withdraw_error_title">Para Çekme Hatası</string>
+ <string name="withdraw_error_test">TESTKUDOS çekilirken hata oluştu</string>
+ <string name="exchange_settings_title">Exchangeler</string>
+ <string name="exchange_settings_summary">Bu cüzdan tarafından bilinen exchangelerin listesini yönetin</string>
+ <string name="exchange_list_title">Exchangeler</string>
+ <string name="exchange_list_currency">Para birimi: %s</string>
+ <string name="exchange_list_add">Exchange ekle</string>
+ <string name="exchange_add_url">Exchange adresini girin</string>
+ <string name="exchange_add_error">Exchange eklenemedi</string>
+ <string name="exchange_menu_manual_withdraw">Para çek</string>
+ <string name="exchange_fee_withdrawal_fee_label">Para çekme ücreti:</string>
+ <string name="exchange_fee_overhead_label">Yuvarlama Zararı:</string>
+ <string name="exchange_fee_coin_expiration_label">Madeni Paranın En Erken Sona Erme Tarihi:</string>
+ <string name="exchange_fee_coin_fees_label">Madeni Para Ücretleri</string>
+ <string name="exchange_fee_refresh_fee">Değişim Ücreti: %s</string>
+ <string name="pending_operations_no_action">(Hiçbir eylem)</string>
+ <string name="exchange_fee_wire_fees_label">Havale Ücretleri</string>
+ <string name="exchange_fee_withdraw_fee">Para çekme Ücreti: %s</string>
+ <string name="exchange_fee_deposit_fee">Depozito Ücreti: %s</string>
+ <string name="exchange_fee_refund_fee">Para iadesi Ücreti: %s</string>
+ <string name="exchange_fee_wire_fee_timespan">Zaman aralığı: %1$s - %2$s</string>
+ <string name="exchange_fee_wire_fee_wire_fee">Havale Ücreti: %s</string>
+ <string name="exchange_fee_wire_fee_closing_fee">Kapanış Ücreti: %s</string>
+ <string name="exchange_tos_accept">Hizmet Şartlarını Kabul Et</string>
+ <string name="exchange_tos_error">Hizmet Şartları gösterilirken hata: %s</string>
+ <string name="pending_operations_title">Bekleyen İşlemler</string>
+ <string name="pending_operations_refuse">Teklifi Reddet</string>
+ <string name="settings_dev_mode">Geliştirici modu</string>
+ <string name="settings_logcat">Hata giderme günlüğü</string>
+ <string name="settings_logcat_summary">Dahili günlüğü görüntüle/gönder</string>
+ <string name="settings_version_app">App Sürümü</string>
+ <string name="settings_version_core">Cüzdan Çekirdek Sürümü</string>
+ <string name="settings_version_protocol_exchange">Desteklenen Exchange Sürümleri</string>
+ <string name="settings_version_protocol_merchant">Desteklenen Satıcı Sürümleri</string>
+ <string name="settings_version_unknown">Bilinmeyen</string>
+ <string name="settings_reset">Cüzdanı Sıfırla (tehlikeli!)</string>
+ <string name="settings_reset_summary">Paranızı çöpe atar</string>
+ <string name="nav_settings_backup">Yedekle</string>
+ <string name="backup_last">Son Yedekleme: %s</string>
+ <string name="refund_title">Para iadesi</string>
+ <string name="refund_success">Para iadesi alındı: %s</string>
+ <string name="tip_title">İkramiye</string>
+ <string name="wifi_disabled_error">Ücretsiz Wi-Fi almak için Wi-Fi\'yi açın</string>
+ <string name="wifi_connect_error">Ücretsiz Wi-Fi\'ye bağlanılamadı: %s</string>
+ <string name="refund_error">Para iade işleminde hata oluştu</string>
+ <plurals name="exchange_fee_coin">
+ <item quantity="one">Madeni para: %s (%d kez kullanıldı)</item>
+ <item quantity="other">Madeni paralar: %s (%d kez kullanıldılar)</item>
+ </plurals>
+</resources> \ No newline at end of file
diff --git a/wallet/src/main/res/values/strings.xml b/wallet/src/main/res/values/strings.xml
index 7595060..592070c 100644
--- a/wallet/src/main/res/values/strings.xml
+++ b/wallet/src/main/res/values/strings.xml
@@ -119,6 +119,10 @@ GNU Taler is immune against many types of fraud, such as phishing of credit card
<string name="withdraw_manual_ready_title">Exchange is ready for withdrawal!</string>
<string name="withdraw_manual_ready_intro">To complete the process you need to wire %s to the exchange bank account</string>
<string name="withdraw_manual_ready_details_intro">Bank transfer details</string>
+ <string name="withdraw_manual_bitcoin_ready_details_intro">Bitcoin transfer details</string>
+ <string name="withdraw_manual_bitcoin_ready_details_segwit">The exchange need a transaction with 3 output, one output is the exchange account and the other two are segwit fake address for metadata with an minimum amount.</string>
+ <string name="withdraw_manual_bitcoin_ready_details_bitcoincore">In bitcoincore wallet use \'Add Recipient\' button to add two additional recipient and copy addresses and amounts</string>
+ <string name="withdraw_manual_bitcoin_ready_details_confirm">Make sure the amount show %s, else you have to change the base unit to BTC</string>
<string name="withdraw_manual_ready_iban">IBAN</string>
<string name="withdraw_manual_ready_subject">Subject</string>
<string name="withdraw_manual_ready_bank_button">Open in banking app</string>
diff --git a/wallet/src/test/java/net/taler/wallet/payment/PaymentResponsesTest.kt b/wallet/src/test/java/net/taler/wallet/payment/PaymentResponsesTest.kt
index fbdc07d..da2378e 100644
--- a/wallet/src/test/java/net/taler/wallet/payment/PaymentResponsesTest.kt
+++ b/wallet/src/test/java/net/taler/wallet/payment/PaymentResponsesTest.kt
@@ -23,7 +23,6 @@ class PaymentResponsesTest {
private val json = Json {
ignoreUnknownKeys = true
- classDiscriminator = PreparePayResponse.discriminator
}
@Test
diff --git a/wallet/src/test/java/net/taler/wallet/withdraw/WithdrawManagerKtTest.kt b/wallet/src/test/java/net/taler/wallet/withdraw/WithdrawManagerKtTest.kt
new file mode 100644
index 0000000..b75e8bf
--- /dev/null
+++ b/wallet/src/test/java/net/taler/wallet/withdraw/WithdrawManagerKtTest.kt
@@ -0,0 +1,51 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2022 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.withdraw
+
+import Bech32.Companion.generateFakeSegwitAddress
+import org.junit.Assert
+import org.junit.Test
+
+class WithdrawManagerKtTest {
+
+ @Test
+ fun generateMainnet() {
+ val (addr1, addr2) = generateFakeSegwitAddress("54ZN9AMVN1R0YZ68ZPVHHQA4KZE1V037M05FNMYH4JQ596YAKJEG",
+ "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq")
+
+ Assert.assertEquals(addr1, "bc1q9yl4f23f8a224xagwq8hej8akuvd63yl8nyedj")
+ Assert.assertEquals(addr2, "bc1q4yl4f2kurkqx0gq2ltfazf9w2jdu48yaqlghnp")
+ }
+
+ @Test
+ fun generateTestnet() {
+ val (addr1, addr2) = generateFakeSegwitAddress("54ZN9AMVN1R0YZ68ZPVHHQA4KZE1V037M05FNMYH4JQ596YAKJEG",
+ "tb1qhxrhccqexg0dv4nltgkuw4fg2ce7muplmjsn0v")
+
+ Assert.assertEquals(addr1, "tb1q9yl4f23f8a224xagwq8hej8akuvd63yld4l2kp")
+ Assert.assertEquals(addr2, "tb1q4yl4f2kurkqx0gq2ltfazf9w2jdu48ya2enygj")
+ }
+
+ @Test
+ fun generateRegnet() {
+ val (addr1, addr2) = generateFakeSegwitAddress("54ZN9AMVN1R0YZ68ZPVHHQA4KZE1V037M05FNMYH4JQ596YAKJEG",
+ "bcrtqhxrhccqexg0dv4nltgkuw4fg2ce7muplmjsn0v")
+
+ Assert.assertEquals(addr1, "bcrt1q9yl4f23f8a224xagwq8hej8akuvd63yl0ux8pg")
+ Assert.assertEquals(addr2, "bcrt1q4yl4f2kurkqx0gq2ltfazf9w2jdu48yags2flm")
+ }
+}