Skip to content

Commit 4f66a6e

Browse files
authored
Merge pull request #96 from soramitsu/v-0.4.1
V 0.4.1
2 parents 66babd8 + 6101063 commit 4f66a6e

File tree

72 files changed

+1313
-563
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+1313
-563
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
### Fearless Wallet Android
2+
[![Google Play](https://img.shields.io/badge/Google%20Play-Android-green?logo=google%20play)](https://play.google.com/store/apps/details?id=jp.co.soramitsu.fearless)
23

34
![logo](/docs/fearlesswallet_promo.png)
45

5-
[![](https://img.shields.io/twitter/follow/FearlessWallet?label=Follow&style=social)](https://twitter.com/FearlessWallet)
6-
76
## About
87
Fearless Wallet is a mobile wallet designed for the decentralized future on the Kusama network, with support on iOS and Android platforms. The best user experience, fast performance, and secure storage for your accounts. Development of Fearless Wallet is supported by Kusama Treasury grant.
98

9+
[![](https://img.shields.io/twitter/follow/FearlessWallet?label=Follow&style=social)](https://twitter.com/FearlessWallet)
10+
11+
1012
## Roadmap
1113
Fearless Wallet roadmap is available for everyone: [roadmap link](https://soramitsucoltd.aha.io/shared/97bc3006ee3c1baa0598863615cf8d14)
1214

1315
## Dev Status
1416
Track features development: [board link](https://soramitsucoltd.aha.io/shared/343e5db57d53398e3f26d0048158c4a2)
1517

1618
## License
17-
Fearless Wallet Android is available under the Apache 2.0 license. See the LICENSE file for more info.
19+
Fearless Wallet Android is available under the Apache 2.0 license. See the LICENSE file for more info.

app/src/main/AndroidManifest.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,17 @@
4040
android:screenOrientation="fullSensor"
4141
android:theme="@style/Theme.Soramitsu.Fearless"
4242
tools:replace="android:theme,screenOrientation" />
43+
44+
<provider
45+
android:name="androidx.core.content.FileProvider"
46+
android:authorities="${applicationId}.provider"
47+
android:grantUriPermissions="true"
48+
android:exported="false">
49+
<meta-data
50+
android:name="android.support.FILE_PROVIDER_PATHS"
51+
android:resource="@xml/provider_paths" />
52+
</provider>
53+
4354
</application>
4455

4556
</manifest>

app/src/main/java/jp/co/soramitsu/app/root/navigation/Navigator.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,10 @@ class Navigator : SplashRouter, OnboardingRouter, AccountRouter, WalletRouter {
167167
navController?.navigate(R.id.action_open_onboarding, WelcomeFragment.getBundle(true))
168168
}
169169

170+
override fun openReceive() {
171+
navController?.navigate(R.id.action_open_receive)
172+
}
173+
170174
override fun openAccountDetails(address: String) {
171175
val extras = AccountDetailsFragment.getBundle(address)
172176

app/src/main/res/navigation/main_nav_graph.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@
1313
app:popEnterAnim="@anim/fragment_close_enter"
1414
app:popExitAnim="@anim/fragment_close_exit" />
1515

16+
<action
17+
android:id="@+id/action_open_receive"
18+
app:destination="@id/receiveFragment"
19+
app:enterAnim="@anim/fragment_open_enter"
20+
app:exitAnim="@anim/fragment_open_exit"
21+
app:popEnterAnim="@anim/fragment_close_enter"
22+
app:popExitAnim="@anim/fragment_close_exit" />
23+
1624
<action
1725
android:id="@+id/open_transaction_detail"
1826
app:destination="@id/transactionDetailFragment"
@@ -211,6 +219,12 @@
211219
app:popExitAnim="@anim/fragment_close_exit" />
212220
</fragment>
213221

222+
<fragment
223+
android:id="@+id/receiveFragment"
224+
android:name="jp.co.soramitsu.feature_wallet_impl.presentation.receive.ReceiveFragment"
225+
android:label="ReceiveFragment"
226+
tools:layout="@layout/fragment_receive" />
227+
214228
<fragment
215229
android:id="@+id/chooseAmountFragment"
216230
android:name="jp.co.soramitsu.feature_wallet_impl.presentation.send.amount.ChooseAmountFragment"

app/src/main/res/navigation/onboarding_nav_graph.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@
6060
app:destination="@id/pincodeFragment"
6161
app:enterAnim="@anim/fragment_open_enter"
6262
app:exitAnim="@anim/fragment_open_exit"
63+
app:popUpTo="@id/root_nav_graph"
64+
app:popUpToInclusive="true"
6365
app:popEnterAnim="@anim/fragment_close_enter"
6466
app:popExitAnim="@anim/fragment_close_exit">
6567

@@ -95,6 +97,8 @@
9597
android:id="@+id/action_confirmMnemonicFragment_to_pincodeFragment"
9698
app:destination="@id/pincodeFragment"
9799
app:enterAnim="@anim/fragment_open_enter"
100+
app:popUpTo="@id/root_nav_graph"
101+
app:popUpToInclusive="true"
98102
app:exitAnim="@anim/fragment_open_exit"
99103
app:popEnterAnim="@anim/fragment_close_enter"
100104
app:popExitAnim="@anim/fragment_close_exit">
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<paths>
2+
<external-path
3+
name="external_files"
4+
path="." />
5+
</paths>

build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
buildscript {
22
ext {
33
// App version
4-
versionName = '0.4'
5-
versionCode = 1
4+
versionName = '0.4.1'
5+
versionCode = 2
66

77
// SDK and tools
88
compileSdkVersion = 29
@@ -148,5 +148,5 @@ task ktlint(type: JavaExec, group: "verification") {
148148
}
149149

150150
task runTest(type: GradleBuild) {
151-
tasks = ['clean', 'ktlint']
151+
tasks = ['clean', 'ktlint', 'runModuleTests']
152152
}

common/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ dependencies {
6868
implementation rxCallAdapterDep
6969
implementation interceptorVersion
7070

71+
implementation zXingCoreDep
72+
implementation zXingEmbeddedDep
73+
7174
implementation wsDep
7275

7376
testImplementation jUnitDep

common/src/main/java/jp/co/soramitsu/common/base/BaseViewModel.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ open class BaseViewModel : ViewModel() {
3434
_messageLiveData.value = Event(text)
3535
}
3636

37+
fun showError(title: String, text: String) {
38+
_errorWithTitleLiveData.value = Event(title to text)
39+
}
40+
3741
fun showError(text: String) {
3842
_errorLiveData.value = Event(text)
3943
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package jp.co.soramitsu.common.data
2+
3+
import android.content.Context
4+
import io.reactivex.Single
5+
import jp.co.soramitsu.common.interfaces.FileProvider
6+
import java.io.File
7+
8+
class FileProviderImpl(
9+
private val context: Context
10+
) : FileProvider {
11+
12+
override fun createFileInTempStorage(fileName: String): Single<File> {
13+
return Single.fromCallable {
14+
val cacheDir = context.externalCacheDir?.absolutePath ?: throw IllegalStateException("cache directory is unavailable")
15+
File(cacheDir, fileName)
16+
}
17+
}
18+
}

common/src/main/java/jp/co/soramitsu/common/data/network/rpc/socket/RpcSocket.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import com.neovisionaries.ws.client.WebSocket
55
import com.neovisionaries.ws.client.WebSocketAdapter
66
import com.neovisionaries.ws.client.WebSocketException
77
import com.neovisionaries.ws.client.WebSocketFactory
8+
import com.neovisionaries.ws.client.WebSocketFrame
89
import com.neovisionaries.ws.client.WebSocketState
910
import jp.co.soramitsu.common.data.network.rpc.subscription.SubscriptionChange
1011
import jp.co.soramitsu.fearless_utils.wsrpc.Logger
1112
import jp.co.soramitsu.fearless_utils.wsrpc.request.base.RpcRequest
1213
import jp.co.soramitsu.fearless_utils.wsrpc.response.RpcResponse
14+
import java.util.concurrent.TimeUnit
1315

1416
interface RpcSocketListener {
1517
fun onResponse(rpcResponse: RpcResponse)
@@ -21,6 +23,8 @@ interface RpcSocketListener {
2123
fun onConnected()
2224
}
2325

26+
private const val PING_INTERVAL_SECONDS = 30L
27+
2428
class RpcSocket(
2529
val url: String,
2630
listener: RpcSocketListener,
@@ -32,6 +36,8 @@ class RpcSocket(
3236

3337
init {
3438
setupListener(listener)
39+
40+
ws.pingInterval = TimeUnit.SECONDS.toMillis(PING_INTERVAL_SECONDS)
3541
}
3642

3743
fun connectAsync() {
@@ -70,6 +76,10 @@ class RpcSocket(
7076
}
7177
}
7278

79+
override fun onPongFrame(websocket: WebSocket?, frame: WebSocketFrame?) {
80+
logger?.log("[RECEIVED] PONG")
81+
}
82+
7383
override fun onError(websocket: WebSocket, cause: WebSocketException) {
7484
logger?.log("$[ERROR] ${cause.message}")
7585
}

common/src/main/java/jp/co/soramitsu/common/di/CommonApi.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ import jp.co.soramitsu.common.data.network.rpc.SocketService
1010
import jp.co.soramitsu.common.data.network.rpc.SocketSingleRequestExecutor
1111
import jp.co.soramitsu.common.data.storage.Preferences
1212
import jp.co.soramitsu.common.data.storage.encrypt.EncryptedPreferences
13+
import jp.co.soramitsu.common.interfaces.FileProvider
1314
import jp.co.soramitsu.common.mixin.api.NetworkStateMixin
1415
import jp.co.soramitsu.common.resources.ClipboardManager
1516
import jp.co.soramitsu.common.resources.ContextManager
1617
import jp.co.soramitsu.common.resources.LanguagesHolder
1718
import jp.co.soramitsu.common.resources.ResourceManager
19+
import jp.co.soramitsu.common.utils.QrCodeGenerator
1820
import jp.co.soramitsu.common.vibration.DeviceVibrator
1921
import jp.co.soramitsu.fearless_utils.bip39.Bip39
2022
import jp.co.soramitsu.fearless_utils.encrypt.KeypairFactory
@@ -71,4 +73,8 @@ interface CommonApi {
7173
fun addressIconGenerator(): AddressIconGenerator
7274

7375
fun networkStateMixin(): NetworkStateMixin
76+
77+
fun qrCodeGenerator(): QrCodeGenerator
78+
79+
fun fileProvider(): FileProvider
7480
}

common/src/main/java/jp/co/soramitsu/common/di/modules/CommonModule.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,25 @@ package jp.co.soramitsu.common.di.modules
22

33
import android.content.Context
44
import android.content.SharedPreferences
5+
import android.graphics.Color
56
import android.os.Vibrator
67
import dagger.Module
78
import dagger.Provides
89
import jp.co.soramitsu.common.account.AddressIconGenerator
10+
import jp.co.soramitsu.common.data.FileProviderImpl
911
import jp.co.soramitsu.common.data.storage.Preferences
1012
import jp.co.soramitsu.common.data.storage.PreferencesImpl
1113
import jp.co.soramitsu.common.data.storage.encrypt.EncryptedPreferences
1214
import jp.co.soramitsu.common.data.storage.encrypt.EncryptedPreferencesImpl
1315
import jp.co.soramitsu.common.data.storage.encrypt.EncryptionUtil
1416
import jp.co.soramitsu.common.di.scope.ApplicationScope
17+
import jp.co.soramitsu.common.interfaces.FileProvider
1518
import jp.co.soramitsu.common.resources.ClipboardManager
1619
import jp.co.soramitsu.common.resources.ContextManager
1720
import jp.co.soramitsu.common.resources.LanguagesHolder
1821
import jp.co.soramitsu.common.resources.ResourceManager
1922
import jp.co.soramitsu.common.resources.ResourceManagerImpl
23+
import jp.co.soramitsu.common.utils.QrCodeGenerator
2024
import jp.co.soramitsu.common.vibration.DeviceVibrator
2125
import jp.co.soramitsu.fearless_utils.bip39.Bip39
2226
import jp.co.soramitsu.fearless_utils.encrypt.KeypairFactory
@@ -124,4 +128,16 @@ class CommonModule {
124128
resourceManager: ResourceManager,
125129
iconGenerator: IconGenerator
126130
): AddressIconGenerator = AddressIconGenerator(iconGenerator, resourceManager)
131+
132+
@Provides
133+
@ApplicationScope
134+
fun provideQrCodeGenerator(): QrCodeGenerator {
135+
return QrCodeGenerator(Color.BLACK, Color.WHITE)
136+
}
137+
138+
@Provides
139+
@ApplicationScope
140+
fun provideFileProvider(contextManager: ContextManager): FileProvider {
141+
return FileProviderImpl(contextManager.getContext())
142+
}
127143
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package jp.co.soramitsu.common.interfaces
2+
3+
import io.reactivex.Single
4+
import java.io.File
5+
6+
interface FileProvider {
7+
8+
fun createFileInTempStorage(fileName: String): Single<File>
9+
}

common/src/main/java/jp/co/soramitsu/common/utils/ContextExt.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ import androidx.annotation.DrawableRes
55
import androidx.core.content.ContextCompat
66

77
fun Context.getDrawableCompat(@DrawableRes drawableRes: Int) =
8-
ContextCompat.getDrawable(this, drawableRes)
8+
ContextCompat.getDrawable(this, drawableRes)!!
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package jp.co.soramitsu.common.utils
2+
3+
import android.graphics.Bitmap
4+
import com.google.zxing.EncodeHintType
5+
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
6+
import com.google.zxing.qrcode.encoder.Encoder
7+
8+
class QrCodeGenerator(
9+
private val firstColor: Int,
10+
private val secondColor: Int
11+
) {
12+
13+
companion object {
14+
private const val RECEIVE_QR_SCALE_SIZE = 1024
15+
private const val PADDING_SIZE = 2
16+
}
17+
18+
fun generateQrBitmap(input: String): Bitmap {
19+
val hints = HashMap<EncodeHintType, String>()
20+
hints[EncodeHintType.CHARACTER_SET] = "UTF-8"
21+
val qrCode = Encoder.encode(input, ErrorCorrectionLevel.H, hints)
22+
val byteMatrix = qrCode.matrix
23+
val width = byteMatrix.width + PADDING_SIZE
24+
val height = byteMatrix.height + PADDING_SIZE
25+
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
26+
for (y in 0 until height) {
27+
for (x in 0 until width) {
28+
if (y == 0 || y > byteMatrix.height || x == 0 || x > byteMatrix.width) {
29+
bitmap.setPixel(x, y, secondColor)
30+
} else {
31+
bitmap.setPixel(x, y, if (byteMatrix.get(x - PADDING_SIZE / 2, y - PADDING_SIZE / 2).toInt() == 1) firstColor else secondColor)
32+
}
33+
}
34+
}
35+
return Bitmap.createScaledBitmap(bitmap, RECEIVE_QR_SCALE_SIZE, RECEIVE_QR_SCALE_SIZE, false)
36+
}
37+
}

common/src/main/java/jp/co/soramitsu/common/utils/ViewExt.kt

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import android.view.inputmethod.EditorInfo
99
import android.widget.EditText
1010
import android.widget.TextView
1111
import androidx.annotation.ColorRes
12+
import androidx.annotation.DrawableRes
1213
import androidx.annotation.LayoutRes
1314
import androidx.core.content.ContextCompat
1415

@@ -53,4 +54,24 @@ fun ViewGroup.inflateChild(@LayoutRes id: Int): View {
5354
}
5455
}
5556

56-
fun TextView.setTextColorRes(@ColorRes colorRes: Int) = setTextColor(ContextCompat.getColor(context, colorRes))
57+
fun TextView.setTextColorRes(@ColorRes colorRes: Int) = setTextColor(ContextCompat.getColor(context, colorRes))
58+
59+
fun TextView.setDrawableStart(
60+
@DrawableRes start: Int? = null,
61+
widthInDp: Int? = null,
62+
heightInDp: Int? = widthInDp
63+
) {
64+
if (start == null) {
65+
setCompoundDrawablesWithIntrinsicBounds(null, null, null, null)
66+
return
67+
}
68+
69+
val drawable = context.getDrawableCompat(start)
70+
71+
val widthInPx = if (widthInDp != null) (resources.displayMetrics.density * widthInDp).toInt() else drawable.intrinsicWidth
72+
val heightInPx = if (heightInDp != null) (resources.displayMetrics.density * heightInDp).toInt() else drawable.intrinsicHeight
73+
74+
drawable.setBounds(0, 0, widthInPx, heightInPx)
75+
76+
setCompoundDrawablesRelative(drawable, null, null, null)
77+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:color="@color/colorPrimary">
4+
<item>
5+
<vector
6+
android:width="343dp"
7+
android:height="52dp"
8+
android:viewportWidth="343"
9+
android:viewportHeight="52">
10+
<path
11+
android:fillColor="#ff000000"
12+
android:pathData="M332.586,51H1L1,10.414L10.414,1H342V41.586L332.586,51Z"
13+
android:strokeWidth="2"
14+
android:strokeColor="#444444" />
15+
</vector>
16+
</item>
17+
</ripple>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<selector xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<item android:drawable="@drawable/bg_button_primary_disabled" android:state_enabled="false" />
5+
<item android:drawable="@drawable/bg_button_outline" />
6+
7+
</selector>

0 commit comments

Comments
 (0)