Updated on 2026-08-14
This commit is contained in:
parent
5a452ba1e2
commit
586dc1a563
30 changed files with 721 additions and 173 deletions
|
|
@ -3,8 +3,7 @@ apply plugin: 'kotlin-android'
|
|||
apply plugin: 'kotlin-android-extensions'
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion "29.0.2"
|
||||
|
||||
buildToolsVersion "29.0.3"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.tangem.tangemtest"
|
||||
|
|
@ -34,13 +33,22 @@ android {
|
|||
dependencies {
|
||||
implementation project(':tangem-core')
|
||||
implementation project(':tangem-sdk')
|
||||
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin"
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.core:core-ktx:1.1.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin"
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.core:core-ktx:1.2.0'
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.0.0-beta4"
|
||||
implementation "androidx.navigation:navigation-fragment-ktx:2.2.1"
|
||||
implementation "androidx.navigation:navigation-ui-ktx:2.2.1"
|
||||
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha01"
|
||||
|
||||
implementation "com.google.android.material:material:1.2.0-alpha05"
|
||||
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@
|
|||
<uses-permission android:name="android.permission.NFC" />
|
||||
|
||||
|
||||
|
||||
<application
|
||||
android:name=".AppTangemDemo"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
|
|
@ -17,15 +17,15 @@
|
|||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<activity android:name=".Old_MainActivity">
|
||||
<activity android:name="._main.MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data
|
||||
android:host="www.tangem.com"
|
||||
android:scheme="http" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.tangemtest
|
||||
|
||||
import android.app.Application
|
||||
import com.tangem.tangemtest.card_use_cases.CardContext
|
||||
import com.tangem.tangemtest.commons.DiManager
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class AppTangemDemo : Application(), DiManager {
|
||||
private val cardContext: CardContext = CardContext()
|
||||
|
||||
override fun getCardContext(): CardContext = cardContext
|
||||
}
|
||||
|
|
@ -1,161 +0,0 @@
|
|||
package com.tangem.tangemtest
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.tangem.CardManager
|
||||
import com.tangem.tangem_sdk_new.extensions.init
|
||||
import com.tangem.tasks.ScanEvent
|
||||
import com.tangem.tasks.TaskError
|
||||
import com.tangem.tasks.TaskEvent
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var cardManager: CardManager
|
||||
private lateinit var cardId: String
|
||||
private lateinit var issuerData: ByteArray
|
||||
private lateinit var issuerDataSignature: ByteArray
|
||||
private var issuerDataCounter: Int = 1
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
cardManager = CardManager.init(this)
|
||||
|
||||
btn_scan?.setOnClickListener { _ ->
|
||||
cardManager.scanCard { taskEvent ->
|
||||
when (taskEvent) {
|
||||
is TaskEvent.Event -> {
|
||||
when (taskEvent.data) {
|
||||
is ScanEvent.OnReadEvent -> {
|
||||
// Handle returned card data
|
||||
cardId = (taskEvent.data as ScanEvent.OnReadEvent).card.cardId
|
||||
runOnUiThread {
|
||||
tv_card_cid?.text = cardId
|
||||
btn_create_wallet.isEnabled = true
|
||||
}
|
||||
}
|
||||
is ScanEvent.OnVerifyEvent -> {
|
||||
//Handle card verification
|
||||
runOnUiThread {
|
||||
tv_card_cid?.text = cardId
|
||||
btn_sign.isEnabled = true
|
||||
btn_read_issuer_data.isEnabled = true
|
||||
btn_read_issuer_extra_data.isEnabled = true
|
||||
btn_write_issuer_data.isEnabled = true
|
||||
btn_purge_wallet.isEnabled = true
|
||||
btn_create_wallet.isEnabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is TaskEvent.Completion -> {
|
||||
if (taskEvent.error != null) {
|
||||
if (taskEvent.error is TaskError.UserCancelled) {
|
||||
// Handle case when user cancelled manually
|
||||
}
|
||||
// Handle other errors
|
||||
}
|
||||
// Handle completion
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
btn_sign?.setOnClickListener { _ ->
|
||||
cardManager.sign(
|
||||
createSampleHashes(),
|
||||
cardId) {
|
||||
when (it) {
|
||||
is TaskEvent.Completion -> {
|
||||
if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName }
|
||||
}
|
||||
is TaskEvent.Event -> runOnUiThread { tv_card_cid?.text = cardId + "was used to sign sample hashes." }
|
||||
}
|
||||
}
|
||||
}
|
||||
btn_read_issuer_data?.setOnClickListener { _ ->
|
||||
cardManager.readIssuerData(cardId) {
|
||||
when (it) {
|
||||
is TaskEvent.Completion -> {
|
||||
if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName }
|
||||
}
|
||||
is TaskEvent.Event -> runOnUiThread {
|
||||
btn_write_issuer_data.isEnabled = true
|
||||
tv_card_cid?.text = it.data.issuerData.contentToString()
|
||||
issuerData = it.data.issuerData
|
||||
issuerDataSignature = it.data.issuerDataSignature
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
btn_write_issuer_data?.setOnClickListener { _ ->
|
||||
cardManager.writeIssuerData(
|
||||
cardId,
|
||||
issuerData,
|
||||
issuerDataSignature) {
|
||||
when (it) {
|
||||
is TaskEvent.Completion -> {
|
||||
if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName }
|
||||
}
|
||||
is TaskEvent.Event -> runOnUiThread {
|
||||
tv_card_cid?.text = it.data.cardId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
btn_read_issuer_extra_data?.setOnClickListener { _ ->
|
||||
cardManager.readIssuerExtraData(cardId) {
|
||||
when (it) {
|
||||
is TaskEvent.Completion -> {
|
||||
if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName }
|
||||
}
|
||||
is TaskEvent.Event -> runOnUiThread {
|
||||
issuerDataCounter = (it.data.issuerDataCounter ?: 0) + 1
|
||||
btn_write_issuer_data.isEnabled = true
|
||||
tv_card_cid?.text = "Read ${it.data.issuerData.size} bytes of data."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
btn_purge_wallet?.setOnClickListener { _ ->
|
||||
cardManager.purgeWallet(
|
||||
cardId) {
|
||||
when (it) {
|
||||
is TaskEvent.Completion -> {
|
||||
if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName }
|
||||
}
|
||||
is TaskEvent.Event -> runOnUiThread {
|
||||
tv_card_cid?.text = it.data.status.name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
btn_create_wallet?.setOnClickListener { _ ->
|
||||
cardManager.createWallet(
|
||||
cardId) {
|
||||
when (it) {
|
||||
is TaskEvent.Completion -> {
|
||||
if (it.error != null) runOnUiThread { tv_card_cid?.text = it.error!!::class.simpleName }
|
||||
}
|
||||
is TaskEvent.Event -> runOnUiThread {
|
||||
tv_card_cid?.text = it.data.status.name
|
||||
btn_sign.isEnabled = true
|
||||
btn_read_issuer_data.isEnabled = true
|
||||
btn_purge_wallet.isEnabled = true
|
||||
btn_create_wallet.isEnabled = false
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
btn_read_write_user_data?.setOnClickListener { startActivity(Intent(this, TestUserDataActivity::class.java)) }
|
||||
}
|
||||
|
||||
private fun createSampleHashes(): Array<ByteArray> {
|
||||
val hash1 = ByteArray(32) { 1 }
|
||||
val hash2 = ByteArray(32) { 2 }
|
||||
return arrayOf(hash1, hash2)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.tangem.tangemtest._main
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.ui.AppBarConfiguration
|
||||
import androidx.navigation.ui.navigateUp
|
||||
import androidx.navigation.ui.setupActionBarWithNavController
|
||||
import com.tangem.tangemtest.R
|
||||
|
||||
/**
|
||||
* A simple activity demonstrating use of a NavHostFragment with a navigation drawer.
|
||||
*/
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private lateinit var appBarConfiguration: AppBarConfiguration
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.a_main)
|
||||
|
||||
val toolbar = findViewById<Toolbar>(R.id.toolbar)
|
||||
setSupportActionBar(toolbar)
|
||||
|
||||
val host: NavHostFragment = supportFragmentManager
|
||||
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment? ?: return
|
||||
|
||||
val navController = host.navController
|
||||
appBarConfiguration = AppBarConfiguration(setOf(R.id.nav_entry_point))
|
||||
setupActionBarWithNavController(navController, appBarConfiguration)
|
||||
|
||||
navController.addOnDestinationChangedListener { _, destination, _ ->
|
||||
val dest: String = try {
|
||||
resources.getResourceName(destination.id)
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
destination.id.toString()
|
||||
}
|
||||
Log.d("NavigationActivity", "Navigated to $dest")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
return findNavController(R.id.nav_host_fragment).navigateUp(appBarConfiguration)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package com.tangem.tangemtest._main.entry_point
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest.card_use_cases.CardContext
|
||||
import com.tangem.tangemtest.card_use_cases.actions.Action
|
||||
import com.tangem.tangemtest.commons.NavigateAction
|
||||
import com.tangem.tangemtest.commons.getDefaultNavigationOptions
|
||||
import com.tangem.tangemtest.commons.getDiManager
|
||||
import kotlinx.android.synthetic.main.fg_entry_point.*
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class EntryPointFragment : Fragment() {
|
||||
|
||||
private val navController: NavController by lazy { findNavController() }
|
||||
private lateinit var rvActions: RecyclerView
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fg_entry_point, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val activity = activity ?: throw Exception("Fragment isn't associated with activity ")
|
||||
|
||||
val cardContext = getDiManager().getCardContext().init(activity)
|
||||
initRecyclerView(cardContext)
|
||||
}
|
||||
|
||||
private fun initRecyclerView(cardContext: CardContext) {
|
||||
rvActions = rv_actions
|
||||
rvActions.layoutManager = LinearLayoutManager(activity)
|
||||
|
||||
val adapter = RvActionsAdapter(cardContext) { position, destinationId -> navigate(destinationId as Int) }
|
||||
adapter.setItems(mutableListOf(
|
||||
NavigateAction(Action.Scan(), R.id.nav_scan),
|
||||
NavigateAction(Action.Sign(), R.id.nav_sign)
|
||||
))
|
||||
rvActions.adapter = adapter
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
rvActions.adapter?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
private fun navigate(destinationId: Int) {
|
||||
navController.navigate(destinationId, null, getDefaultNavigationOptions())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
package com.tangem.tangemtest._main.entry_point
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest.card_use_cases.CardContext
|
||||
import com.tangem.tangemtest.card_use_cases.actions.Action
|
||||
import com.tangem.tangemtest.commons.Bindable
|
||||
import com.tangem.tangemtest.commons.NavigateAction
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
typealias RvCallback = (Int, Any?) -> Unit
|
||||
|
||||
class RvActionsAdapter(
|
||||
private val cardContext: CardContext,
|
||||
private val callback: RvCallback
|
||||
) : RecyclerView.Adapter<RvActionsVH>() {
|
||||
|
||||
private val actionList = mutableListOf<NavigateAction>()
|
||||
|
||||
fun setItems(list: MutableList<NavigateAction>) {
|
||||
actionList.clear()
|
||||
actionList.addAll(list)
|
||||
}
|
||||
|
||||
fun addToItems(list: MutableList<NavigateAction>) {
|
||||
actionList.addAll(list)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RvActionsVH {
|
||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.vh_actions, parent, false)
|
||||
return RvActionsVH(view, cardContext.isVerified, callback)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = actionList.size
|
||||
|
||||
override fun onBindViewHolder(holder: RvActionsVH, position: Int) {
|
||||
holder.bind(actionList[position])
|
||||
}
|
||||
}
|
||||
|
||||
class RvActionsVH(
|
||||
view: View,
|
||||
private val cardIsVerified: Boolean,
|
||||
private val callback: RvCallback
|
||||
) : RecyclerView.ViewHolder(view), Bindable<NavigateAction> {
|
||||
|
||||
private val tvAction = itemView.findViewById<TextView>(R.id.tv_title)
|
||||
private val navigateRight = itemView.findViewById<TextView>(R.id.tv_navigate_right)
|
||||
|
||||
override fun bind(data: NavigateAction) {
|
||||
tvAction.text = tvAction.context.getString(data.action.resId)
|
||||
itemView.setOnClickListener { callback(adapterPosition, data.destinationId) }
|
||||
switchItemBackground(data.action)
|
||||
}
|
||||
|
||||
private fun switchItemBackground(action: Action) {
|
||||
when (action) {
|
||||
is Action.Scan -> {
|
||||
}
|
||||
else -> itemView.visibility = if (cardIsVerified) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.tangemtest.card_use_cases
|
||||
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.tangem.CardManager
|
||||
import com.tangem.commands.Card
|
||||
import com.tangem.tangem_sdk_new.extensions.init
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class CardContext {
|
||||
lateinit var cardManager: CardManager
|
||||
private set
|
||||
|
||||
var card: Card? = null
|
||||
var isVerified: Boolean = false
|
||||
|
||||
fun reset() {
|
||||
isVerified = false
|
||||
card = null
|
||||
}
|
||||
|
||||
fun requireCard(): Card = card!!
|
||||
|
||||
fun init(activity: FragmentActivity): CardContext {
|
||||
if (::cardManager.isInitialized) return this
|
||||
|
||||
cardManager = CardManager.init(activity)
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.tangemtest.card_use_cases.actions
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import com.google.gson.Gson
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest.card_use_cases.base.BaseCardActionFragment
|
||||
import com.tangem.tangemtest.commons.postUI
|
||||
import com.tangem.tasks.ScanEvent
|
||||
import com.tangem.tasks.TaskEvent
|
||||
import kotlinx.android.synthetic.main.fg_card_scan.*
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class ScanActionFragment : BaseCardActionFragment() {
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.fg_card_scan
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
cardContext.reset()
|
||||
cardContext.cardManager.scanCard { taskEvent ->
|
||||
when (taskEvent) {
|
||||
is TaskEvent.Event -> handleDataEvent(taskEvent.data)
|
||||
is TaskEvent.Completion -> handleCompletionEvent(taskEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDataEvent(event: ScanEvent) {
|
||||
when (event) {
|
||||
is ScanEvent.OnReadEvent -> {
|
||||
cardContext.card = event.card
|
||||
postUI { tv_card_cid.text = Gson().toJson(cardContext.card) }
|
||||
}
|
||||
is ScanEvent.OnVerifyEvent -> cardContext.isVerified = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.tangemtest.card_use_cases.actions
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import com.tangem.commands.SignResponse
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest.card_use_cases.base.BaseCardActionFragment
|
||||
import com.tangem.tangemtest.commons.postUI
|
||||
import com.tangem.tasks.TaskEvent
|
||||
import kotlinx.android.synthetic.main.fg_card_sign.*
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class SignActionFragment : BaseCardActionFragment() {
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.fg_card_sign
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val cardId = cardContext.requireCard().cardId
|
||||
cardContext.cardManager.sign(createSampleHashes(), cardId) { taskEvent ->
|
||||
when (taskEvent) {
|
||||
is TaskEvent.Event -> handleDataEvent(taskEvent.data)
|
||||
is TaskEvent.Completion -> handleCompletionEvent(taskEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDataEvent(event: SignResponse) {
|
||||
postUI { tv_sign_result.text = toJson(event) }
|
||||
}
|
||||
|
||||
private fun createSampleHashes(): Array<ByteArray> {
|
||||
val hash1 = ByteArray(32) { 1 }
|
||||
val hash2 = ByteArray(32) { 2 }
|
||||
return arrayOf(hash1, hash2)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.tangemtest.card_use_cases.actions
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.tangem.tangemtest.R
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed class Action(@StringRes val resId: Int) {
|
||||
class Scan: Action(R.string.action_card_scan)
|
||||
class Sign: Action(R.string.action_card_sign)
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package com.tangem.tangemtest.card_use_cases.base
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.snackbar.BaseTransientBottomBar
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.gson.Gson
|
||||
import com.tangem.tangemtest.card_use_cases.CardContext
|
||||
import com.tangem.tangemtest.commons.LayoutHolder
|
||||
import com.tangem.tangemtest.commons.getDiManager
|
||||
import com.tangem.tasks.TaskError
|
||||
import com.tangem.tasks.TaskEvent
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
abstract class BaseCardActionFragment : Fragment(), LayoutHolder {
|
||||
|
||||
protected lateinit var cardContext: CardContext
|
||||
protected lateinit var mainView: View
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
mainView = inflater.inflate(getLayoutId(), container, false)
|
||||
return mainView
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
cardContext = getDiManager().getCardContext()
|
||||
}
|
||||
|
||||
@UiThread
|
||||
protected fun handleCompletionEvent(taskEvent: TaskEvent.Completion<*>) {
|
||||
val error: TaskError = taskEvent.error ?: return
|
||||
|
||||
when (error) {
|
||||
is TaskError.UserCancelled -> showSnackbarMessage("Error: User was canceled")
|
||||
else -> showSnackbarMessage("Description not implemented. code: ${error.code}")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected fun showSnackbarMessage(message: String) {
|
||||
Snackbar.make(mainView, message, BaseTransientBottomBar.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
protected fun toJson(obj: Any): String = Gson().toJson(obj)
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.tangemtest.commons
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface LayoutHolder {
|
||||
fun getLayoutId(): Int
|
||||
}
|
||||
|
||||
interface Bindable<T> {
|
||||
fun bind(data: T)
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.tangemtest.commons
|
||||
|
||||
import android.content.Context
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.tangem.tangemtest.card_use_cases.CardContext
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
interface DiManager {
|
||||
fun getCardContext(): CardContext
|
||||
}
|
||||
|
||||
fun Context.getDiManager(): DiManager = applicationContext as DiManager
|
||||
|
||||
fun Fragment.getDiManager(): DiManager = activity?.applicationContext as DiManager
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.tangemtest.commons
|
||||
|
||||
import androidx.navigation.NavOptions
|
||||
import androidx.navigation.navOptions
|
||||
import com.tangem.tangemtest.R
|
||||
import com.tangem.tangemtest.card_use_cases.actions.Action
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
fun getDefaultNavigationOptions(): NavOptions {
|
||||
return navOptions {
|
||||
anim {
|
||||
enter = R.anim.slide_in_right
|
||||
exit = R.anim.slide_out_left
|
||||
popEnter = R.anim.slide_in_left
|
||||
popExit = R.anim.slide_out_right
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class NavigateAction(val action: Action, val destinationId: Int)
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.tangemtest.commons
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import android.os.Looper
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
object Threading {
|
||||
val uiHandler: Handler = Handler(Looper.getMainLooper())
|
||||
val workHandler: Handler = Handler(HandlerThread("Application worker thread").apply { start() }.looper)
|
||||
|
||||
fun postInCurrentThread(msTime: Long, func: () -> Unit) {
|
||||
Handler().postDelayed({ func() }, msTime)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun post(msTime: Long, func: () -> Unit) {
|
||||
Threading.postInCurrentThread(msTime, func)
|
||||
}
|
||||
|
||||
fun postUI(msTime: Long = 0, func: () -> Unit) {
|
||||
if (msTime > 0) Threading.uiHandler.postDelayed({ func() }, msTime) else Threading.uiHandler.post(func)
|
||||
}
|
||||
|
||||
fun postWork(msTime: Long = 0, func: () -> Unit) {
|
||||
if (msTime > 0) Threading.workHandler.postDelayed({ func() }, msTime) else Threading.workHandler.post(func)
|
||||
}
|
||||
22
tangem-demo/src/main/res/anim/fade_in.xml
Normal file
22
tangem-demo/src/main/res/anim/fade_in.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (C) 2018 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<alpha
|
||||
android:duration="@android:integer/config_mediumAnimTime"
|
||||
android:fromAlpha="0.0"
|
||||
android:toAlpha="1.0" />
|
||||
</set>
|
||||
22
tangem-demo/src/main/res/anim/fade_out.xml
Normal file
22
tangem-demo/src/main/res/anim/fade_out.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (C) 2018 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<alpha
|
||||
android:duration="@android:integer/config_mediumAnimTime"
|
||||
android:fromAlpha="1.0"
|
||||
android:toAlpha="0.0" />
|
||||
</set>
|
||||
10
tangem-demo/src/main/res/anim/slide_in_left.xml
Normal file
10
tangem-demo/src/main/res/anim/slide_in_left.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<translate
|
||||
android:duration="@android:integer/config_mediumAnimTime"
|
||||
android:fromXDelta="-100%"
|
||||
android:fromYDelta="0%"
|
||||
android:toXDelta="0%"
|
||||
android:toYDelta="0%" />
|
||||
</set>
|
||||
10
tangem-demo/src/main/res/anim/slide_in_right.xml
Normal file
10
tangem-demo/src/main/res/anim/slide_in_right.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<translate
|
||||
android:duration="@android:integer/config_mediumAnimTime"
|
||||
android:fromXDelta="100%"
|
||||
android:fromYDelta="0%"
|
||||
android:toXDelta="0%"
|
||||
android:toYDelta="0%" />
|
||||
</set>
|
||||
10
tangem-demo/src/main/res/anim/slide_out_left.xml
Normal file
10
tangem-demo/src/main/res/anim/slide_out_left.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<translate
|
||||
android:duration="@android:integer/config_mediumAnimTime"
|
||||
android:fromXDelta="0%"
|
||||
android:fromYDelta="0%"
|
||||
android:toXDelta="-100%"
|
||||
android:toYDelta="0%" />
|
||||
</set>
|
||||
10
tangem-demo/src/main/res/anim/slide_out_right.xml
Normal file
10
tangem-demo/src/main/res/anim/slide_out_right.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<translate
|
||||
android:duration="@android:integer/config_mediumAnimTime"
|
||||
android:fromXDelta="0%"
|
||||
android:fromYDelta="0%"
|
||||
android:toXDelta="100%"
|
||||
android:toYDelta="0%" />
|
||||
</set>
|
||||
22
tangem-demo/src/main/res/layout/a_main.xml
Normal file
22
tangem-demo/src/main/res/layout/a_main.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/colorPrimary"
|
||||
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_host_fragment"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:defaultNavHost="true"
|
||||
app:navGraph="@navigation/navigation" />
|
||||
</LinearLayout>
|
||||
17
tangem-demo/src/main/res/layout/fg_card_scan.xml
Normal file
17
tangem-demo/src/main/res/layout/fg_card_scan.xml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_card_cid"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/action_card_scan"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
17
tangem-demo/src/main/res/layout/fg_card_sign.xml
Normal file
17
tangem-demo/src/main/res/layout/fg_card_sign.xml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_sign_result"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/action_card_sign"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
16
tangem-demo/src/main/res/layout/fg_entry_point.xml
Normal file
16
tangem-demo/src/main/res/layout/fg_entry_point.xml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rv_actions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
31
tangem-demo/src/main/res/layout/vh_actions.xml
Normal file
31
tangem-demo/src/main/res/layout/vh_actions.xml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_navigate_right"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:text=">"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
58
tangem-demo/src/main/res/navigation/navigation.xml
Normal file
58
tangem-demo/src/main/res/navigation/navigation.xml
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
app:startDestination="@+id/nav_entry_point">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_entry_point"
|
||||
android:name="com.tangem.tangemtest._main.entry_point.EntryPointFragment"
|
||||
android:label="@string/fg_name_entry_point"
|
||||
tools:layout="@layout/fg_entry_point" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_scan"
|
||||
android:name="com.tangem.tangemtest.card_use_cases.actions.ScanActionFragment"
|
||||
android:label="@string/action_card_scan"
|
||||
tools:layout="@layout/fg_card_scan" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_sign"
|
||||
android:name="com.tangem.tangemtest.card_use_cases.actions.SignActionFragment"
|
||||
android:label="@string/action_card_scan"
|
||||
tools:layout="@layout/fg_card_sign" />
|
||||
|
||||
<!-- <fragment-->
|
||||
<!-- android:id="@+id/flow_step_one_dest"-->
|
||||
<!-- android:name="com.example.android.codelabs.navigation.FlowStepFragment"-->
|
||||
<!-- tools:layout="@layout/flow_step_one_fragment">-->
|
||||
<!-- <argument-->
|
||||
<!-- android:name="flowStepNumber"-->
|
||||
<!-- android:defaultValue="1"-->
|
||||
<!-- app:argType="integer" />-->
|
||||
|
||||
<!-- <action-->
|
||||
<!-- android:id="@+id/next_action"-->
|
||||
<!-- app:destination="@+id/flow_step_two_dest" />-->
|
||||
<!-- </fragment>-->
|
||||
|
||||
<!-- <fragment-->
|
||||
<!-- android:id="@+id/settings_dest"-->
|
||||
<!-- android:name="com.example.android.codelabs.navigation.SettingsFragment"-->
|
||||
<!-- android:label="@string/settings"-->
|
||||
<!-- tools:layout="@layout/settings_fragment" />-->
|
||||
|
||||
<!-- <fragment-->
|
||||
<!-- android:id="@+id/deeplink_dest"-->
|
||||
<!-- android:name="com.example.android.codelabs.navigation.DeepLinkFragment"-->
|
||||
<!-- android:label="@string/deeplink"-->
|
||||
<!-- tools:layout="@layout/deeplink_fragment">-->
|
||||
|
||||
<!-- <argument-->
|
||||
<!-- android:name="myarg"-->
|
||||
<!-- android:defaultValue="Android!" />-->
|
||||
|
||||
<!-- <deepLink app:uri="www.example.com/{myarg}" />-->
|
||||
|
||||
<!-- </fragment>-->
|
||||
</navigation>
|
||||
|
|
@ -1,3 +1,7 @@
|
|||
<resources>
|
||||
<string name="app_name">TangemTest</string>
|
||||
<string name="app_name">Tangem Demo</string>
|
||||
|
||||
<string name="fg_name_entry_point">@string/app_name</string>
|
||||
<string name="action_card_scan">Read card</string>
|
||||
<string name="action_card_sign">Sign card</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue