Skip to content

Commit

Permalink
Merge pull request #12 from alexandr7035/develop
Browse files Browse the repository at this point in the history
Release v0.1-alpha

Implement basic use cases for Affinidi Cloud Wallet:
- Sign up
- Sign in
- View info (username, DID)
- Logout
  • Loading branch information
alexandr7035 authored Dec 21, 2021
2 parents ed6faca + fe86469 commit 19dad0e
Show file tree
Hide file tree
Showing 83 changed files with 2,801 additions and 50 deletions.
61 changes: 58 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,26 +1,51 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id "dagger.hilt.android.plugin"
id "androidx.navigation.safeargs"
}

def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

android {
compileSdk 31

signingConfigs {
config {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}

defaultConfig {
applicationId "by.alexandr7035.myapplication"
applicationId "by.alexandr7035.affinidi_id"
minSdk 21
targetSdk 31
versionCode 1
versionName "1.0"
versionCode 100
versionName "0.1-alpha"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

buildConfigField("String", "API_KEY", API_KEY)
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}

debug {
signingConfig signingConfigs.config
applicationIdSuffix ".debug"
debuggable true
versionNameSuffix ".debug"
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
Expand All @@ -29,6 +54,10 @@ android {
kotlinOptions {
jvmTarget = '1.8'
}

buildFeatures {
viewBinding true
}
}

dependencies {
Expand All @@ -37,7 +66,33 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

// Hilt
implementation("com.google.dagger:hilt-android:2.38.1")
kapt("com.google.dagger:hilt-android-compiler:2.38.1")

// Timber
implementation 'com.jakewharton.timber:timber:5.0.1'

// Retrofit
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation "com.squareup.okhttp3:logging-interceptor:4.9.1"

// Navigation
def nav_version = "2.3.5"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

// ViewBinding delegate
def viewbindingdelegate_version = "1.5.3"
implementation "com.github.kirich1409:viewbindingpropertydelegate-noreflection:$viewbindingdelegate_version"

// Coil
implementation "io.coil-kt:coil:1.4.0"
implementation "io.coil-kt:coil-svg:1.4.0"
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package by.alexandr7035.myapplication
package by.alexandr7035.affinidi_id

import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
Expand All @@ -19,6 +19,6 @@ class ExampleInstrumentedTest {
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("by.alexandr7035.myapplication", appContext.packageName)
assertEquals("by.alexandr7035.affinidi_id", appContext.packageName)
}
}
8 changes: 5 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="by.alexandr7035.myapplication">
package="by.alexandr7035.affinidi_id">

<uses-permission android:name="android.permission.INTERNET"/>

<application
android:allowBackup="true"
android:name=".core.App"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AffinidiID">
<activity
android:name=".MainActivity"
android:name=".presentation.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand Down
19 changes: 19 additions & 0 deletions app/src/main/java/by/alexandr7035/affinidi_id/core/App.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package by.alexandr7035.affinidi_id.core

import android.app.Application
import by.alexandr7035.affinidi_id.BuildConfig
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber

@HiltAndroidApp
class App: Application() {
override fun onCreate() {
super.onCreate()

// Setup timber
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package by.alexandr7035.affinidi_id.core

import java.io.IOException

data class AppError(val errorType: ErrorType): IOException()
11 changes: 11 additions & 0 deletions app/src/main/java/by/alexandr7035/affinidi_id/core/ErrorType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package by.alexandr7035.affinidi_id.core

enum class ErrorType {
FAILED_CONNECTION,
USER_ALREADY_EXISTS,
USER_DOES_NOT_EXIST,
WRONG_USERNAME_OR_PASSWORD,
WRONG_CONFIRMATION_CODE,
CONFIRMATION_CODE_DIALOG_DISMISSED,
UNKNOWN_ERROR
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package by.alexandr7035.affinidi_id.core.extensions

import android.content.Context
import android.widget.Toast

fun Context.showToast(message: String, duration: Int = Toast.LENGTH_LONG) {
Toast.makeText(this, message, duration).show()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package by.alexandr7035.affinidi_id.core.extensions

import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import by.alexandr7035.affinidi_id.presentation.registration.RegistrationFragmentDirections

fun Fragment.showErrorDialog(title: String, message: String) {
findNavController().navigateSafe(
RegistrationFragmentDirections
.actionGlobalErrorDialogFragment(title, message))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package by.alexandr7035.affinidi_id.core.extensions

import androidx.navigation.NavController
import androidx.navigation.NavDirections

// Prevents crashes when second navigation request is triggered
// from a fragment that is no longer the current destination
fun NavController.navigateSafe(navDirection: NavDirections) {
val action = currentDestination?.getAction(navDirection.actionId)

if (action != null) {
navigate(navDirection)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package by.alexandr7035.affinidi_id.core.extensions

import android.graphics.Typeface
import android.text.SpannableString
import android.text.Spanned
import android.text.TextPaint
import android.text.style.ClickableSpan
import android.text.style.StyleSpan
import android.view.View

fun String.getClickableSpannable(
clickableText: String,
clickListener: View.OnClickListener,
isBold: Boolean,
spannableColor: Int? = null,
isUnderlined: Boolean = true
): SpannableString {

val startIndex = this.indexOf(clickableText, startIndex = 0, ignoreCase = false)
val endIndex = startIndex + clickableText.length

val spannableString = SpannableString(this)

val clickable = object : ClickableSpan() {
override fun onClick(view: View) {
clickListener.onClick(view)
}

override fun updateDrawState(ds: TextPaint) {
super.updateDrawState(ds)
ds.isUnderlineText = isUnderlined

if (spannableColor != null) {
ds.color = spannableColor
}
}
}

spannableString.setSpan(clickable, startIndex, endIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)

if (isBold) {
spannableString.setSpan(
StyleSpan(Typeface.BOLD),
startIndex,
endIndex,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}

return spannableString
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package by.alexandr7035.affinidi_id.core.extensions

import android.text.TextUtils
import com.google.android.material.textfield.TextInputLayout

fun TextInputLayout.clearError() {

if (!TextUtils.isEmpty(error)) {
error = null
isErrorEnabled = false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package by.alexandr7035.affinidi_id.core.extensions

import timber.log.Timber

fun Timber.Forest.debug(message: String) {
tag("DEBUG_TAG").d(message)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package by.alexandr7035.affinidi_id.core.livedata

import android.util.Log
import androidx.annotation.MainThread
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import java.util.concurrent.atomic.AtomicBoolean

class SingleLiveEvent<T>() : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)

@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}

// Observe the internal MutableLiveData
super.observe(owner, Observer<T> { t ->
if (pending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}

@MainThread
override fun setValue(t: T?) {
pending.set(true)
super.setValue(t)
}

/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
fun call() {
value = null
}

companion object {
private const val TAG = "SingleLiveEvent"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package by.alexandr7035.affinidi_id.core.network

import by.alexandr7035.affinidi_id.BuildConfig
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response

class AuthInterceptor(): Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
// return chain.proceed(chain.request().withHeader("Authorization", "token ${appPreferences.token}"))

val request = chain.request()

val newRequest: Request = chain.request().newBuilder()
.header("Api-Key", BuildConfig.API_KEY)
.method(request.method, request.body)
.build()

return chain.proceed(newRequest)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package by.alexandr7035.affinidi_id.core.network

import by.alexandr7035.affinidi_id.core.App
import by.alexandr7035.affinidi_id.core.AppError
import by.alexandr7035.affinidi_id.core.ErrorType
import by.alexandr7035.affinidi_id.core.extensions.debug
import okhttp3.Interceptor
import okhttp3.Response
import timber.log.Timber
import java.net.ConnectException
import java.net.UnknownHostException

class ErrorInterceptor: Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {

val res = try {
chain.proceed(chain.request())
}
catch (e: UnknownHostException) {
e.printStackTrace()
throw AppError(ErrorType.FAILED_CONNECTION)
}
catch (e: ConnectException) {
e.printStackTrace()
throw AppError(ErrorType.FAILED_CONNECTION)
}

Timber.debug("INTERCEPTOR ${res.code}")
return res
}
}
Loading

0 comments on commit 19dad0e

Please sign in to comment.