diff --git a/.gitignore b/.gitignore index 66e553b6e..1a29acd2f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ build *.iml local.properties .gradle -gossipML/.cxx **.project **.settings **.classpath diff --git a/.gitmodules b/.gitmodules index 8c9eb5b03..6991e3845 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ [submodule "kotlin-ipv8"] path = kotlin-ipv8 url = https://github.com/Tribler/kotlin-ipv8.git -[submodule "gossipML/superapp-essentia"] - path = gossipML/superapp-essentia - url = https://github.com/Tribler/superapp-essentia.git diff --git a/README.md b/README.md index 47969eb93..2a95f778d 100644 --- a/README.md +++ b/README.md @@ -77,16 +77,6 @@ Video 1: Load example. This uses a defaul Video 2: Share track. Note: as a fresh magnet link is generated in this video, there is only 1 peer. For this reason it will be difficult to obtain the metadata of the magnet link (cold start issue, write about this in thesis) so the video stops there. -### Federated, privacy-preserving music recommendations via gossiping - -This is a demonstration of machine learning which relies exclusively on edge computing. Music recommendation inside the MusicDAO is used to demonstrate gossip-based machine learning. - -Every time a user opens MusicDAO, they are asked to reload the page in order to get recommendations. The recommendation engine yields two recommendations made by two different models: a musical feature-based model and a collaborative filtering model. The collaborative filtering model is based on federated matrix factorization as introduced in [this paper](https://dmle.iais.fraunhofer.de/papers/hegedus2019decentralized.pdf). The feature-based models are from this [paper](https://arxiv.org/pdf/1109.1396.pdf), called Adaline and Pegasos. These models are trained on audio features extracted from music files with the [Essentia library](https://essentia.upf.edu/). - - -The feature-based models are gossiped along random walks through the network. At each peer they are merged and re-trained on peer's local data. The matrix factorization model seeks to learn a factorization of the user-song matrix. This means that one of the two factors contains only information on how users generally rate each song. This matrix can then be gossiped around the network while a user's personal vector as well as their listening history are kept private. - - [More about federated machine learning using gossiping for music recommendations](gossipML/README.md) - ### Do you want to add your own app? - [Adding your own app to the TrustChain Super App](doc/AppTutorial.md) diff --git a/app/build.gradle b/app/build.gradle index a545c4db3..97db41481 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -113,7 +113,6 @@ allprojects { dependencies { implementation project(':debug') implementation project(':freedomOfComputing') - implementation project(':peerchat') implementation project(':eurotoken') implementation project(':valuetransfer') api(project(':common')) { @@ -125,9 +124,6 @@ dependencies { api(project(':musicdao')) { exclude group: 'net.java.dev.jna' } - api(project(':gossipML')) { - exclude group: 'net.java.dev.jna' - } implementation 'org.jetbrains.kotlinx:kotlinx-serialization-runtime:1.0-M1-1.4.0-rc' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4c115ff3f..1dcd46984 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -70,11 +70,6 @@ android:name="nl.tudelft.trustchain.debug.DebugActivity" android:parentActivityName=".ui.dashboard.DashboardActivity" /> - - , val disableImageTint: Boolean = false, ) { - PEERCHAT( - R.drawable.ic_chat_black_24dp, - "PeerChat", - R.color.purple, - PeerChatActivity::class.java - ), DEBUG( R.drawable.ic_bug_report_black_24dp, "Debug", diff --git a/app/src/main/java/nl/tudelft/trustchain/app/TrustChainApplication.kt b/app/src/main/java/nl/tudelft/trustchain/app/TrustChainApplication.kt index 1d80e0763..5b7507123 100644 --- a/app/src/main/java/nl/tudelft/trustchain/app/TrustChainApplication.kt +++ b/app/src/main/java/nl/tudelft/trustchain/app/TrustChainApplication.kt @@ -62,13 +62,10 @@ import nl.tudelft.trustchain.common.eurotoken.TransactionRepository import nl.tudelft.trustchain.musicdao.core.dao.CoinCommunity import nl.tudelft.trustchain.eurotoken.community.EuroTokenCommunity import nl.tudelft.trustchain.eurotoken.db.TrustStore -import nl.tudelft.trustchain.gossipML.RecommenderCommunity -import nl.tudelft.trustchain.gossipML.db.RecommenderStore -import nl.tudelft.trustchain.peerchat.community.PeerChatCommunity -import nl.tudelft.trustchain.peerchat.db.PeerChatStore +import nl.tudelft.trustchain.valuetransfer.community.PeerChatCommunity +import nl.tudelft.trustchain.valuetransfer.util.PeerChatStore import nl.tudelft.trustchain.valuetransfer.community.IdentityCommunity import nl.tudelft.trustchain.valuetransfer.db.IdentityStore -import nl.tudelft.gossipML.sqldelight.Database as MLDatabase val Context.dataStore: DataStore by preferencesDataStore(name = "settings") @@ -110,7 +107,6 @@ class TrustChainApplication : Application() { createCoinCommunity(), createDaoCommunity(), createMusicCommunity(), - createRecommenderCommunity(), createIdentityCommunity(), createFOCCommunity(), ), @@ -339,23 +335,6 @@ class TrustChainApplication : Application() { ) } - @OptIn(DelicateCoroutinesApi::class) // TODO: Verify whether usage is correct. - private fun createRecommenderCommunity(): OverlayConfiguration { - val settings = TrustChainSettings() - val musicDriver = AndroidSqliteDriver(Database.Schema, this, "music.db") - val musicStore = TrustChainSQLiteStore(Database(musicDriver)) - val driver = AndroidSqliteDriver(MLDatabase.Schema, this, "recommend.db") - val database = MLDatabase(driver) - - val recommendStore = RecommenderStore.getInstance(musicStore, database) - recommendStore.essentiaJob = GlobalScope.launch { recommendStore.addAllLocalFeatures() } - val randomWalk = RandomWalk.Factory() - return OverlayConfiguration( - RecommenderCommunity.Factory(recommendStore, settings, musicStore), - listOf(randomWalk) - ) - } - private fun createFOCCommunity(): OverlayConfiguration { val randomWalk = RandomWalk.Factory() return OverlayConfiguration( diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml index 04f33e5b3..025bcdf55 100644 --- a/app/src/main/res/layout/content_main.xml +++ b/app/src/main/res/layout/content_main.xml @@ -13,17 +13,10 @@ android:layout_width="match_parent" android:layout_height="0dp" app:defaultNavHost="true" - app:layout_constraintBottom_toTopOf="@id/bottomNavigation" + app:layout_constraintBottom_toTopOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:navGraph="@navigation/nav_graph_dashboard" /> - diff --git a/build.gradle b/build.gradle index 72c0b27b4..e85726cf9 100644 --- a/build.gradle +++ b/build.gradle @@ -32,7 +32,6 @@ buildscript { // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files - // TODO: Find a way to move this to trustchain-trader (gossipML relies on this too) classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" } } diff --git a/gossipML/.gitignore b/gossipML/.gitignore deleted file mode 100644 index 42afabfd2..000000000 --- a/gossipML/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/gossipML/README.md b/gossipML/README.md deleted file mode 100644 index a3c52873c..000000000 --- a/gossipML/README.md +++ /dev/null @@ -1,87 +0,0 @@ -# Federated, privacy-preserving music recommendations via gossiping - -This module contains a collection of recommendation models built on top of [IPv8](https://github.com/MattSkala/kotlin-ipv8) - -Every time a user opens MusicDAO, they are asked to reload the page in order to get recommendations. This is done with the purpose of getting recommendations on request and avoid recommending meaningless data when there is a lack of local songs (training data). -
- - -## Overview - -The recommendation engine yields two recommendations made by two different models: a musical feature-based model and a collaborative filtering model. -The idea is that the two models will capture different aspects in the patterns of a user's musical preference and so provide distinct recommendations. - -The collaborative filtering model is based on federated matrix factorization as introduced in [this paper](https://dmle.iais.fraunhofer.de/papers/hegedus2019decentralized.pdf). -The feature-based models are from this [paper](https://arxiv.org/pdf/1109.1396.pdf), called Adaline and Pegasos. -These models are trained on audio features extracted from music files with the [Essentia library](https://essentia.upf.edu/). - -All peers in the network have their own local model instances for both model types. -The local models are fine-tuned before making predictions to be personalized to a user's listening history. -Peers gossip models and features around the network to increase the predictive performance for everyone. -This is done in such a way that no private user data (listening history) is ever exchanged with peers. - -Local Essentia features are also gossiped among peers in order to have features for recommendations of 'unseen' songs. -This also prevents duplicate work, as the Essentia features are relatively costly to calculate on mobile phones. - -The two recommendation models follow slightly different gossiping strategies (according to the algorithms described in their respective papers / sections below). The figure below gives an overview of each model's propagation through the network. - - - -The feature-based models are gossiped along random walks through the network. At each peer they are merged and re-trained on peer's local data. - -The matrix factorization model seeks to learn a factorization of the user-song matrix. This means that one of the two factors contains only information on how users generally rate each song. This matrix can then be gossiped around the network while a user's personal vector as well as their listening history are kept private. - -## Module structure - -The general structure of the `gossipML` module can be seen below. The RecommenderCommunity is responsible for communications between peers. The RecommenderStore handles local data operations and preprocessing (audio features, model weights, listening history, etc.). The models are actually executed from the RecommendationFragment, which is in charge of user interaction with the UI. - - - -## Model details - -### Feature-based models - -Adaline (ADAptive LInear NEuron) is a single layer neural network that uses continuous predicted values from the net input. -Due to the use of continuous predicted labels taken before activation function, Adaline is capable of measuring the extent by which predictions were right/wrong. - -Pegasos (Primal Estimated sub-Gradient SOlver for SVM) is a sub-gradient descent algorithm for solving optimization problems cast by support vector machines. This is the feature-based model in use in the network. - -Pseudocode for Pegasos and Adaline models is shown below: - - - -More formally, learning rule for Pegasos is defined as: - - -And learning rule for Adaline is defined as: - - -### Collaborative filtering models - -Matrix Factorization model is collaborative filtering model that bases recommendations on private logs of user activity (song history) by means of low-rank matrix decomposition. -In the private model, every matrix row corresponds to some user private information on music history. -This private matrix is then approximatelly decomposed into matrices, X and Y, with Y shared among the users. - -The general error that the model tries to minimize is: -, -where the bias (c and b) represent the overall average score of the given user/song, and X and Y represent relative differences. - -Below is the pseudocode for the update rule corresponding to the above loss function: - - - -Merging of gossiped models and local models happens via age-weighted averaging. This gives more weight to rows of the song factor matrix which have been updated more often (and so are probably more robust overall). - - - -## Model performance - -So far, the MatrixFactorization model showed to be pretty reliable on example tests. - -Unfortunatelly, the feature-based models still lack proper pre-training/fine-tuning. -We have selected Essentia features that most distinctly separated a set of example albums (the features of which can be found in the 'superapp-essentia/test/res' folder). -Nevertheless, prediction are not able to achieve high accuracy and we lack user preference data in order to properly group music data and tune the models. - -One thing that we have observed during testing is that upon merging the global walking model, local models weights were being overpowered when using a 50-50 weighting. -Thus, contrary to the paper, we average two models with 90% local model and only 10% walking model in order to keep local models tuned to local data. -In our case, we are not interested in one global model that can predict likes for everyone, but rather many local ones which are informed of useful feature representations by the global model. \ No newline at end of file diff --git a/gossipML/build.gradle b/gossipML/build.gradle deleted file mode 100644 index 5cbf9f023..000000000 --- a/gossipML/build.gradle +++ /dev/null @@ -1,154 +0,0 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' -apply plugin: 'org.jlleitschuh.gradle.ktlint' -apply plugin: 'kotlinx-serialization' -apply plugin: 'com.squareup.sqldelight' - -sqldelight { - Database { - packageName = "nl.tudelft.gossipML.sqldelight" - sourceFolders = ["sqldelight"] - schemaOutputDirectory = file("src/main/sqldelight/databases") - } -} - -repositories { - mavenCentral() - google() -} - -android { - compileSdkVersion 33 - - useLibrary 'android.test.base' - useLibrary 'android.test.mock' - - buildFeatures{ - viewBinding = true - } - - defaultConfig { - minSdkVersion 22 - targetSdkVersion 33 - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles 'consumer-rules.pro' - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - externalNativeBuild { - cmake { - path "src/main/cpp/CMakeLists.txt" - version "3.22.1" - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = "1.8" - } - - testOptions { - unitTests.returnDefaultValues = true - } - packagingOptions { - resources { - excludes += ['META-INF/DEPENDENCIES', 'META-INF/LICENSE', 'META-INF/LICENSE.txt', 'META-INF/license.txt', 'META-INF/NOTICE', 'META-INF/NOTICE.txt', 'META-INF/notice.txt', 'META-INF/ASL2.0', 'META-INF/*.kotlin_module'] - } - } - - - sourceSets { - androidTest { - java.srcDirs = ['src/androidTest/java'] - res.srcDirs = ['src/androidTest/res'] - } - } - - splits { - abi { - enable true - reset() - include 'arm64-v8a', 'x86', 'armeabi-v7a', 'x86_64' - universalApk true - } - } - ndkVersion '21.4.7075529' - namespace 'nl.tudelft.trustchain.gossipML' -} - -dependencies { - implementation project(':common') - api(project(':ipv8-android')){ - exclude group: 'net.java.dev.jna' - exclude group: 'org.bouncycastle' - } - implementation project(':musicdao-datafeeder') - - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - - implementation 'androidx.appcompat:appcompat:1.2.0' - implementation "androidx.preference:preference-ktx:1.1.1" - implementation 'androidx.core:core-ktx:1.9.0' -// implementation "androidx.collection:collection-ktx:1.3.2" - implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'com.google.android.material:material:1.3.0' - implementation 'androidx.constraintlayout:constraintlayout:2.0.4' - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' - implementation 'androidx.navigation:navigation-fragment-ktx:2.3.3' - implementation 'androidx.navigation:navigation-ui-ktx:2.3.3' - implementation 'com.google.android:flexbox:2.0.1' - - implementation 'org.jetbrains.kotlinx:kotlinx-serialization-runtime:1.0-M1-1.4.0-rc' - - // BitTorrent/Libtorrent libraries - implementation 'com.turn:ttorrent-core:1.5' - - implementation 'com.google.android.exoplayer:exoplayer-core:2.10.5' - implementation 'com.google.android.exoplayer:exoplayer-dash:2.10.5' - implementation 'com.google.android.exoplayer:exoplayer-ui:2.10.5' - implementation 'com.google.android.exoplayer:exoplayer-hls:2.10.5' - - // Cryptocurrency integration - implementation('org.bitcoinj:bitcoinj-core:0.15.10') - implementation 'org.knowm.xchange:xchange-parent:5.0.1' - implementation 'org.knowm.xchange:xchange-binance:5.0.1' - - // Testing - testImplementation 'junit:junit:4.12' - testImplementation "io.mockk:mockk:1.9.3" - testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.7' - testImplementation "org.robolectric:robolectric:3.4.2" - testImplementation "com.goterl:lazysodium-java:5.1.4" - testImplementation 'androidx.core:core-ktx:1.9.0' - - // Testing and generating example data - implementation "com.squareup.sqldelight:sqlite-driver:$sqldelight_version" - - // Reading MP3 metadata - implementation 'com.mpatric:mp3agic:0.9.1' - - // Logging - implementation 'io.github.microutils:kotlin-logging:1.7.7' - - implementation "com.github.kotlin-graphics:kotlin-unsigned:v2.1" -} - -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { - kotlinOptions.freeCompilerArgs += [ - "-opt-in=kotlin.Experimental,kotlin.ExperimentalUnsignedTypes" - ] -} diff --git a/gossipML/consumer-rules.pro b/gossipML/consumer-rules.pro deleted file mode 100644 index e69de29bb..000000000 diff --git a/gossipML/docs/etc/essentia_test_albums_stats.ods b/gossipML/docs/etc/essentia_test_albums_stats.ods deleted file mode 100644 index 8bc4a061c..000000000 Binary files a/gossipML/docs/etc/essentia_test_albums_stats.ods and /dev/null differ diff --git a/gossipML/docs/etc/recording.gif b/gossipML/docs/etc/recording.gif deleted file mode 100644 index 1bfc6ac6a..000000000 Binary files a/gossipML/docs/etc/recording.gif and /dev/null differ diff --git a/gossipML/docs/imgs/ada_rule.png b/gossipML/docs/imgs/ada_rule.png deleted file mode 100644 index 39eb59e56..000000000 Binary files a/gossipML/docs/imgs/ada_rule.png and /dev/null differ diff --git a/gossipML/docs/imgs/adaline_alg.png b/gossipML/docs/imgs/adaline_alg.png deleted file mode 100644 index e62eb9e8f..000000000 Binary files a/gossipML/docs/imgs/adaline_alg.png and /dev/null differ diff --git a/gossipML/docs/imgs/gossipML_diagram.png b/gossipML/docs/imgs/gossipML_diagram.png deleted file mode 100644 index a618f2da0..000000000 Binary files a/gossipML/docs/imgs/gossipML_diagram.png and /dev/null differ diff --git a/gossipML/docs/imgs/mf.png b/gossipML/docs/imgs/mf.png deleted file mode 100644 index a4b327a5e..000000000 Binary files a/gossipML/docs/imgs/mf.png and /dev/null differ diff --git a/gossipML/docs/imgs/mf_alg.png b/gossipML/docs/imgs/mf_alg.png deleted file mode 100644 index c3664781b..000000000 Binary files a/gossipML/docs/imgs/mf_alg.png and /dev/null differ diff --git a/gossipML/docs/imgs/mf_merge.png b/gossipML/docs/imgs/mf_merge.png deleted file mode 100644 index 555f3d5b3..000000000 Binary files a/gossipML/docs/imgs/mf_merge.png and /dev/null differ diff --git a/gossipML/docs/imgs/overview.png b/gossipML/docs/imgs/overview.png deleted file mode 100644 index 2919aaa05..000000000 Binary files a/gossipML/docs/imgs/overview.png and /dev/null differ diff --git a/gossipML/docs/imgs/pegasos_rule.png b/gossipML/docs/imgs/pegasos_rule.png deleted file mode 100644 index 45c2aef34..000000000 Binary files a/gossipML/docs/imgs/pegasos_rule.png and /dev/null differ diff --git a/gossipML/docs/imgs/recommendations.png b/gossipML/docs/imgs/recommendations.png deleted file mode 100644 index d1189a2b2..000000000 Binary files a/gossipML/docs/imgs/recommendations.png and /dev/null differ diff --git a/gossipML/docs/imgs/start_recommendations.png b/gossipML/docs/imgs/start_recommendations.png deleted file mode 100644 index 91e6faf12..000000000 Binary files a/gossipML/docs/imgs/start_recommendations.png and /dev/null differ diff --git a/gossipML/proguard-rules.pro b/gossipML/proguard-rules.pro deleted file mode 100644 index 8491f82d2..000000000 --- a/gossipML/proguard-rules.pro +++ /dev/null @@ -1,18 +0,0 @@ --keepattributes *Annotation*, InnerClasses --dontnote kotlinx.serialization.AnnotationsKt # core serialization annotations - -# kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer --keepclassmembers class kotlinx.serialization.json.** { - *** Companion; -} --keepclasseswithmembers class kotlinx.serialization.json.** { - kotlinx.serialization.KSerializer serializer(...); -} - --keep,includedescriptorclasses class nl.tudelft.trustchain.gossipML.**$$serializer { *; } # <-- change package name to your app's --keepclassmembers class nl.tudelft.trustchain.gossipML.** { # <-- change package name to your app's - *** Companion; -} --keepclasseswithmembers class nl.tudelft.trustchain.gossipML.** { # <-- change package name to your app's - kotlinx.serialization.KSerializer serializer(...); -} \ No newline at end of file diff --git a/gossipML/src/androidTest/java/EssentiaTest.kt b/gossipML/src/androidTest/java/EssentiaTest.kt deleted file mode 100644 index d7207eba6..000000000 --- a/gossipML/src/androidTest/java/EssentiaTest.kt +++ /dev/null @@ -1,19 +0,0 @@ -import androidx.test.ext.junit.runners.AndroidJUnit4 -import nl.tudelft.trustchain.gossipML.Essentia -import org.junit.Assert.assertEquals -import org.junit.Test -import org.junit.runner.RunWith -import java.io.File - -@RunWith(AndroidJUnit4::class) -class EssentiaTest { - - @Test - fun testEssentia() { - var filepath = File("superapp-essentia/test/assets/GrayMicRecords-LofiDream.mp3").absolutePath - assertEquals(0, Essentia.extractData(filepath, filepath.replace(".mp3", ".json"))) - - filepath = File("superapp-essentia/test/assets/Helen&Shanna-Saudades.mp3").absolutePath - assertEquals(0, Essentia.extractData(filepath, filepath.replace(".mp3", ".json"))) - } -} diff --git a/gossipML/src/main/AndroidManifest.xml b/gossipML/src/main/AndroidManifest.xml deleted file mode 100644 index 8b84ec926..000000000 --- a/gossipML/src/main/AndroidManifest.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/gossipML/src/main/cpp/CMakeLists.txt b/gossipML/src/main/cpp/CMakeLists.txt deleted file mode 100644 index 1efe2539c..000000000 --- a/gossipML/src/main/cpp/CMakeLists.txt +++ /dev/null @@ -1,68 +0,0 @@ -cmake_minimum_required(VERSION 3.22.1) - -project("superappessentia") - -add_library(superappessentia - SHARED - essentia_jni.cpp) - -add_library(essentia STATIC IMPORTED) -add_library(avformat STATIC IMPORTED) -add_library(avcodec STATIC IMPORTED) -add_library(avutil STATIC IMPORTED) -add_library(avresample STATIC IMPORTED) -add_library(samplerate STATIC IMPORTED) -add_library(mp3lame STATIC IMPORTED) -add_library(chromaprint STATIC IMPORTED) -add_library(tag STATIC IMPORTED) -add_library(yaml STATIC IMPORTED) - -set_target_properties( essentia - PROPERTIES IMPORTED_LOCATION - ${CMAKE_SOURCE_DIR}/../../../superapp-essentia/libs/${ANDROID_ABI}/libessentia.a) - -set_target_properties( avformat - PROPERTIES IMPORTED_LOCATION - ${CMAKE_SOURCE_DIR}/../../../superapp-essentia/libs/${ANDROID_ABI}/libavformat.a) - -set_target_properties( avcodec - PROPERTIES IMPORTED_LOCATION - ${CMAKE_SOURCE_DIR}/../../../superapp-essentia/libs/${ANDROID_ABI}/libavcodec.a) - -set_target_properties( avutil - PROPERTIES IMPORTED_LOCATION - ${CMAKE_SOURCE_DIR}/../../../superapp-essentia/libs/${ANDROID_ABI}/libavutil.a) - -set_target_properties( avresample - PROPERTIES IMPORTED_LOCATION - ${CMAKE_SOURCE_DIR}/../../../superapp-essentia/libs/${ANDROID_ABI}/libavresample.a) - -set_target_properties( samplerate - PROPERTIES IMPORTED_LOCATION - ${CMAKE_SOURCE_DIR}/../../../superapp-essentia/libs/${ANDROID_ABI}/libsamplerate.a) - -set_target_properties( mp3lame - PROPERTIES IMPORTED_LOCATION - ${CMAKE_SOURCE_DIR}/../../../superapp-essentia/libs/${ANDROID_ABI}/libmp3lame.a) - -set_target_properties( chromaprint - PROPERTIES IMPORTED_LOCATION - ${CMAKE_SOURCE_DIR}/../../../superapp-essentia/libs/${ANDROID_ABI}/libchromaprint.a) - -set_target_properties( tag - PROPERTIES IMPORTED_LOCATION - ${CMAKE_SOURCE_DIR}/../../../superapp-essentia/libs/${ANDROID_ABI}/libtag.a) - -set_target_properties( yaml - PROPERTIES IMPORTED_LOCATION - ${CMAKE_SOURCE_DIR}/../../../superapp-essentia/libs/${ANDROID_ABI}/libyaml.a) - -include_directories(${CMAKE_SOURCE_DIR}/../../../superapp-essentia/include) -include_directories(${CMAKE_SOURCE_DIR}/../../../superapp-essentia/include/eigen3) - -find_library(log-lib log) -find_library(math-lib m) -find_library(zlib z) - -target_link_libraries(superappessentia essentia yaml avresample avformat avcodec avutil mp3lame - chromaprint tag samplerate ${log-lib} ${math-lib} ${zlib}) \ No newline at end of file diff --git a/gossipML/src/main/cpp/essentia_jni.cpp b/gossipML/src/main/cpp/essentia_jni.cpp deleted file mode 100644 index 831372c88..000000000 --- a/gossipML/src/main/cpp/essentia_jni.cpp +++ /dev/null @@ -1,135 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std; -using namespace essentia; -using namespace essentia::standard; - -void setExtractorDefaultOptions(Pool &options) { - options.set("outputFrames", false); - options.set("outputFormat", "json"); - options.set("requireMbid", false); - options.set("indent", 2); - - options.set("highlevel.inputFormat", "json"); -} - -void setExtractorOptions(const std::string& filename, Pool& options) { - if (filename.empty()) return; - - Pool opts; - Algorithm * yaml = AlgorithmFactory::create("YamlInput", "filename", filename); - yaml->output("pool").set(opts); - yaml->compute(); - delete yaml; - options.merge(opts, "replace"); -} - -void mergeValues(Pool& pool, Pool& options) { - string mergeKeyPrefix = "mergeValues"; - vector keys = options.descriptorNames(mergeKeyPrefix); - - for (int i=0; i<(int) keys.size(); ++i) { - keys[i].replace(0, mergeKeyPrefix.size()+1, ""); - pool.set(keys[i], options.value(mergeKeyPrefix + "." + keys[i])); - } -} - -void outputToFile(Pool& pool, const string& outputFilename, Pool& options) { - cerr << "Writing results to file " << outputFilename << endl; - int indent = (int)options.value("indent"); - - string format = options.value("outputFormat"); - Algorithm* output = AlgorithmFactory::create("YamlOutput", - "filename", outputFilename, - "doubleCheck", true, - "format", format, - "writeVersion", false, - "indent", indent); - output->input("pool").set(pool); - output->compute(); - delete output; -} - -jmp_buf env; - -void on_sigabrt (int signum) -{ - signal (signum, SIG_DFL); - __android_log_write(ANDROID_LOG_ERROR, "Essentia Android", "SIGSEGV or SIGABRT :("); - longjmp (env, 1); -} - -int essentia_main(string audioFilename, string outputFilename) { - // Returns: 1 on essentia error - - try { - essentia::init(); - setDebugLevel(ENone); - - cout.precision(10); - - Pool options; - setExtractorDefaultOptions(options); - setExtractorOptions("", options); - - - Algorithm* extractor = AlgorithmFactory::create("MusicExtractor"); - Pool results, resultsFrames; - - extractor->input("filename").set(audioFilename); - extractor->output("results").set(results); - extractor->output("resultsFrames").set(resultsFrames); - - if (setjmp (env) == 0) { - signal(SIGABRT, &on_sigabrt); - signal(SIGSEGV, &on_sigabrt); - extractor->compute(); - } - else { - return 1; - } - - mergeValues(results, options); - - outputToFile(results, outputFilename, options); - delete extractor; - essentia::shutdown(); - } - catch (EssentiaException& e) { - __android_log_write(ANDROID_LOG_ERROR, "Essentia Android", e.what()); - return 1; - } - catch (const std::bad_alloc& e) { - __android_log_write(ANDROID_LOG_ERROR, "Essentia Android", "bad_alloc exception: Out of memory"); - __android_log_write(ANDROID_LOG_ERROR, "Essentia Android", e.what()); - return 1; - } - - return 0; -} - -string convertJStringToString(JNIEnv *env, jstring str) { - jboolean is_copy; - const char *CString = env->GetStringUTFChars(str, &is_copy); - return std::string(CString); -} - -extern "C" -JNIEXPORT jint JNICALL -Java_nl_tudelft_trustchain_gossipML_Essentia_extractData(JNIEnv *env, jclass, jstring input_path, jstring output_path) { - std::string input = convertJStringToString(env, input_path); - std::string output = convertJStringToString(env, output_path); - int returnCode = essentia_main(input, output); - return returnCode; -} \ No newline at end of file diff --git a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/Essentia.java b/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/Essentia.java deleted file mode 100644 index 0dcbb1b4d..000000000 --- a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/Essentia.java +++ /dev/null @@ -1,11 +0,0 @@ -package nl.tudelft.trustchain.gossipML; - -/** - * Bridge between Essentia music library and federated ml library - */ -public final class Essentia { - static { - System.loadLibrary("superappessentia"); - } - public native static int extractData(String inputPath, String outputPath); -} diff --git a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/RecommenderCommunity.kt b/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/RecommenderCommunity.kt deleted file mode 100644 index 10e947630..000000000 --- a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/RecommenderCommunity.kt +++ /dev/null @@ -1,307 +0,0 @@ -package nl.tudelft.trustchain.gossipML - -import android.util.Log -import nl.tudelft.ipv8.Overlay -import nl.tudelft.ipv8.attestation.trustchain.TrustChainCommunity -import nl.tudelft.ipv8.attestation.trustchain.TrustChainCrawler -import nl.tudelft.ipv8.attestation.trustchain.TrustChainSettings -import nl.tudelft.ipv8.attestation.trustchain.store.TrustChainStore -import nl.tudelft.ipv8.messaging.Packet -import nl.tudelft.trustchain.gossipML.db.RecommenderStore -import nl.tudelft.trustchain.gossipML.ipv8.FeaturesExchangeMessage -import nl.tudelft.trustchain.gossipML.ipv8.ModelExchangeMessage -import nl.tudelft.trustchain.gossipML.ipv8.RequestModelMessage -import nl.tudelft.trustchain.gossipML.models.Model -import nl.tudelft.trustchain.gossipML.models.OnlineModel -import nl.tudelft.trustchain.gossipML.models.collaborative_filtering.MatrixFactorization -import nl.tudelft.trustchain.gossipML.models.collaborative_filtering.PublicMatrixFactorization -import java.util.* -import kotlin.random.Random - -/** - * Recommender community for gossiping recommendation models and songs features among peers - */ -@ExperimentalUnsignedTypes -open class RecommenderCommunity( - val recommendStore: RecommenderStore, - settings: TrustChainSettings, - database: TrustChainStore, - crawler: TrustChainCrawler = TrustChainCrawler() -) : TrustChainCommunity(settings, database, crawler) { - override val serviceId = "29384902d2938f34872398758cf7ca9238ccc333" - - class Factory( - private val recommendStore: RecommenderStore, - private val settings: TrustChainSettings, - private val database: TrustChainStore, - private val crawler: TrustChainCrawler = TrustChainCrawler() - ) : Overlay.Factory(RecommenderCommunity::class.java) { - override fun create(): RecommenderCommunity { - return RecommenderCommunity(recommendStore, settings, database, crawler) - } - } - - init { - messageHandlers[MessageId.MODEL_EXCHANGE_MESSAGE] = ::onModelExchange - messageHandlers[MessageId.REQUEST_MODEL] = ::onModelRequest - messageHandlers[MessageId.EXCHANGE_FEATURES] = ::onFeaturesExchange - } - - /** - * on initialization initiate walking models - */ - override fun load() { - super.load() - - if (Random.nextInt(0, 1) == 0) initiateWalkingModel() - } - - /** - * exchange extracted features for local songs with other peers - */ - fun exchangeMyFeatures() { - try { - Log.i("Recommend", "Send my features") - performLocalFeaturesExchange() - } catch (e: Exception) { - Log.i("Recommend", "Sending local features failed") - e.printStackTrace() - } - } - - /** - * Load / create walking models (feature based and collaborative filtering) - */ - fun initiateWalkingModel() { - try { - Log.i("Recommend", "Initiate random walk") - performRemoteModelExchange(recommendStore.getLocalModel("Pegasos")) - performRemoteModelExchange(recommendStore.getLocalModel("MatrixFactorization")) - } catch (e: Exception) { - Log.i("Recommend", "Random walk failed") - e.printStackTrace() - } - } - - /** - * Exchange walking model with other peers - * - * @param model walking model - * @param ttl ttl of a model packet - * @param originPublicKey public key - * @return amount of peers to whom model has been sent - */ - @ExperimentalUnsignedTypes - fun performRemoteModelExchange( - model: Model, - ttl: UInt = 2u, - originPublicKey: ByteArray = myPeer.publicKey.keyToBin() - ): Int { - val maxPeersToAsk = 5 - var count = 0 - for ((index, peer) in getPeers().withIndex()) { - if (index >= maxPeersToAsk) break - val packet = serializePacket( - MessageId.MODEL_EXCHANGE_MESSAGE, - ModelExchangeMessage(originPublicKey, ttl, model.name, model) - ) - Log.i("Recommend", "Sending models to ${peer.address}") - send(peer, packet) - count += 1 - } - return count - } - - /** - * Exchange local song's features with other peers - */ - private fun performLocalFeaturesExchange() { - val myFeatures = recommendStore.getMyFeatures() - for (feature in myFeatures) { - feature.songFeatures?.let { performFeaturesExchange(feature.key, it) } - } - } - - /** - * exchange packet with song features with other peers - * - * @param identifier song indentifier - * @param features song features - * @param ttl packet ttl - * @param originPublicKey original public key - * @return amount of peers to whom features has been sent - */ - @ExperimentalUnsignedTypes - fun performFeaturesExchange( - identifier: String, - features: String, - ttl: UInt = 2u, - originPublicKey: ByteArray = myPeer.publicKey.keyToBin() - ): Int { - val maxPeersToAsk = 5 - var count = 0 - for ((index, peer) in getPeers().withIndex()) { - if (index >= maxPeersToAsk) break - val packet = serializePacket( - MessageId.EXCHANGE_FEATURES, - FeaturesExchangeMessage(originPublicKey, ttl, identifier, features) - ) - Log.i("Recommend", "Sending features to ${peer.address}") - send(peer, packet) - count += 1 - } - Log.i("Recommend", "Features exchanged with $count peer(s)") - return count - } - - /** - * Handling incoming walking model by updating it with local features - * and merging with local model - * - * @param packet incoming packet with walking model - */ - @ExperimentalUnsignedTypes - fun onModelExchange(packet: Packet) { - Log.i("Recommend", "Some packet with model received") - val (_, payload) = packet.getAuthPayload(ModelExchangeMessage) - - // packet contains model type and weights from peer - @Suppress("DEPRECATION") - val modelType = payload.modelType.toLowerCase(Locale.ROOT) - var peerModel = payload.model - Log.i("Recommend", "Walking model is de-packaged") - - var localModel = recommendStore.getLocalModel(modelType) - - if (modelType == "Adaline" || modelType == "Pegasos") { - val data = recommendStore.getLocalSongData() - val songFeatures = data.first - val playcounts = data.second - val models = mergeFeatureModel( - localModel as OnlineModel, - peerModel as OnlineModel, - songFeatures, - playcounts - ) - Log.i("Recommend", "Walking an random models are merged") - recommendStore.storeModelLocally(models.first) - if (payload.checkTTL()) performRemoteModelExchange(models.second) - } else { - peerModel = peerModel as PublicMatrixFactorization - localModel = localModel as MatrixFactorization - if (localModel.songFeatures.size == 0) { - localModel = MatrixFactorization(peerModel.peerFeatures) - localModel.updateRatings( - recommendStore.getSongIds().zip(recommendStore.getPlaycounts()).toMap() - .toSortedMap() - ) - recommendStore.storeModelLocally(localModel) - val maxPeersToAsk = 5 - var count = 0 - for ((index, peer) in getPeers().withIndex()) { - if (index >= maxPeersToAsk) break - send( - peer, - serializePacket( - MessageId.REQUEST_MODEL, - RequestModelMessage( - myPeer.publicKey.keyToBin(), - 2u, - "MatrixFactorization" - ) - ) - ) - count += 1 - } - Log.i("Recommend", "Model request sent to $count peer(s)") - } else { - Log.i("Recommend", "Merging MatrixFactorization") - localModel.updateRatings( - recommendStore.getSongIds().zip(recommendStore.getPlaycounts()).toMap() - .toSortedMap() - ) - localModel.merge(peerModel.peerFeatures) - recommendStore.storeModelLocally(localModel) - Log.i("Recommend", "Stored new MatrixFactorization") - if (payload.checkTTL()) performRemoteModelExchange(localModel) - } - } - } - - /** - * Handling incomingpacket with song features - * by adding 'unseen' features to the local database - * - * @param packet incoming packet with song features - */ - @ExperimentalUnsignedTypes - fun onFeaturesExchange(packet: Packet) { - Log.i("Recommend", "Some packet with features received") - val (_, payload) = packet.getAuthPayload(FeaturesExchangeMessage) - - // packet contains model type and weights from peer - @Suppress("DEPRECATION") - val songIdentifier = payload.songIdentifier.toLowerCase(Locale.ROOT) - val features = payload.features - Log.i("Recommend", "Song features are de-packaged") - - recommendStore.addNewFeatures(songIdentifier, features) - if (payload.checkTTL()) performFeaturesExchange(songIdentifier, features) - } - - /** - * Handle model request from other peers - * - * @param packet request model packet - */ - private fun onModelRequest(packet: Packet) { - Log.i("Recommend", "Model request received") - val (_, payload) = packet.getAuthPayload(ModelExchangeMessage) - @Suppress("DEPRECATION") - val modelType = payload.modelType.toLowerCase(Locale.ROOT) - val model = recommendStore.getLocalModel(modelType) - send( - packet.source, - serializePacket( - MessageId.MODEL_EXCHANGE_MESSAGE, - ModelExchangeMessage(myPeer.publicKey.keyToBin(), 1u, model.name, model) - ) - ) - } - - /** - * Handle local and walking model merging and updating - * - * @param incomingModel walking model - * @param localModel local model - * @param features local features - * @param labels local labels - * @return pair of merged local and merged walking models - */ - private fun mergeFeatureModel( - incomingModel: OnlineModel, - localModel: OnlineModel, - features: Array>, - labels: IntArray - ): - Pair { - localModel.merge(incomingModel) - incomingModel.update(features, labels.map { it.toDouble() }.toTypedArray()) - return Pair(localModel, incomingModel) - } - - object MessageId { - val MODEL_EXCHANGE_MESSAGE: Int - get() { - return 27 - } - val REQUEST_MODEL: Int - get() { - return 40 - } - val EXCHANGE_FEATURES: Int - get() { - return 99 - } - } -} diff --git a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/db/RecommenderStore.kt b/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/db/RecommenderStore.kt deleted file mode 100644 index e57feae6b..000000000 --- a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/db/RecommenderStore.kt +++ /dev/null @@ -1,583 +0,0 @@ -package nl.tudelft.trustchain.gossipML.db - -import android.annotation.SuppressLint -import android.util.Log -import nl.tudelft.trustchain.musicdaodatafeeder.AudioFileFilter -import com.mpatric.mp3agic.Mp3File -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import nl.tudelft.gossipML.sqldelight.Database -import nl.tudelft.gossipML.sqldelight.Features -import nl.tudelft.gossipML.sqldelight.Unseen_features -import nl.tudelft.ipv8.attestation.trustchain.TrustChainBlock -import nl.tudelft.ipv8.attestation.trustchain.store.TrustChainSQLiteStore -import nl.tudelft.trustchain.gossipML.Essentia -import nl.tudelft.trustchain.gossipML.models.Model -import nl.tudelft.trustchain.gossipML.models.collaborative_filtering.MatrixFactorization -import nl.tudelft.trustchain.gossipML.models.feature_based.Adaline -import nl.tudelft.trustchain.gossipML.models.feature_based.Pegasos -import org.json.JSONObject -import java.io.File -import kotlin.math.log10 - -/** - * Handles database operations and preprocessing for local models and features - */ -open class RecommenderStore( - private val musicStore: TrustChainSQLiteStore, - private val database: Database -) { - lateinit var key: ByteArray - @SuppressLint("SdCardPath") - private val musicDir = File("/data/user/0/nl.tudelft.trustchain/cache/") - lateinit var essentiaJob: Job - private var highVarianceFeatureLabels = arrayOf(3, 62, 162, 223, 130, 140) - // 2 block features + cherry-picked essentia features - private val totalAmountFeatures = highVarianceFeatureLabels.size + 2 - - /** - * save local model to the database - * - * @param model model to be stored - */ - fun storeModelLocally(model: Model) { - if (model.name == "MatrixFactorization") { - database.dbModelQueries.addModel( - name = model.name, - type = model.name, - parameters = (model as MatrixFactorization).serialize(private = true) - ) - } else if (model.name == "Pegasos") { - database.dbModelQueries.addModel( - name = model.name, - type = model.name, - parameters = (model as Pegasos).serialize() - ) - } else { - database.dbModelQueries.addModel( - name = model.name, - type = model.name, - parameters = (model as Adaline).serialize() - ) - } - } - - /** - * loads model from the local database using name is a key - * - * @param name model name - * @return loaded model - */ - fun getLocalModel(name: String): Model { - Log.i("Recommend", "Loading $name") - val dbModel = database.dbModelQueries.getModel(name).executeAsOneOrNull() - val model: Model - if (name == "Adaline") { - if (dbModel != null) { - Log.i("Recommend", "Load existing local model") - model = Json.decodeFromString(dbModel.parameters) - } else { - model = Adaline(0.1, totalAmountFeatures) - Log.i("Recommend", "Initialized local model") - Log.i("Recommend", model.name) - } - } else if (name == "Pegasos") { - if (dbModel != null) { - Log.i("Recommend", "Load existing local model") - model = Json.decodeFromString(dbModel.parameters) - } else { - model = Pegasos(0.01, totalAmountFeatures, 10) - Log.i("Recommend", "Initialized local model") - Log.i("Recommend", model.name) - } - } else { - if (dbModel != null) { - Log.i("Recommend", "Load existing local model") - model = Json.decodeFromString(dbModel.parameters) - } else { - model = MatrixFactorization(Array(0) { "" }.zip(Array(0) { 0.0 }).toMap().toSortedMap()) - Log.i("Recommend", "Initialized local model") - Log.i("Recommend", model.name) - } - } - /** - * we are trying to 'bias' our model with local preferences such that it is tuned for local user - * thus, we re-train it on local dataset every time we load the model - */ - val trainingData = getLocalSongData() - if (trainingData.first.isNotEmpty()) { - if (name == "Pegasos") { - (model as Pegasos).update(trainingData.first, trainingData.second.map { it.toDouble() }.toTypedArray()) - } else if (name == "Adaline") { - (model as Adaline).update(trainingData.first, trainingData.second.map { it.toDouble() }.toTypedArray()) - } - } - storeModelLocally(model) - Log.i("Recommend", "Model completely loaded") - return model - } - - /** - * get playcounts for local songs (i.e. get labels for local data) - * - * @return array of playcounts - */ - fun getPlaycounts(): Array { - val songBlocks = musicStore.getBlocksWithType("publish_release") - val playcounts = Array(globalSongCount()) { 0.0 } - for ((i, block) in songBlocks.withIndex()) { - if (block.transaction["title"] != null && block.transaction["artist"] != null) { - playcounts[i] = database.dbFeaturesQueries.getFeature( - "local-${block.transaction["title"]}-${block.transaction["artist"]}" - ) - .executeAsOneOrNull()?.count?.toDouble() ?: 0.0 - } - } - return playcounts - } - - /** - * extract song ids from trustchain blocks with music data - * - * @return distinct song ids - */ - fun getSongIds(): Set { - val songBlocks = musicStore.getBlocksWithType("publish_release") - val songIds = HashSet() - for (block in songBlocks) { - if (block.transaction["title"] != null && block.transaction["artist"] != null) { - songIds.add("${block.transaction["title"]}-${block.transaction["artist"]}") - } - } - return songIds - } - - /** - * Update local features with features from new local file - * - * @param file unseen music file - */ - fun updateLocalFeatures(file: File) { - val mp3File = Mp3File(file) - val k = if (mp3File.id3v2Tag != null) - "local-${mp3File.id3v2Tag.title}-${mp3File.id3v2Tag.artist}" - else if (mp3File.id3v1Tag != null) - "local-${mp3File.id3v1Tag.title}-${mp3File.id3v1Tag.artist}" - else - return - val existingFeature = database.dbFeaturesQueries.getFeature(key = k).executeAsOneOrNull() - var count = 1 - if (existingFeature != null) { - count = existingFeature.count.toInt() + 1 - Log.i("Recommend", "Song exists! Increment counter") - } - val mp3Features = extractMP3Features(mp3File) - database.dbFeaturesQueries.addFeature( - key = k, - songFeatures = mp3Features.contentToString(), - count = count.toLong() - ) - } - - /** - * extracts music block metadata and transforms it to array of features - * that can be directly fed into ml models - * - * @param block music block - * @return array of features for given instance - */ - private fun blockMetadata(block: TrustChainBlock): Array { - var year = -1.0 - var genre = -1.0 - val k: String - - if (block.transaction["title"] != null && block.transaction["artist"] != null) { - k = "local-${block.transaction["title"]}-${block.transaction["artist"]}" - val unseenFeatures = this.database.dbUnseenFeaturesQueries.getFeature(k).executeAsOneOrNull() - if (unseenFeatures != null) { - return Json.decodeFromString(unseenFeatures.songFeatures!!).toTypedArray() - } - } - - if (block.transaction["date"] != null) { - try { - year = Integer.parseInt(block.transaction["date"] as String).toDouble() - } catch (e: Exception) { - } - - try { - // Ectrat year from string format '01/02/2020' - year = Integer.parseInt( - ( - block.transaction["date"] as - String - ).split("/").toTypedArray()[-1] - ).toDouble() - } catch (e: Exception) { - } - } - - if (block.transaction["genre"] != null) { - genre = block.transaction["genre"] as Double - } - - return arrayOf(year, genre) + Array(223) { -1.0 } - } - - /** - * get amount of songs from trustchain for which we can extract features - * - * @return amount of test songs - */ - fun globalSongCount(): Int { - return getSongBlockMetadata(musicStore.getBlocksWithType("publish_release")).size - } - - private fun getSongBlockMetadata(songsHistory: List): Array> { - val features = Array(songsHistory.size) { Array(totalAmountFeatures) { 0.0 } } - for (i in songsHistory.indices) { - features[i] = blockMetadata(songsHistory[i]) - } - return features - } - - /** - * process local files adnd add songs features to the db - */ - fun addAllLocalFeatures() { - if (!musicDir.isDirectory) return - - val allFiles = musicDir.listFiles() ?: return - Log.i("Recommend", "Amount of files is ${allFiles.size}") - - var idx = 0 - for (albumFile in allFiles) { - Log.i("Recommend", "Local album is ${albumFile.name}") - if (albumFile.isDirectory) { - val audioFiles = albumFile.listFiles(AudioFileFilter()) ?: continue - Log.i("Recommend", "Local songs amount in alum: ${audioFiles.size}") - for (f in audioFiles) { - if (Mp3File(f).id3v2Tag != null) { - val updatedFile = Mp3File(f) - - val k = "local-${updatedFile.id3v2Tag.title}-${updatedFile.id3v2Tag.artist}" - Log.i("Recommend", "${updatedFile.filename} haveFeature: ${haveFeature(k, zerosFine = false)}") - if (!haveFeature(k, zerosFine = false)) { - try { - val mp3Features = extractMP3Features(Mp3File(f)) - val count = 1 - database.dbFeaturesQueries.addFeature( - key = k, - songFeatures = mp3Features.contentToString(), - count = count.toLong() - ) - } catch (e: Exception) { - Log.e("Recommend", "Extracting audio features failed!") - Log.e("Recommend", e.toString()) - } - } - idx += 1 - } - } - } - } - } - - /** - * this method is used upon reception of gossiped features (by other peers) - * process received features and store to the table of unseen features - * if don't have this song locally - * - * @param songIdentifier song id - * @param features song feature - */ - fun addNewFeatures(songIdentifier: String, features: String) { - val seen = database.dbFeaturesQueries.getFeature(songIdentifier).executeAsOneOrNull() - val unseen = database.dbUnseenFeaturesQueries.getFeature(songIdentifier).executeAsOneOrNull() - if (seen == null && unseen == null) { - database.dbUnseenFeaturesQueries.addFeature( - key = songIdentifier, - songFeatures = features - ) - } - } - - /** - * get local song data for model training - * - * @return training data (pair of features and labels) from local songs - */ - fun getLocalSongData(): Pair>, IntArray> { - if (!essentiaJob.isActive) - essentiaJob = GlobalScope.launch { addAllLocalFeatures() } // analyze local music files - val batch = database.dbFeaturesQueries.getAllFeatures().executeAsList() - if (batch.isEmpty()) { - Log.w( - "Recommend", - "Local feature database is empty! " + - "Analyzing files in background thread now, current recommendation will be empty." - ) - return Pair(emptyArray(), emptyArray().toIntArray()) - } - val features = Array(batch.size) { Array(totalAmountFeatures) { 0.0 } } - val playcounts = Array(batch.size) { 0 }.toIntArray() - for (i in batch.indices) { - features[i] = Json.decodeFromString(batch[i].songFeatures!!).toTypedArray() - playcounts[i] = batch[i].count.toInt() - } - return Pair(features, playcounts) - } - - /** - * load new songs from trustchain - * - * @return pair of new song's features and corresponding trustchain blocks - */ - fun getNewSongs(): Pair>, List> { - val songsHistory = musicStore.getBlocksWithType("publish_release") - val data = getSongBlockMetadata(songsHistory) - return Pair(data, songsHistory) - } - - /** - * extracts mp3features from a given file using Essentia - * - * @param mp3File file with song - * @return features - */ - private fun extractMP3Features(mp3File: Mp3File): Array { - var features = Array(225) { -1.0 } - val year = (mp3File.id3v2Tag ?: mp3File.id3v1Tag)?.year?.toDouble() ?: -1.0 - val genre = (mp3File.id3v2Tag ?: mp3File.id3v1Tag)?.genre?.toDouble() ?: -1.0 - try { - val filename = mp3File.filename - - val k = if (mp3File.id3v2Tag != null) - "local-${mp3File.id3v2Tag.title}-${mp3File.id3v2Tag.artist}" - else if (mp3File.id3v1Tag != null) - "local-${mp3File.id3v1Tag.title}-${mp3File.id3v1Tag.artist}" - else - "" - - if (haveFeature(k)) { - val featureText = database.dbFeaturesQueries.getFeature(k).executeAsOneOrNull() - ?.songFeatures - ?: database.dbUnseenFeaturesQueries.getFeature(k).executeAsOneOrNull()?.songFeatures - return Json.decodeFromString(featureText!!).toTypedArray() - } - - val jsonfile = filename.replace(".mp3", ".json") - - if (!File(jsonfile).exists()) { - if (Essentia.extractData(filename, jsonfile) == 1) { - Log.e("Recommend", "Error extracting data with Essentia") - } else { - Log.i("Recommend", "Got essentia features for $filename") - } - } - val essentiaFeatures = JSONObject(File(jsonfile).bufferedReader().use { it.readText() }) - val keys = arrayOf( - "barkbands_crest", "barkbands_flatness_db", "barkbands_kurtosis", "barkbands_skewness", "barkbands_spread", - "dissonance", "erbbands_crest", "erbbands_flatness_db", "erbbands_kurtosis", "erbbands_skewness", - "erbbands_spread", "hfc", "melbands_crest", "melbands_flatness_db", "melbands_kurtosis", "melbands_skewness", - "melbands_spread", "pitch_salience", "spectral_centroid", "spectral_complexity", "spectral_decrease", - "spectral_energy", "spectral_energyband_high", "spectral_energyband_low", "spectral_energyband_middle_high", - "spectral_energyband_middle_low", "spectral_entropy", "spectral_flux", "spectral_kurtosis", "spectral_rms", - "spectral_rolloff", "spectral_skewness", "spectral_spread", "spectral_strongpeak", "zerocrossingrate" - ) - - var dynamic_complexity = 0.0 - var average_loudness = 0.0 - var integrated_loudness = 0.0 - var loudness_range = 0.0 - var momentary = arrayOf(0.0, 0.0, 0.0, 0.0, 0.0) - var short_term = arrayOf(0.0, 0.0, 0.0, 0.0, 0.0) - val lowlevelStats = Array(keys.size) { Array(5) { -1.0 } } - - if (essentiaFeatures.has("lowlevel")) { - val lowlevel = essentiaFeatures.getJSONObject("lowlevel") - try { - dynamic_complexity = lowlevel.getDouble("dynamic_complexity") - average_loudness = lowlevel.getDouble("average_loudness") - integrated_loudness = lowlevel.getJSONObject("loudness_ebu128").getDouble("integrated") - loudness_range = lowlevel.getJSONObject("loudness_ebu128").getDouble("loudness_range") - momentary = stats(lowlevel.getJSONObject("loudness_ebu128").getJSONObject("momentary")) - short_term = stats(lowlevel.getJSONObject("loudness_ebu128").getJSONObject("short_term")) - - for ((i, key) in keys.withIndex()) { - lowlevelStats[i] = stats(lowlevel.getJSONObject(key)) - } - } catch (e: java.lang.Exception) { - Log.e("Recommend", e.toString()) - } - } - - val tonal: JSONObject - var key = arrayOf(0.0, 0.0) - var key_edma = arrayOf(0.0, 0.0) - var key_krumhansl = arrayOf(0.0, 0.0) - var key_temperley = arrayOf(0.0, 0.0) - var chords_strength = arrayOf(0.0, 0.0, 0.0, 0.0, 0.0) - var hpcp_crest = arrayOf(0.0, 0.0, 0.0, 0.0, 0.0) - var hpcp_entropy = arrayOf(0.0, 0.0, 0.0, 0.0, 0.0) - var tuning_nontempered_energy_ratio = 0.0 - var tuning_diatonic_strength = 0.0 - if (essentiaFeatures.has("tonal")) { - tonal = essentiaFeatures.getJSONObject("tonal") - try { - key = scale2label(tonal.getString("chords_key"), tonal.getString("chords_scale")) - key_edma = scale2label( - tonal.getJSONObject("key_edma").getString("key"), - tonal.getJSONObject("key_edma").getString("scale") - ) - key_krumhansl = scale2label( - tonal.getJSONObject("key_krumhansl").getString("key"), - tonal.getJSONObject("key_krumhansl").getString("scale") - ) - key_temperley = scale2label( - tonal.getJSONObject("key_temperley").getString("key"), - tonal.getJSONObject("key_temperley").getString("scale") - ) - chords_strength = stats(tonal.getJSONObject("chords_strength")) - hpcp_crest = stats(tonal.getJSONObject("hpcp_crest")) - hpcp_entropy = stats(tonal.getJSONObject("hpcp_entropy")) - tuning_nontempered_energy_ratio = tonal.getDouble("tuning_nontempered_energy_ratio") - tuning_diatonic_strength = tonal.getDouble("tuning_diatonic_strength") - } catch (e: java.lang.Exception) { - Log.e("Recommend", e.toString()) - } - } - - val rhythm: JSONObject - var bpm = 0.0 - var danceability = 0.0 - var beats_loudness = arrayOf(0.0, 0.0, 0.0, 0.0, 0.0) - if (essentiaFeatures.has("rhythm")) { - try { - rhythm = essentiaFeatures.getJSONObject("rhythm") - bpm = rhythm.getDouble("bpm") - danceability = rhythm.getDouble("danceability") - beats_loudness = stats(rhythm.getJSONObject("beats_loudness")) - } catch (e: java.lang.Exception) { - Log.e("Recommend", e.toString()) - } - } - - val metadata: JSONObject - var length = 0.0 - var replay_gain = 0.0 - if (essentiaFeatures.has("rhythm")) { - metadata = essentiaFeatures.getJSONObject("metadata") - try { - length = metadata.getJSONObject("audio_properties").getDouble("length") - replay_gain = metadata.getJSONObject("audio_properties").getDouble("replay_gain") - } catch (e: java.lang.Exception) { - Log.e("Recommend", e.toString()) - } - } - - features = arrayOf( - arrayOf( - year, genre, length, replay_gain, dynamic_complexity, average_loudness, - integrated_loudness, loudness_range, bpm, danceability, tuning_nontempered_energy_ratio, - tuning_diatonic_strength - ), - momentary, short_term, lowlevelStats.flatten().toTypedArray(), key, key_edma, key_krumhansl, key_temperley, - chords_strength, hpcp_crest, hpcp_entropy, beats_loudness - ).flatten().toTypedArray() - } catch (e: Exception) { - Log.e("Recommend", "Essentia extraction failed:") - Log.e("Recommend", e.stackTraceToString()) - } - - // log transform on features - for ((i, feat) in features.withIndex()) { - features[i] = if (feat < 0.0) -log10(-feat) else if (feat > 0.0) log10(feat) else 0.0 - } - Log.i("Recommend", "Extracted MP3 features") - - /** - * pick most 'promising' Essentia features, read docs for more insight - * we still keep 2 block features - year and genre, - * in order to have some data for completely unseen features - */ - val finalFeatures = Array(totalAmountFeatures) { 0.0 } - finalFeatures[0] = year - finalFeatures[1] = genre - for ((idx, fidx) in this.highVarianceFeatureLabels.withIndex()) { - finalFeatures[idx] = features[fidx + 1] - } - - return finalFeatures - } - - fun getMyFeatures(): List { - return this.database.dbFeaturesQueries.getAllFeatures().executeAsList() - } - - private fun getRemoteFeatures(): List { - return this.database.dbUnseenFeaturesQueries.getAllFeatures().executeAsList() - } - - private fun haveFeature(key: String, zerosFine: Boolean = true): Boolean { - for (feat in getMyFeatures()) - if (feat.key == key) { - return if (zerosFine) true else { - for ((i, d) in Json.decodeFromString(feat.songFeatures!!).toTypedArray().withIndex()) { - if (i >= 2 && d != 0.0) - return true - } - return false - } - } - for (feat in getRemoteFeatures()) - if (feat.key == key) { - return if (zerosFine) true else { - for ((i, d) in Json.decodeFromString(feat.songFeatures!!).toTypedArray().withIndex()) { - if (i >= 2 && d != 0.0) - return true - } - return false - } - } - return false - } - - companion object { - @SuppressLint("StaticFieldLeak") - private lateinit var instance: RecommenderStore - fun getInstance(musicStore: TrustChainSQLiteStore, database: Database): RecommenderStore { - if (!Companion::instance.isInitialized) { - instance = RecommenderStore(musicStore, database) - } - return instance - } - } -} - -// positions of scales on the circle of fifths -val majorKeys = mapOf( - "C" to 0.0, "G" to 1.0, "D" to 2.0, "A" to 3.0, "E" to 4.0, "B" to 5.0, "Gb" to 6.0, - "F#" to 6.0, "Db" to 7.0, "Ab" to 8.0, "Eb" to 9.0, "Bb" to 10.0, "F" to 11.0 -) -val minorKeys = mapOf( - "A" to 0.0, "E" to 1.0, "B" to 2.0, "F#" to 3.0, "C#" to 4.0, "G#" to 5.0, "D#" to 6.0, - "Eb" to 6.0, "Bb" to 7.0, "F" to 8.0, "C" to 9.0, "G" to 10.0, "D" to 11.0 -) -fun scale2label(key: String, mode: String): Array { - val keyCode = (if (mode == "major") majorKeys[key] else minorKeys[key]) ?: -1.0 - val modeCode = if (mode == "major") 0.0 else 1.0 - return arrayOf(keyCode, modeCode) -} -fun stats(obj: JSONObject): Array { - return arrayOf( - obj.getDouble("min"), - obj.getDouble("median"), - obj.getDouble("mean"), - obj.getDouble("max"), - obj.getDouble("var"), - ) -} diff --git a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/ipv8/FeaturesExchangeMessage.kt b/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/ipv8/FeaturesExchangeMessage.kt deleted file mode 100644 index d08827601..000000000 --- a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/ipv8/FeaturesExchangeMessage.kt +++ /dev/null @@ -1,61 +0,0 @@ -package nl.tudelft.trustchain.gossipML.ipv8 - -import nl.tudelft.ipv8.messaging.* - -/** - * This is a message from a peer sending local songs features to other peers - */ -open class FeaturesExchangeMessage @ExperimentalUnsignedTypes constructor( - val originPublicKey: ByteArray, - var ttl: UInt, - val songIdentifier: String, - val features: String -) : Serializable { - - override fun serialize(): ByteArray { - return originPublicKey + - serializeUInt(ttl) + - serializeVarLen(songIdentifier.toByteArray(Charsets.UTF_8)) + - serializeVarLen(features.toByteArray(Charsets.UTF_8)) - } - - @kotlin.ExperimentalUnsignedTypes - fun checkTTL(): Boolean { - ttl -= 1u - if (ttl < 1u) return false - return true - } - - companion object Deserializer : Deserializable { - @ExperimentalUnsignedTypes - @JvmStatic - override fun deserialize(buffer: ByteArray, offset: Int): Pair { - var localOffset = 0 - val originPublicKey = buffer.copyOfRange( - offset + localOffset, - offset + localOffset + SERIALIZED_PUBLIC_KEY_SIZE - ) - - localOffset += SERIALIZED_PUBLIC_KEY_SIZE - val ttl = deserializeUInt(buffer, offset + localOffset) - localOffset += SERIALIZED_UINT_SIZE - - val (songIdentifierBytes, songIdentifierSize) = deserializeVarLen(buffer, offset + localOffset) - val songIdentifier = songIdentifierBytes.toString(Charsets.UTF_8) - localOffset += songIdentifierSize - val (featuresBytes, featuresSize) = deserializeVarLen(buffer, offset + localOffset) - val features = featuresBytes.toString(Charsets.UTF_8) - localOffset += featuresSize - - return Pair( - first = FeaturesExchangeMessage( - originPublicKey = originPublicKey, - ttl = ttl, - songIdentifier = songIdentifier, - features = features - ), - second = localOffset - ) - } - } -} diff --git a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/ipv8/ModelExchangeMessage.kt b/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/ipv8/ModelExchangeMessage.kt deleted file mode 100644 index d44fd4b74..000000000 --- a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/ipv8/ModelExchangeMessage.kt +++ /dev/null @@ -1,74 +0,0 @@ -package nl.tudelft.trustchain.gossipML.ipv8 - -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import nl.tudelft.ipv8.messaging.* -import nl.tudelft.trustchain.gossipML.models.Model -import nl.tudelft.trustchain.gossipML.models.collaborative_filtering.PublicMatrixFactorization -import nl.tudelft.trustchain.gossipML.models.feature_based.Adaline -import nl.tudelft.trustchain.gossipML.models.feature_based.Pegasos - -/** - * This is a message from a peer sending walking model to other peers - */ -open class ModelExchangeMessage @ExperimentalUnsignedTypes constructor( - val originPublicKey: ByteArray, - var ttl: UInt, - val modelType: String, - val model: Model -) : Serializable { - - override fun serialize(): ByteArray { - return originPublicKey + - serializeUInt(ttl) + - serializeVarLen(modelType.toByteArray(Charsets.UTF_8)) + - serializeVarLen(model.serialize().toByteArray(Charsets.UTF_8)) - } - - @kotlin.ExperimentalUnsignedTypes - fun checkTTL(): Boolean { - ttl -= 1u - if (ttl < 1u) return false - return true - } - - companion object Deserializer : Deserializable { - @ExperimentalUnsignedTypes - @JvmStatic - override fun deserialize(buffer: ByteArray, offset: Int): Pair { - var localOffset = 0 - val originPublicKey = buffer.copyOfRange( - offset + localOffset, - offset + localOffset + SERIALIZED_PUBLIC_KEY_SIZE - ) - - localOffset += SERIALIZED_PUBLIC_KEY_SIZE - val ttl = deserializeUInt(buffer, offset + localOffset) - localOffset += SERIALIZED_UINT_SIZE - - val (modelTypeBytes, modelTypeSize) = deserializeVarLen(buffer, offset + localOffset) - val modelType = modelTypeBytes.toString(Charsets.UTF_8) - localOffset += modelTypeSize - val (modelBytes, modelSize) = deserializeVarLen(buffer, offset + localOffset) - val modelJsonStr = modelBytes.toString(Charsets.UTF_8) - localOffset += modelSize - - val model = if (modelType == "Adaline") - Json.decodeFromString(modelJsonStr) - else if (modelType == "Pegasos") - Json.decodeFromString(modelJsonStr) - else - Json.decodeFromString(modelJsonStr) - - return Pair( - first = ModelExchangeMessage( - originPublicKey = originPublicKey, - ttl = ttl, - modelType = modelType, - model = model, - ), - second = localOffset - ) - } - } -} diff --git a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/ipv8/RequestModelMessage.kt b/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/ipv8/RequestModelMessage.kt deleted file mode 100644 index 39e782e48..000000000 --- a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/ipv8/RequestModelMessage.kt +++ /dev/null @@ -1,54 +0,0 @@ -package nl.tudelft.trustchain.gossipML.ipv8 - -import nl.tudelft.ipv8.messaging.* - -/** - * This is a message from a peer sending and asking for a model from other peers - */ -open class RequestModelMessage @ExperimentalUnsignedTypes constructor( - val originPublicKey: ByteArray, - var ttl: UInt, - val modelType: String -) : Serializable { - - override fun serialize(): ByteArray { - return originPublicKey + serializeUInt(ttl) - } - - @kotlin.ExperimentalUnsignedTypes - fun checkTTL(): Boolean { - ttl -= 1u - if (ttl < 1u) return false - return true - } - - companion object Deserializer : Deserializable { - @ExperimentalUnsignedTypes - @JvmStatic - override fun deserialize(buffer: ByteArray, offset: Int): Pair { - var localOffset = 0 - - val originPublicKey = buffer.copyOfRange( - offset + localOffset, - offset + localOffset + SERIALIZED_PUBLIC_KEY_SIZE - ) - localOffset += SERIALIZED_PUBLIC_KEY_SIZE - - val ttl = deserializeUInt(buffer, offset + localOffset) - localOffset += SERIALIZED_UINT_SIZE - - val (modelTypeBytes, modelTypeSize) = deserializeVarLen(buffer, offset + localOffset) - val modelType = modelTypeBytes.toString(Charsets.UTF_8) - localOffset += modelTypeSize - - return Pair( - first = RequestModelMessage( - originPublicKey = originPublicKey, - ttl = ttl, - modelType = modelType - ), - second = localOffset - ) - } - } -} diff --git a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/ipv8/SerializableSparseArray.kt b/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/ipv8/SerializableSparseArray.kt deleted file mode 100644 index b9a4e6b49..000000000 --- a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/ipv8/SerializableSparseArray.kt +++ /dev/null @@ -1,74 +0,0 @@ -package nl.tudelft.trustchain.gossipML.ipv8 - -import android.util.SparseArray -import kotlinx.serialization.* -import java.io.IOException -import java.io.ObjectInputStream -import java.io.ObjectOutputStream - -/** - * @author Asaf Pinhassi www.mobiledev.co.il https://stackoverflow.com/a/21574953 - * @param - */ -@Serializable -class SerializableSparseArray : SparseArray { - constructor(capacity: Int) : super(capacity) {} - constructor() : super() {} - - /** - * This method is private but it is called using reflection by java - * serialization mechanism. It overwrites the default object serialization. - * - * **IMPORTANT** - * The access modifier for this method MUST be set to **private** otherwise [java.io.StreamCorruptedException] - * will be thrown. - * - * @param oos - * the stream the data is stored into - * @throws IOException - * an exception that might occur during data storing - */ - @Throws(IOException::class) - private fun writeObject(oos: ObjectOutputStream) { - val data = arrayOfNulls(size()) - for (i in data.indices.reversed()) { - val pair = arrayOf(keyAt(i), valueAt(i)) - data[i] = pair - } - oos.writeObject(data) - } - - /** - * This method is private but it is called using reflection by java - * serialization mechanism. It overwrites the default object serialization. - * - *



**IMPORTANT** - * The access modifier for this method MUST be set to **private** otherwise [java.io.StreamCorruptedException] - * will be thrown. - * - * @param oos - * the stream the data is read from - * @throws IOException - * an exception that might occur during data reading - * @throws ClassNotFoundException - * this exception will be raised when a class is read that is - * not known to the current ClassLoader - * @throws ClassCastException - * thrown when cast to E does not succeed - */ - @Throws(IOException::class, ClassNotFoundException::class, ClassCastException::class) - private fun readObject(ois: ObjectInputStream) { - val data = ois.readObject() as Array<*> - for (i in data.indices.reversed()) { - val pair = data[i] as Array<*> - val key = pair[0] as Int - @Suppress("UNCHECKED_CAST") val value = pair[1] as E - this.append(key, value) - } - return - } - - companion object { - private const val serialVersionUID = 824056059663678000L - } -} diff --git a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/models/DietNumpy.kt b/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/models/DietNumpy.kt deleted file mode 100644 index 3a8809c45..000000000 --- a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/models/DietNumpy.kt +++ /dev/null @@ -1,45 +0,0 @@ -package nl.tudelft.trustchain.gossipML.models - -operator fun Array.plus(other: Double): Array { - return this.map { it + other }.toTypedArray() -} - -operator fun Double.plus(doubles: Array): Array { - return doubles + this -} - -operator fun Array.minus(other: Double): Array { - return this.map { it - other }.toTypedArray() -} - -operator fun Double.minus(doubles: Array): Array { - return doubles - this -} - -operator fun Array.times(other: Double): Array { - return this.map { it * other }.toTypedArray() -} - -operator fun Double.times(doubles: Array): Array { - return doubles * this -} - -operator fun Array.div(other: Double): Array { - return this.map { it / other }.toTypedArray() -} - -operator fun Double.div(doubles: Array): Array { - return doubles / this -} - -operator fun Array.plus(other: Array): Array { - return this.zip(other).map { (i1, i2) -> i1 + i2 }.toTypedArray() -} - -operator fun Array.minus(other: Array): Array { - return this.zip(other).map { (i1, i2) -> i1 - i2 }.toTypedArray() -} - -operator fun Array.times(other: Array): Double { - return this.zip(other).map { (i1, i2) -> i1 * i2 }.toTypedArray().sum() -} diff --git a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/models/Model.kt b/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/models/Model.kt deleted file mode 100644 index c7df97548..000000000 --- a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/models/Model.kt +++ /dev/null @@ -1,20 +0,0 @@ -package nl.tudelft.trustchain.gossipML.models - -import kotlinx.serialization.Serializable -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json - -/** - * General model class to accommodate both feature-based and - * collaborative filtering models - * - * @property name model name - */ -@Serializable -open class Model(open val name: String) { - open fun serialize(): String { - return Json.encodeToString(this) - } - - open fun update() {} -} diff --git a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/models/OnlineModel.kt b/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/models/OnlineModel.kt deleted file mode 100644 index e6ed5717c..000000000 --- a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/models/OnlineModel.kt +++ /dev/null @@ -1,71 +0,0 @@ -package nl.tudelft.trustchain.gossipML.models -import kotlinx.serialization.* -import kotlinx.serialization.json.Json -import java.util.* - -/** - * Online model generalization of Adaline and Pegasos models (feature-based models) - */ -@Serializable -open class OnlineModel : Model { - val amountFeatures: Int - var weights: Array - - constructor(amountFeatures: Int, name: String) : super(name) { - this.amountFeatures = amountFeatures - this.weights = Array(amountFeatures) { _ -> 1.0 } - } - - /** - * merge 2 online models - * - * @param otherOnlineModel other model to merge with - * @return merged model - */ - fun merge(otherOnlineModel: OnlineModel): OnlineModel { - for (idx in weights.indices) { - weights[idx] = (weights[idx] * 0.9) + (otherOnlineModel.weights[idx] / 10) // it was divied by 2? - } - return this - } - - /** - * make predictions for a batch of input instances - * - * @param x - array of feature arrays - * @param y - array of labels - * @return array of predictions - */ - fun predict(x: Array>): Array { - val result = Array(x.size) { 0.0 } - for ((idx, item) in x.withIndex()) { - result[idx] = predict(item) - } - return result - } - - /** - * test method for getting prediction accuracy on some test instances - * - * @param x - array of feature arrays - * @param y - array of labels - * @return prediction score - */ - fun score(x: Array>, y: Array): Double { - var correct = 0.0 - for (i in x.indices) if (predict(x[i]) == y[i]) correct ++ - return (correct / x.size) - } - - open fun update(x: Array>, y: Array) {} - - open fun predict(x: Array): Double { - return 1.0 - } - - open fun update(x: Array, y: Double) {} - - override fun serialize(): String { - return Json.encodeToString(this) - } -} diff --git a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/models/collaborative_filtering/MatrixFactorization.kt b/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/models/collaborative_filtering/MatrixFactorization.kt deleted file mode 100644 index faf592e4f..000000000 --- a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/models/collaborative_filtering/MatrixFactorization.kt +++ /dev/null @@ -1,280 +0,0 @@ -package nl.tudelft.trustchain.gossipML.models.collaborative_filtering - -import android.util.Log -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.builtins.MapSerializer -import kotlinx.serialization.builtins.serializer -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encodeToString -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.json.Json -import nl.tudelft.trustchain.gossipML.models.Model -import nl.tudelft.trustchain.gossipML.models.plus -import nl.tudelft.trustchain.gossipML.models.times -import java.lang.Double.NEGATIVE_INFINITY -import java.util.* -import kotlin.math.abs -import kotlin.math.max -import kotlin.math.sqrt -import kotlin.random.Random.Default.nextDouble - -object SongFeatureSerializer : KSerializer> { - private val mapSerializer = MapSerializer(String.serializer(), SongFeature.serializer()) - override val descriptor: SerialDescriptor = mapSerializer.descriptor - override fun serialize(encoder: Encoder, value: SortedMap) { - mapSerializer.serialize(encoder, value) - } - override fun deserialize(decoder: Decoder): SortedMap { - return mapSerializer.deserialize(decoder).toSortedMap() - } -} - -object RatingSerializer : KSerializer> { - private val mapSerializer = MapSerializer(String.serializer(), Double.serializer()) - override val descriptor: SerialDescriptor = mapSerializer.descriptor - override fun serialize(encoder: Encoder, value: SortedMap) { - mapSerializer.serialize(encoder, value) - } - override fun deserialize(decoder: Decoder): SortedMap { - return mapSerializer.deserialize(decoder).toSortedMap() - } -} - -/** - * Publicly avaliable part of matrix factarization model - * - * @property peerFeatures amount of peer features - */ -@Serializable -data class PublicMatrixFactorization( - @Serializable(with = SongFeatureSerializer::class) - val peerFeatures: SortedMap -) : Model("PublicMatrixFactorization") - -/** - * Class that represents song features for matrix factorization model - * - * @property age song age - * @property feature normal song features - * @property bias song bias - */ -@Serializable -data class SongFeature( - var age: Double, - var feature: Array, - var bias: Double -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - other as SongFeature - if (age != other.age) return false - if (!feature.contentEquals(other.feature)) return false - if (bias != other.bias) return false - return true - } - - override fun hashCode(): Int { - var result = age.hashCode() - result = 31 * result + feature.contentHashCode() - result = 31 * result + bias.hashCode() - return result - } -} - -fun songFeaturesFromArrays(age: Array, features: Array>, bias: Array): Array { - return age.zip(features).zip(bias) { (a, b), c -> SongFeature(a, b, c) }.toTypedArray() -} - -/** - * General matrix factorization model - * - * @property ratings - */ -@Serializable -open class MatrixFactorization( - @Serializable(with = RatingSerializer::class) var ratings: SortedMap -) : Model("MatrixFactorization") { - val k = 5 - - private val lr = 0.01 - private val lambda = 1 - - private val minR = 0.0 - private var maxR = 20.0 - - @Serializable(with = SongFeatureSerializer::class) - var songFeatures = ratings.keys.zip( - songFeaturesFromArrays( - Array(ratings.size) { i -> if (ratings[ratings.keys.toTypedArray()[i]]!! > 0.0) 1.0 else 0.0 }, // ages - Array(ratings.size) { Array(k) { initFeat() } }, // song features - Array(ratings.size) { 0.0 } // biases - ) - ).toMap().toSortedMap() - - private var rateFeature = Array(k) { initFeat() } - private var rateBias = 0.0 - - constructor(peerModel: PublicMatrixFactorization) : this(peerModel.peerFeatures) - constructor(peerFeatures: SortedMap, True: Boolean = true) : this( - ratings = peerFeatures.keys.zip(Array(peerFeatures.size) { 0.0 }).toMap().toSortedMap() - ) { - // True in constructor to avoid java type signature class with main constructor - if (True) this.songFeatures = peerFeatures.toSortedMap() - } - - /** - * Initalize feature - * - * @return initialized feature - */ - private fun initFeat(): Double { - return nextDouble() * sqrt((maxR - minR) / k) - } - - /** - * Predict most fitting song - * - * @return name of most relevant song - */ - fun predict(): String { - var bestSong = "" - var mostRelevant = NEGATIVE_INFINITY - songFeatures.forEach { - val (name, triple) = it - val (_, feature, bias) = triple - if (ratings[name]!! == 0.0) { - val relevance = rateFeature * feature + rateBias + bias - if (relevance > mostRelevant) { - bestSong = name - mostRelevant = relevance - } - } - } - Log.i("Recommend", "Best colaborative score: $mostRelevant") - return bestSong - } - - /** - * Updates matrix ratings - * - * @param newRatings new ratings to be merged with old ones - */ - fun updateRatings(newRatings: SortedMap) { - for ((name, rating) in newRatings) { - ratings[name] = rating - if (songFeatures[name] == null) { // maybe initialize newly rated song - songFeatures[name] = SongFeature(0.0, Array(k) { initFeat() }, 0.0) - } - } - update() - } - - /** - * Update helper method to update songs features - * - */ - override fun update() { - var errTotal = 0.0 - songFeatures.forEach { - val (name, triple) = it - val (age, feature, bias) = triple - if (ratings[name] != 0.0) { - val err = ratings[name]!! - rateFeature * feature - rateBias - bias - errTotal += abs(err) - - val (newSongFeature, newRateFeature) = Pair( - (1.0 - lr * lambda) * feature + lr * err * rateFeature, - (1.0 - lr * lambda) * rateFeature + lr * err * feature - ) - - songFeatures[name]!!.age = age + 1.0 - - songFeatures[name]!!.feature = newSongFeature - rateFeature = newRateFeature - - songFeatures[name]!!.bias += lr * err - rateBias += lr * err - } - } - } - - /** - * Open function to merge matrix fatorization models - * - * @param peerModel - */ - open fun merge(peerModel: PublicMatrixFactorization) { - merge(peerModel.peerFeatures) - } - - /** - * Actual merging function - * - * @param peerFeatures features to be merged with - */ - open fun merge(peerFeatures: SortedMap) { - if (peerFeatures.keys.toSet() != songFeatures.keys) { - for (name in peerFeatures.keys.toSet() + songFeatures.keys.toSet()) { - // initialize rows not yet present in each map - if (songFeatures[name] == null) { - songFeatures[name] = SongFeature(0.0, Array(k) { initFeat() }, 0.0) - } - if (ratings[name] == null) { - ratings[name] = 0.0 - } - if (peerFeatures[name] == null) { - peerFeatures[name] = SongFeature(0.0, Array(k) { initFeat() }, 0.0) - } - } - } - - // age weighted average, more weight to rows which have been updated many times - songFeatures.forEach { - val (name, triple) = it - val (age, feature, bias) = triple - val tripleNew = peerFeatures[name]!! - val (ageNew, featureNew, biasNew) = tripleNew - if (ageNew != 0.0) { - val w = ageNew / (age + ageNew) - songFeatures[name] = SongFeature( - age = max(age, ageNew), - feature = feature * (1 - w) + featureNew * w, - bias = bias * (1 - w) + biasNew * w - ) - } - } - - update() - } - - private fun compress(map: SortedMap): SortedMap { - /* - from paper: subsample songFeatures to a fixed number of rows instead of whole thing - maybe also compress with lz4 or something? - */ - return map - } - - /** - * the function used during automatic serialization in model exchange messages - * don't serialize private ratings matrix - */ - override fun serialize(): String { - return this.serialize(private = false) - } - - /** - * the function used during serialization for local database storage - */ - fun serialize(private: Boolean): String { - Log.i( - "Recommend", - "Serializing MatrixFactorization, including private data: $private" - ) - return if (private) Json.encodeToString(this) - else Json.encodeToString(PublicMatrixFactorization(compress(songFeatures.toSortedMap()))) - } -} diff --git a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/models/feature_based/Adaline.kt b/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/models/feature_based/Adaline.kt deleted file mode 100644 index bee9c97d0..000000000 --- a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/models/feature_based/Adaline.kt +++ /dev/null @@ -1,102 +0,0 @@ -package nl.tudelft.trustchain.gossipML.models.feature_based -import kotlinx.serialization.Serializable -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import nl.tudelft.trustchain.gossipML.models.OnlineModel -import java.util.* - -/** - * Machine Learning Adaline model (Adaptive Linear Neuron) - * Single layer NN used in gossiping user preference models - */ -@Serializable -class Adaline : OnlineModel { - private val learningRate: Double - var bias = Random().nextDouble() - - constructor(learningRate: Double, amountFeatures: Int) : super(amountFeatures, "Adaline") { - this.learningRate = learningRate - } - - /** - * update model with single data instance - * - * @param x - feature array - * @param y - label - */ - override fun update(x: Array, y: Double) { - val error = y - activation(forward(x)) - this.bias += this.learningRate * error - for ((idx, item) in x.withIndex()) { - weights[idx] = weights[idx] + learningRate * error * item - } - } - - /** - * update model with multiple data instances - * - * @param x - array of feature arrays - * @param y - array of labels - */ - override fun update(x: Array>, y: Array) { - require(x.size == y.size) { - String.format("Input vector x of size %d not equal to length %d of y", x.size, y.size) - } - for (i in x.indices) { - update(x[i], y[i]) - } - } - - /** - * predict score for a given data instance - * - * @param x feature array - * @return predicted score - */ - override fun predict(x: Array): Double { - return activation(forward(x)) - } - - /** - * binary classification of input instance based on activation - * e.g. - like / dislike song - * - * @param x feature array - * @return binary predicted label - */ - fun classify(x: Array): Double { - return if (activation(forward(x)) >= 0.0) { - 1.0 - } else { - 0.0 - } - } - - /** - * Adaline activation function - * - * @param x output of the previous neuron - * @return activation of x - */ - private fun activation(x: Double): Double { - return x - } - - /** - * Forward pass of Adaline - * - * @param x single data instance - * @return propagated value - */ - private fun forward(x: Array): Double { - var weightedSum = this.bias - for (idx in 1 until x.size) { - weightedSum += this.weights[idx] * x[idx] - } - return weightedSum - } - - override fun serialize(): String { - return Json.encodeToString(this) - } -} diff --git a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/models/feature_based/Pegasos.kt b/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/models/feature_based/Pegasos.kt deleted file mode 100644 index 0966bafc3..000000000 --- a/gossipML/src/main/java/nl/tudelft/trustchain/gossipML/models/feature_based/Pegasos.kt +++ /dev/null @@ -1,107 +0,0 @@ -package nl.tudelft.trustchain.gossipML.models.feature_based -import kotlinx.serialization.Serializable -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import nl.tudelft.trustchain.gossipML.models.OnlineModel -import nl.tudelft.trustchain.gossipML.models.plus -import nl.tudelft.trustchain.gossipML.models.times -import java.util.* - -/** - * Pegasos Machine Learning model (Primal Estimated sub-gradient solver for SVM) - */ -@Serializable -class Pegasos : OnlineModel { - private val regularization: Double - private val iterations: Int - - constructor(regularization: Double, amountFeatures: Int, iterations: Int) : super(amountFeatures, "Pegasos") { - this.regularization = regularization - this.iterations = iterations - } - - /** - * update model with single data instance - * - * @param x - feature array - * @param y - label - */ - override fun update(x: Array, y: Double) { - val eta = 1.0 / regularization - gradientSVM(x, y, eta) - } - - /** - * update model with multiple data instances - * - * @param x - array of feature arrays - * @param y - array of labels - */ - override fun update(x: Array>, y: Array) { - require(x.size == y.size) { - String.format("Input vector x of size %d not equal to length %d of y", x.size, y.size) - } - - for (iteration in 0..iterations) { - val i = Random().nextInt(x.size) - val eta = 1.0 / (regularization * (i + 1)) - - gradientSVM(x[i], y[i], eta) - } - } - - /** - * Pegasos activation function used in predictions - * - * @param x output of SVM model - * @return activation of x - */ - private fun activation(x: Double): Double { - return x - } - - /** - * predict score for a given data instance - * - * @param x feature array - * @return predicted score - */ - override fun predict(x: Array): Double { - return activation(weights * x) - } - - /** - * binary classification of input instance based on activation - * e.g. - like / dislike song - * - * @param x feature array - * @return binary predicted label - */ - fun classify(x: Array): Double { - return if (activation(weights * x) >= 0.0) { - 1.0 - } else { - 0.0 - } - } - - /** - * learning function of Pegasos, updates model weights - * - * @param x - features - * @param y - label - * @param eta - step value - */ - private fun gradientSVM(x: Array, y: Double, eta: Double) { - val score = weights * x - weights = if (y * score < 1.0) { - (1.0 - eta * regularization) * weights + eta * y * x - } else { - (1.0 - eta * regularization) * weights - } - } - - override fun serialize(): String { - return Json.encodeToString(this) - } -} diff --git a/gossipML/src/main/sqldelight/nl/tudelft/gossipML/sqldelight/DbFeatures.sq b/gossipML/src/main/sqldelight/nl/tudelft/gossipML/sqldelight/DbFeatures.sq deleted file mode 100644 index 92bee512f..000000000 --- a/gossipML/src/main/sqldelight/nl/tudelft/gossipML/sqldelight/DbFeatures.sq +++ /dev/null @@ -1,22 +0,0 @@ -CREATE TABLE features ( - key TEXT NOT NULL, - songFeatures TEXT, - count INTEGER NOT NULL, - PRIMARY KEY (key) -); - -addFeature: -REPLACE INTO features (key, songFeatures, count) -VALUES(?, ?, ?); - -getFeature: -SELECT * FROM features WHERE key = ? LIMIT 1; - -getSongIds: -SELECT key FROM features; - -getAllFeatures: -SELECT * FROM features; - -deleteAllFeatures: -DELETE FROM features; diff --git a/gossipML/src/main/sqldelight/nl/tudelft/gossipML/sqldelight/DbModel.sq b/gossipML/src/main/sqldelight/nl/tudelft/gossipML/sqldelight/DbModel.sq deleted file mode 100644 index dacada5d7..000000000 --- a/gossipML/src/main/sqldelight/nl/tudelft/gossipML/sqldelight/DbModel.sq +++ /dev/null @@ -1,19 +0,0 @@ -CREATE TABLE models ( - name TEXT NOT NULL, - type TEXT NOT NULL, - parameters TEXT NOT NULL, - PRIMARY KEY (name) -); - -addModel: -REPLACE INTO models (name, type, parameters) -VALUES(?, ?, ?); - -getModel: -SELECT * FROM models WHERE name = ? LIMIT 1; - -deleteModel: -DELETE FROM models WHERE name = ?; - -deleteAll: -DELETE FROM models; diff --git a/gossipML/src/main/sqldelight/nl/tudelft/gossipML/sqldelight/DbUnseenFeatures.sq b/gossipML/src/main/sqldelight/nl/tudelft/gossipML/sqldelight/DbUnseenFeatures.sq deleted file mode 100644 index c87570e51..000000000 --- a/gossipML/src/main/sqldelight/nl/tudelft/gossipML/sqldelight/DbUnseenFeatures.sq +++ /dev/null @@ -1,21 +0,0 @@ -CREATE TABLE unseen_features ( - key TEXT NOT NULL, - songFeatures TEXT, - PRIMARY KEY (key) -); - -addFeature: -REPLACE INTO unseen_features (key, songFeatures) -VALUES(?, ?); - -getFeature: -SELECT * FROM unseen_features WHERE key = ? LIMIT 1; - -getSongIds: -SELECT key FROM unseen_features; - -getAllFeatures: -SELECT * FROM unseen_features; - -deleteAllFeatures: -DELETE FROM unseen_features; diff --git a/gossipML/src/test/java/gossipML/ipv8/ModelExchangeMessageTest.kt b/gossipML/src/test/java/gossipML/ipv8/ModelExchangeMessageTest.kt deleted file mode 100644 index f02e87107..000000000 --- a/gossipML/src/test/java/gossipML/ipv8/ModelExchangeMessageTest.kt +++ /dev/null @@ -1,74 +0,0 @@ -package nl.tudelft.trustchain.gossipML.ipv8 - -import com.goterl.lazysodium.LazySodiumJava -import com.goterl.lazysodium.SodiumJava -import nl.tudelft.ipv8.keyvault.LibNaClSK -import nl.tudelft.ipv8.util.hexToBytes -import nl.tudelft.trustchain.gossipML.models.OnlineModel -import nl.tudelft.trustchain.gossipML.models.collaborative_filtering.MatrixFactorization -import nl.tudelft.trustchain.gossipML.models.feature_based.Adaline -import nl.tudelft.trustchain.gossipML.models.feature_based.Pegasos -import org.junit.Assert -import org.junit.Test - -@ExperimentalUnsignedTypes -class ModelExchangeMessageTest { - private val lazySodium = LazySodiumJava(SodiumJava()) - private val key = LibNaClSK.fromBin( - "4c69624e61434c534b3a054b2367b4854a8bf2d12fcd12158a6731fcad9cfbff7dd71f9985eb9f064c8118b1a89c931819d3482c73ebd9be9ee1750dc143981f7a481b10496c4e0ef982".hexToBytes(), - lazySodium - ) - private val originPublicKey = key.pub().keyToBin() - private val ttl = 2u - private val model1 = Pegasos(0.4, 10, 5) - private val model2 = MatrixFactorization(Array(0) { "" }.zip(Array(0) { 0.0 }).toMap().toSortedMap()) - - @ExperimentalUnsignedTypes - @Test - fun checkTTL() { - val modelType = model1.name - val payload = ModelExchangeMessage(originPublicKey, ttl, modelType, model1) - Assert.assertTrue(payload.checkTTL()) - Assert.assertFalse(payload.checkTTL()) - } - - @ExperimentalUnsignedTypes - @Test - fun serializeAndDeserializeFeatureBasedModel() { - val modelType = model1.name - val payload = ModelExchangeMessage(originPublicKey, ttl, modelType, model1) - val delta = 0.01 - val serialized = payload.serialize() - val (deserialized, size) = ModelExchangeMessage.deserialize(serialized, 0) - Assert.assertEquals(serialized.size, size) - Assert.assertEquals(payload.modelType, deserialized.modelType) - Assert.assertArrayEquals( - (payload.model as OnlineModel).weights, - (deserialized.model as OnlineModel).weights - ) - if (payload.model is Adaline && deserialized.model is Adaline) { - Assert.assertEquals( - (payload.model as Adaline).bias, - (deserialized.model as Adaline).bias, - delta - ) - } - - Assert.assertEquals(payload.ttl, deserialized.ttl) - Assert.assertArrayEquals(payload.originPublicKey, deserialized.originPublicKey) - } - - @ExperimentalUnsignedTypes - @Test - fun serializeAndDeserializeMFBasedModel() { - val modelType = model2.name - val payload = ModelExchangeMessage(originPublicKey, ttl, modelType, model2) - val serialized = payload.serialize() - val (deserialized, size) = ModelExchangeMessage.deserialize(serialized, 0) - Assert.assertEquals(serialized.size, size) - Assert.assertEquals(payload.modelType, deserialized.modelType) - - Assert.assertEquals(payload.ttl, deserialized.ttl) - Assert.assertArrayEquals(payload.originPublicKey, deserialized.originPublicKey) - } -} diff --git a/gossipML/src/test/java/gossipML/models/ColabFilterTest.kt b/gossipML/src/test/java/gossipML/models/ColabFilterTest.kt deleted file mode 100644 index f829580dd..000000000 --- a/gossipML/src/test/java/gossipML/models/ColabFilterTest.kt +++ /dev/null @@ -1,176 +0,0 @@ -package nl.tudelft.trustchain.gossipML.models - -import nl.tudelft.trustchain.gossipML.models.collaborative_filtering.MatrixFactorization -import nl.tudelft.trustchain.gossipML.models.collaborative_filtering.PublicMatrixFactorization -import nl.tudelft.trustchain.gossipML.models.collaborative_filtering.SongFeature -import org.hamcrest.CoreMatchers.instanceOf -import org.junit.Assert -import org.junit.Test -import kotlin.math.abs -import kotlin.random.Random - -class ColabFilterTest { - - @Test - fun testCreateAndUpdate() { - val model = MatrixFactorization(Array(0) { "" }.zip(Array(0) { 0.0 }).toMap().toSortedMap()) - - model.merge( - sortedMapOf( - Pair("a", SongFeature(5.0, Array(5) { Random.nextDouble(1.0, 5.0) }, 0.0)), - Pair("b", SongFeature(2.0, Array(5) { Random.nextDouble(0.0, 3.0) }, 0.0)), - Pair("c", SongFeature(7.0, Array(5) { Random.nextDouble(0.0, 8.0) }, 0.0)), - ) - ) - - Assert.assertThat(model, instanceOf(MatrixFactorization::class.java)) - Assert.assertEquals(model.songFeatures.size, 3) - } - - @Test - fun testPredictions() { - val pubModel = PublicMatrixFactorization( - sortedMapOf( - Pair("good", SongFeature(1.0, Array(5) { Random.nextDouble(2.0, 5.0) }, 0.0)), - Pair("bad", SongFeature(1.0, Array(5) { 1.0 }, 0.0)), // nobody likes this song - ) - ) - val model = MatrixFactorization(pubModel) - val pred = model.predict() - Assert.assertEquals(pred, "good") - } - - @Test - fun testRatingsUpdate() { - val model = MatrixFactorization( - sortedMapOf( - Pair("good", SongFeature(1.0, Array(5) { Random.nextDouble(2.0, 5.0) }, 0.0)), - Pair("bad", SongFeature(1.0, Array(5) { 1.0 }, 0.0)), // nobody likes this song - ) - ) - - model.updateRatings( - sortedMapOf( - Pair("good", 0.0), - Pair("bad", 1.0) - ) - ) - - Assert.assertNotEquals(model.songFeatures["bad"], Array(5) { 0.0 }) - } - - @Test - fun testSyncModels() { - val pubModel = PublicMatrixFactorization( - sortedMapOf( - Pair("good", SongFeature(1.0, Array(5) { Random.nextDouble(2.0, 5.0) }, 0.0)), - Pair("bad", SongFeature(1.0, Array(5) { 1.0 }, 0.0)), // nobody likes this song - ) - ) - val models = Array(3) { MatrixFactorization(pubModel) } - - val updatedModel = MatrixFactorization(pubModel) - updatedModel.updateRatings(sortedMapOf(Pair("new", 5.0))) - - // gossip new song to peers - for (model in models) { - model.merge(PublicMatrixFactorization(updatedModel.songFeatures)) - Assert.assertTrue(model.songFeatures.keys.contains("new")) - } - } - - private fun pairwiseDifference(models: Array): Double { - var diff = 0.0 - var total = 0.0 - for (m1 in models) { - for (m2 in models) { - if (m1 === m2) continue - for (k in m1.songFeatures.keys) { - diff += abs((m1.songFeatures[k]!!.feature - m2.songFeatures[k]!!.feature).sum()) - total += (m1.songFeatures[k]!!.feature + m2.songFeatures[k]!!.feature).sum() / 2 - } - } - } - return diff / total - } - - @Test - fun testGossipConvergence() { - val models = arrayOf( - // a fans - MatrixFactorization(sortedMapOf(Pair("a", 25.0), Pair("aa", 30.0), Pair("aaa", 27.0), Pair("b", 1.0), Pair("cc", 1.0))), - MatrixFactorization(sortedMapOf(Pair("a", 28.0), Pair("aa", 29.0), Pair("dd", 1.0), Pair("bbb", 1.0))), - MatrixFactorization(sortedMapOf(Pair("aaa", 21.0), Pair("aa", 23.0), Pair("c", 1.0), Pair("d", 1.0))), - // b fans - MatrixFactorization(sortedMapOf(Pair("b", 25.0), Pair("bb", 29.0), Pair("bbb", 24.0), Pair("a", 1.0))), - MatrixFactorization(sortedMapOf(Pair("bb", 26.0), Pair("b", 28.0), Pair("d", 3.0), Pair("dd", 1.0))), - MatrixFactorization(sortedMapOf(Pair("bbb", 27.0), Pair("b", 27.0), Pair("c", 2.0), Pair("aa", 1.0))), - MatrixFactorization(sortedMapOf(Pair("bb", 28.0), Pair("bbb", 26.0), Pair("ccc", 1.0), Pair("aaa", 1.0))), - // c fans - MatrixFactorization(sortedMapOf(Pair("c", 21.0), Pair("cc", 26.0), Pair("ccc", 27.0), Pair("bbb", 1.0))), - MatrixFactorization(sortedMapOf(Pair("cc", 22.0), Pair("c", 25.0), Pair("bb", 1.0), Pair("d", 1.0))), - MatrixFactorization(sortedMapOf(Pair("ccc", 23.0), Pair("c", 24.0), Pair("b", 2.0), Pair("aa", 1.0), Pair("aaa", 1.0))), - // d fan - MatrixFactorization(sortedMapOf(Pair("d", 30.0), Pair("dd", 30.0), Pair("aaa", 1.0), Pair("bb", 1.0), Pair("c", 1.0))), - ) - - // gossip models iteratively until (hopefully) convergence - for (round in 1..10) { - for (m1 in models) { - for (m2 in models) { - if (m1 === m2) continue - m2.merge(PublicMatrixFactorization(m1.songFeatures.toSortedMap())) - } - } - } - - // models never converge completely because update() is called after merging - Assert.assertTrue(pairwiseDifference(models) < 0.1) - } - - @Test - fun testRecommendations() { - // high play counts within group, low play counts outside - val models = arrayOf( - // a fans - MatrixFactorization(sortedMapOf(Pair("a", 25.0), Pair("aa", 30.0), Pair("aaa", 27.0), Pair("b", 1.0), Pair("cc", 1.0))), - MatrixFactorization(sortedMapOf(Pair("a", 28.0), Pair("aa", 29.0), Pair("dd", 1.0), Pair("bbb", 1.0))), - MatrixFactorization(sortedMapOf(Pair("aaa", 21.0), Pair("aa", 23.0), Pair("c", 1.0), Pair("d", 1.0))), - // b fans - MatrixFactorization(sortedMapOf(Pair("b", 25.0), Pair("bb", 29.0), Pair("bbb", 24.0), Pair("a", 1.0))), - MatrixFactorization(sortedMapOf(Pair("bb", 26.0), Pair("b", 28.0), Pair("d", 3.0), Pair("dd", 1.0))), - MatrixFactorization(sortedMapOf(Pair("bbb", 27.0), Pair("b", 27.0), Pair("c", 2.0), Pair("aa", 1.0))), - MatrixFactorization(sortedMapOf(Pair("bb", 28.0), Pair("bbb", 26.0), Pair("ccc", 1.0), Pair("aaa", 1.0))), - // c fans - MatrixFactorization(sortedMapOf(Pair("c", 21.0), Pair("cc", 26.0), Pair("ccc", 27.0), Pair("bbb", 1.0))), - MatrixFactorization(sortedMapOf(Pair("cc", 22.0), Pair("c", 25.0), Pair("bb", 1.0), Pair("d", 1.0))), - MatrixFactorization(sortedMapOf(Pair("ccc", 23.0), Pair("c", 24.0), Pair("b", 2.0), Pair("aa", 1.0), Pair("aaa", 1.0))), - // d fan - MatrixFactorization(sortedMapOf(Pair("d", 30.0), Pair("dd", 30.0), Pair("aaa", 1.0), Pair("bb", 1.0), Pair("c", 1.0))), - ) - - // gossip models iteratively until convergence - for (round in 1..30) { - for (m1 in models) { - for (m2 in models) { - if (m1 === m2) continue - m2.merge(PublicMatrixFactorization(m1.songFeatures.toSortedMap())) - } - } - } - - // often there's still 1 error, but 87% is good enough for this test set - val numCorrect = arrayOf( - "aaa" == models[1].predict(), - "a" == models[2].predict(), - "bbb" == models[4].predict(), - "bb" == models[5].predict(), - "b" == models[6].predict(), - "ccc" == models[8].predict(), - "cc" == models[9].predict(), - "b" == models[7].predict() - ).map { if (it) 1 else 0 }.sum() - - Assert.assertTrue(numCorrect >= 5) - } -} diff --git a/gossipML/src/test/java/gossipML/models/FeatureBasedTest.kt b/gossipML/src/test/java/gossipML/models/FeatureBasedTest.kt deleted file mode 100644 index 6ed75ab67..000000000 --- a/gossipML/src/test/java/gossipML/models/FeatureBasedTest.kt +++ /dev/null @@ -1,354 +0,0 @@ -package nl.tudelft.trustchain.gossipML.models - -import nl.tudelft.trustchain.gossipML.db.scale2label -import nl.tudelft.trustchain.gossipML.db.stats -import nl.tudelft.trustchain.gossipML.models.feature_based.Adaline -import nl.tudelft.trustchain.gossipML.models.feature_based.Pegasos -import org.hamcrest.CoreMatchers.instanceOf -import org.json.JSONObject -import org.junit.Assert -import org.junit.Test -import java.io.File -import kotlin.math.absoluteValue -import kotlin.math.log10 -import kotlin.math.pow -import kotlin.math.sqrt -import kotlin.random.Random - -class FeatureBasedTest { - private val amountFeatures = 10 - private var features: Array> = Array(amountFeatures) { _ -> Array(amountFeatures) { Random.nextDouble(0.0, 5.0) } } - private var labels = Array(amountFeatures) { Random.nextInt(0, 2).toDouble() } - private var highVarianceFeatureLabels = arrayOf(3, 62, 162, 223, 130, 140) - - @Test - fun testPegasos() { - val model = Pegasos(0.4, amountFeatures, 5) - - model.update(features, labels) - - model.predict(Array(amountFeatures) { Random.nextDouble(0.0, 5.0) }) - - val mergeModelEq = Pegasos(0.4, amountFeatures, 5) - model.merge(mergeModelEq) - Assert.assertThat(model, instanceOf(Pegasos::class.java)) - - val mergeModelDiff = Adaline(0.1, amountFeatures) - model.merge(mergeModelDiff) - Assert.assertThat(model, instanceOf(Pegasos::class.java)) - } - - @Test - fun testAdaline() { - val model = Adaline(0.1, amountFeatures) - - model.update(features, labels) - - model.predict(Array(amountFeatures) { Random.nextDouble(0.0, 5.0) }) - - val mergeModelEq = Adaline(0.1, amountFeatures) - model.merge(mergeModelEq) - Assert.assertThat(model, instanceOf(Adaline::class.java)) - - val mergeModelDiff = Pegasos(0.4, amountFeatures, 5) - model.merge(mergeModelDiff) - Assert.assertThat(model, instanceOf(Adaline::class.java)) - } - - @Test - fun testAdalinePredictions() { - val model = Adaline(1.0, 2) - val biasedFeatures = arrayOf(arrayOf(100.0), arrayOf(-1.0)) - val biasedLabels = arrayOf(50.0, 0.0) - - for (i in 0..100) { - model.update(biasedFeatures, biasedLabels) - } - - val biasedTestSamples = arrayOf(arrayOf(100.0), arrayOf(-1.0)) - - val res = model.predict(biasedTestSamples) - Assert.assertTrue(res[0] >= res[1]) - } - - private fun pairwiseDifference(models: Array): Double { - var diff = 0.0 - var total = 0.0 - for (m1 in models) { - for (m2 in models) { - if (m1 === m2) continue - for (k in m1.weights.indices) { - diff += (m1.weights[k] - m2.weights[k]).absoluteValue - total += (m1.weights[k] + m2.weights[k]) / 2 - } - } - } - return diff / total - } - - @Test - fun testGossipConvergence() { - val model1 = Adaline(1.0, 4) - val model11 = Adaline(1.0, 4) - - val model2 = Pegasos(0.1, 4, 100) - val model22 = Pegasos(0.1, 4, 100) - - for (i in 0..100) { - model1.merge(model11) - model2.merge(model22) - - model11.merge(model1) - model22.merge(model2) - } - - val models1 = arrayOf(model1 as OnlineModel, model11 as OnlineModel) - val models2 = arrayOf(model2 as OnlineModel, model22 as OnlineModel) - - Assert.assertTrue(pairwiseDifference(models1) < 0.1) - Assert.assertTrue(pairwiseDifference(models2) < 0.1) - } - - @Test - fun testToyRecommendations() { - val features = arrayOf( - arrayOf(-1.0, 97.0, -1.0, -1.0), - arrayOf(-1.0, 101.0, -1.0, -1.0), - arrayOf(-1.0, -1.0, -1.0, -1.0), - arrayOf(-1.0, 95.0, -1.0, -1.0), - arrayOf(-1.0, 97.0, -1.0, -1.0), - arrayOf(-1.0, 101.0, -1.0, -1.0), - arrayOf(-1.0, 1.0, -1.0, -1.0), - arrayOf(-1.0, 95.0, -1.0, -1.0), - arrayOf(-1.0, 103.0, -1.0, -1.0), - arrayOf(-1.0, 101.0, -1.0, -1.0), - arrayOf(-1.0, 0.0, -1.0, -1.0), - arrayOf(-1.0, 96.0, -1.0, -1.0), - ) - val labels = arrayOf(50.0, 44.0, 0.0, 49.0, 50.0, 44.0, 0.0, 49.0, 50.0, 44.0, 0.0, 49.0) - val model = Pegasos(0.1, 4, 100) - model.update(features, labels) - - var correctPredictions = 0 - - for (i in 0..10) { - val test = arrayOf( - arrayOf(-1.0, Random.nextDouble(95.0, 100.0), -1.0, -1.0), - arrayOf(-1.0, Random.nextDouble(95.0, 100.0), -1.0, -1.0), - arrayOf(-1.0, Random.nextDouble(-1.0, 1.0), -1.0, -1.0), - arrayOf(-1.0, Random.nextDouble(95.0, 100.0), -1.0, -1.0), - ).map { model.predict(it) } - - if (test[0] >= test[2] && test[1] >= test[2] && test[3] >= test[2]) { - correctPredictions += 1 - } - } - - Assert.assertTrue(correctPredictions >= 5) - } - - /** - * Use this to get statistics for local training data - */ - fun calculateStats(classRanges: Array, features: Array>) { - val means = Array>(5) { emptyArray() } - val variance = Array>(5) { emptyArray() } - for (i in classRanges.indices) { - var counter = 0 - if (i > 0) { - means[i - 1] = Array(225) { 0.0 } - variance[i - 1] = Array(225) { 0.0 } - val subf = features.copyOfRange(classRanges[i - 1], classRanges[i]) - for (fs in subf) { - means[i - 1] += fs - counter += 1 - } - means[i - 1] = means[i - 1].map { p -> p / counter }.toTypedArray() - - for (fs in subf) { - variance[i - 1] += (fs - means[i - 1]).map { p -> p.pow(2) }.toTypedArray() - } - - variance[i - 1] = variance[i - 1].map { p -> sqrt(p / counter) }.toTypedArray() - } - } - } - - @Test - fun testRecommendations() { - val numFeat = highVarianceFeatureLabels.size - val classRanges = Array(6) { -1 } - val mutFeatures: MutableList> = mutableListOf() - var c = 0 - var f = 0 - File("superapp-essentia/test/res").listFiles()!!.forEach { classDir -> - classRanges[c] = f - classDir.listFiles()!!.forEach { jsonFile -> - mutFeatures.add(featuresFromJson(jsonFile)) - f += 1 - } - c += 1 - } - - classRanges[c] = f - - val features = mutFeatures.toTypedArray() - - val usersPerGroup = 3 - val models = Array(usersPerGroup * 5) { Pegasos(0.1, numFeat, 100) } - val globalModel = Pegasos(0.1, numFeat, 100) - - val plays: MutableList> = mutableListOf() - for ((i, _) in models.withIndex()) { - val myClass = i / usersPerGroup - // 10 random songs in-class - val likedIdxs = Array(10) { (classRanges[myClass]..classRanges[myClass + 1]).random() } - val likedPlays = Array(10) { (20..50).random().toDouble() } - - // 7 random songs out-of-class - val mehIdxs = Array(7) { - var idx = (0..features.size).random() - while (classRanges[myClass] <= idx && idx < classRanges[myClass + 1]) idx = (0..features.size).random() - idx - } - val mehPlays = Array(7) { (0..1).random().toDouble() } - - val playMap = arrayOf(*likedIdxs, *mehIdxs).zip(arrayOf(*likedPlays, *mehPlays)).toMap() - val play = features.mapIndexed { fid, _ -> playMap[fid] ?: -1.0 }.toTypedArray() - plays.add(play) - } - - // gossip train for global model - repeat(100) { - for ((i, mi) in models.withIndex()) { - for ((j, mj) in models.withIndex()) { - val flag = Random.nextBoolean() - if (flag) { - mj.merge(globalModel) - globalModel.update(features, plays[j]) - } else { - mi.merge(globalModel) - globalModel.update(features, plays[i]) - } - - // we bias our model by re-training it every time on local features - for ((k, mk) in models.withIndex()) { - mk.update(features, plays[k]) - mk.update(features, plays[k]) - } - } - } - } - - val correct = models.mapIndexed { i, m -> - val myClass = i / 5 - val preds = m.predict(features) - val predIdx = preds.indexOfFirst { it == preds.maxOrNull()!! } - if (classRanges[myClass] <= predIdx && predIdx < classRanges[myClass + 1]) 1 else 0 - } - val numCorrect = correct.sum() - Assert.assertTrue("num correct: $numCorrect", numCorrect >= 0) - } - - fun featuresFromJson(jsonFile: File): Array { - val year = -1.0 - val genre = -1.0 - - val essentiaFeatures = JSONObject(jsonFile.bufferedReader().use { it.readText() }) - - val lowlevel = essentiaFeatures.getJSONObject("lowlevel") - - val dynamic_complexity = lowlevel.getDouble("dynamic_complexity") - val average_loudness = lowlevel.getDouble("average_loudness") - - var integrated_loudness = -1.0 - var loudness_range = -1.0 - var momentary = Array(5) { -1.0 } - var short_term = Array(5) { -1.0 } - try { - integrated_loudness = lowlevel.getJSONObject("loudness_ebu128").getDouble("integrated") - loudness_range = lowlevel.getJSONObject("loudness_ebu128").getDouble("loudness_range") - momentary = stats(lowlevel.getJSONObject("loudness_ebu128").getJSONObject("momentary")) - short_term = stats(lowlevel.getJSONObject("loudness_ebu128").getJSONObject("short_term")) - } catch (e: Exception) { - } - - val keys = arrayOf( - "barkbands_crest", "barkbands_flatness_db", "barkbands_kurtosis", "barkbands_skewness", "barkbands_spread", - "dissonance", "erbbands_crest", "erbbands_flatness_db", "erbbands_kurtosis", "erbbands_skewness", - "erbbands_spread", "hfc", "melbands_crest", "melbands_flatness_db", "melbands_kurtosis", "melbands_skewness", - "melbands_spread", "pitch_salience", "spectral_centroid", "spectral_complexity", "spectral_decrease", - "spectral_energy", "spectral_energyband_high", "spectral_energyband_low", "spectral_energyband_middle_high", - "spectral_energyband_middle_low", "spectral_entropy", "spectral_flux", "spectral_kurtosis", "spectral_rms", - "spectral_rolloff", "spectral_skewness", "spectral_spread", "spectral_strongpeak", "zerocrossingrate" - ) - val lowlevelStats = Array(keys.size) { Array(5) { -1.0 } } - for ((i, key) in keys.withIndex()) { - lowlevelStats[i] = stats(lowlevel.getJSONObject(key)) - } - - val tonal = essentiaFeatures.getJSONObject("tonal") - - val key = scale2label(tonal.getString("chords_key"), tonal.getString("chords_scale")) - - var key_edma: Array - var key_krumhansl = arrayOf(0.0, 0.0) - var key_temperley = arrayOf(0.0, 0.0) - try { - key_edma = scale2label( - tonal.getJSONObject("key_edma").getString("key"), - tonal.getJSONObject("key_edma").getString("scale") - ) - key_krumhansl = scale2label( - tonal.getJSONObject("key_krumhansl").getString("key"), - tonal.getJSONObject("key_krumhansl").getString("scale") - ) - key_temperley = scale2label( - tonal.getJSONObject("key_temperley").getString("key"), - tonal.getJSONObject("key_temperley").getString("scale") - ) - } catch (e: Exception) { - key_edma = scale2label( - tonal.getString("key_key"), - tonal.getString("key_scale") - ) - } - - val chords_strength = stats(tonal.getJSONObject("chords_strength")) - val hpcp_crest = try { stats(tonal.getJSONObject("hpcp_crest")) } catch (e: Exception) { Array(5) { -1.0 } } - val hpcp_entropy = stats(tonal.getJSONObject("hpcp_entropy")) - val tuning_nontempered_energy_ratio = tonal.getDouble("tuning_nontempered_energy_ratio") - val tuning_diatonic_strength = tonal.getDouble("tuning_diatonic_strength") - - val rhythm: JSONObject = essentiaFeatures.getJSONObject("rhythm") - val bpm = rhythm.getDouble("bpm") - val danceability = rhythm.getDouble("danceability") - val beats_loudness = stats(rhythm.getJSONObject("beats_loudness")) - - val metadata: JSONObject = essentiaFeatures.getJSONObject("metadata") - val length = metadata.getJSONObject("audio_properties").getDouble("length") - val replay_gain = metadata.getJSONObject("audio_properties").getDouble("replay_gain") - - val features = arrayOf( - arrayOf( - year, genre, length, replay_gain, dynamic_complexity, average_loudness, - integrated_loudness, loudness_range, bpm, danceability, tuning_nontempered_energy_ratio, - tuning_diatonic_strength - ), - momentary, short_term, lowlevelStats.flatten().toTypedArray(), key, key_edma, key_krumhansl, key_temperley, - chords_strength, hpcp_crest, hpcp_entropy, beats_loudness - ).flatten().toTypedArray() - - // log transform on features - for ((i, feat) in features.withIndex()) { - features[i] = if (feat < 0.0) -log10(-feat) else if (feat > 0.0) log10(feat) else 0.0 - } - - var finalFeatures = Array(highVarianceFeatureLabels.size) { 0.0 } - for ((idx, fidx) in this.highVarianceFeatureLabels.withIndex()) { - finalFeatures[idx] = features[fidx] - } - - return finalFeatures - } -} diff --git a/gossipML/superapp-essentia b/gossipML/superapp-essentia deleted file mode 160000 index f56b302db..000000000 --- a/gossipML/superapp-essentia +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f56b302db5d4a56a1d9388c27a3c8dcd81727e4f diff --git a/musicdao/build.gradle b/musicdao/build.gradle index 6c36d9908..cffc08fef 100644 --- a/musicdao/build.gradle +++ b/musicdao/build.gradle @@ -77,7 +77,6 @@ android { } dependencies { - implementation project(':gossipML') implementation project(':common') api(project(':ipv8-android')) { diff --git a/peerchat/build.gradle b/peerchat/build.gradle deleted file mode 100644 index 3a80e5c7f..000000000 --- a/peerchat/build.gradle +++ /dev/null @@ -1,102 +0,0 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' -apply plugin: 'com.squareup.sqldelight' -apply plugin: 'org.jlleitschuh.gradle.ktlint' - -ktlint { - version = "$ktlint_version" - android = true - outputToConsole = true - ignoreFailures = false -} - -sqldelight { - Database { - packageName = "nl.tudelft.peerchat.sqldelight" - sourceFolders = ["sqldelight"] - schemaOutputDirectory = file("src/main/sqldelight/databases") - } -} - -android { - compileSdkVersion 33 - - defaultConfig { - minSdkVersion 22 - targetSdkVersion 33 - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles 'consumer-rules.pro' - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - kotlinOptions { - jvmTarget = "1.8" - } - - buildFeatures { - viewBinding = true - } - namespace 'nl.tudelft.trustchain.peerchat' -} - -dependencies { - implementation project(':common') - - // AndroidX - implementation 'androidx.appcompat:appcompat:1.1.0' - implementation 'androidx.core:core-ktx:1.9.0' - implementation 'androidx.constraintlayout:constraintlayout:1.1.3' - implementation "androidx.recyclerview:recyclerview:1.1.0" - implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" - implementation "androidx.navigation:navigation-ui-ktx:$nav_version" - implementation "androidx.fragment:fragment-ktx:$fragment_version" - implementation "androidx.preference:preference:1.1.0" - implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version" - implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" - implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" - - // Material - implementation 'com.google.android.material:material:1.1.0' - implementation 'com.getbase:floatingactionbutton:1.10.1' - - // Kotlin - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" - implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - - // Logging - implementation 'io.github.microutils:kotlin-logging:1.7.7' - implementation 'com.github.tony19:logback-android:2.0.0' - - implementation 'com.github.MattSkala:recyclerview-itemadapter:0.4' - implementation 'com.github.bumptech.glide:glide:4.11.0' - implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' - annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' - - // Testing - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' -} - -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { - kotlinOptions.freeCompilerArgs += [ - "-opt-in=kotlin.RequiresOptIn" - ] -} diff --git a/peerchat/doc/add_contact.png b/peerchat/doc/add_contact.png deleted file mode 100644 index 4ba0ffa26..000000000 Binary files a/peerchat/doc/add_contact.png and /dev/null differ diff --git a/peerchat/doc/add_nearby.png b/peerchat/doc/add_nearby.png deleted file mode 100644 index c318f72d3..000000000 Binary files a/peerchat/doc/add_nearby.png and /dev/null differ diff --git a/peerchat/doc/add_remote.png b/peerchat/doc/add_remote.png deleted file mode 100644 index fb143ebb2..000000000 Binary files a/peerchat/doc/add_remote.png and /dev/null differ diff --git a/peerchat/doc/contacts.png b/peerchat/doc/contacts.png deleted file mode 100644 index d3fc15e00..000000000 Binary files a/peerchat/doc/contacts.png and /dev/null differ diff --git a/peerchat/doc/conversation.png b/peerchat/doc/conversation.png deleted file mode 100644 index dddcc8a7d..000000000 Binary files a/peerchat/doc/conversation.png and /dev/null differ diff --git a/peerchat/src/main/AndroidManifest.xml b/peerchat/src/main/AndroidManifest.xml deleted file mode 100644 index 628740cbf..000000000 --- a/peerchat/src/main/AndroidManifest.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/PeerChatActivity.kt b/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/PeerChatActivity.kt deleted file mode 100644 index a9ab5dcc9..000000000 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/PeerChatActivity.kt +++ /dev/null @@ -1,8 +0,0 @@ -package nl.tudelft.trustchain.peerchat - -import nl.tudelft.trustchain.common.BaseActivity - -class PeerChatActivity : BaseActivity() { - override val navigationGraph = R.navigation.nav_graph_peerchat - override val bottomNavigationMenu = R.menu.peerchat_navigation_menu -} diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/addcontact/AddContactFragment.kt b/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/addcontact/AddContactFragment.kt deleted file mode 100644 index 2dd9ef48b..000000000 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/addcontact/AddContactFragment.kt +++ /dev/null @@ -1,60 +0,0 @@ -package nl.tudelft.trustchain.peerchat.ui.addcontact - -import android.os.Bundle -import android.view.View -import android.widget.Toast -import androidx.lifecycle.lifecycleScope -import androidx.navigation.fragment.findNavController -import kotlinx.android.synthetic.main.fragment_add_contact.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import nl.tudelft.ipv8.keyvault.defaultCryptoProvider -import nl.tudelft.ipv8.util.hexToBytes -import nl.tudelft.ipv8.util.toHex -import nl.tudelft.trustchain.common.contacts.ContactStore -import nl.tudelft.trustchain.common.ui.BaseFragment -import nl.tudelft.trustchain.common.util.QRCodeUtils -import nl.tudelft.trustchain.common.util.viewBinding -import nl.tudelft.trustchain.peerchat.R -import nl.tudelft.trustchain.peerchat.databinding.FragmentAddContactBinding - -class AddContactFragment : BaseFragment(R.layout.fragment_add_contact) { - private val binding by viewBinding(FragmentAddContactBinding::bind) - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val publicKeyBin = requireArguments().getString(ARG_PUBLIC_KEY)!! - binding.txtPublicKey.text = publicKeyBin - lifecycleScope.launch { - val bitmap = withContext(Dispatchers.Default) { - QRCodeUtils(requireContext()).createQR(publicKeyBin) - } - binding.qr.setImageBitmap(bitmap) - } - - binding.btnSave.setOnClickListener { - val name = edtName.text.toString() - if (name.isNotEmpty()) { - val publicKey = defaultCryptoProvider.keyFromPublicBin(publicKeyBin.hexToBytes()) - - val myPublicKey = getIpv8().myPeer.publicKey.keyToBin().toHex() - if (publicKeyBin != myPublicKey) { - ContactStore.getInstance(requireContext()) - .addContact(publicKey, name) - findNavController().popBackStack(R.id.contactsFragment, false) - } else { - Toast.makeText(requireContext(), "You cannot add yourself", Toast.LENGTH_SHORT) - .show() - } - } else { - Toast.makeText(requireContext(), "Name is empty", Toast.LENGTH_SHORT).show() - } - } - } - - companion object { - const val ARG_PUBLIC_KEY = "public_key" - } -} diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/addcontact/AddNearbyFragment.kt b/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/addcontact/AddNearbyFragment.kt deleted file mode 100644 index bb9c134d5..000000000 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/addcontact/AddNearbyFragment.kt +++ /dev/null @@ -1,112 +0,0 @@ -package nl.tudelft.trustchain.peerchat.ui.addcontact - -import android.content.Intent -import android.os.Bundle -import android.view.View -import android.widget.Toast -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.Observer -import androidx.lifecycle.lifecycleScope -import androidx.navigation.fragment.findNavController -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import nl.tudelft.ipv8.keyvault.defaultCryptoProvider -import nl.tudelft.ipv8.util.hexToBytes -import nl.tudelft.ipv8.util.toHex -import nl.tudelft.trustchain.common.ui.BaseFragment -import nl.tudelft.trustchain.common.util.QRCodeUtils -import nl.tudelft.trustchain.common.util.viewBinding -import nl.tudelft.trustchain.peerchat.R -import nl.tudelft.trustchain.peerchat.databinding.FragmentAddNearbyBinding - -class AddNearbyFragment : BaseFragment(R.layout.fragment_add_nearby) { - private val binding by viewBinding(FragmentAddNearbyBinding::bind) - - private var publicKeyBin = MutableLiveData() - - private val qrCodeUtils by lazy { - QRCodeUtils(requireContext()) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - publicKeyBin.value = savedInstanceState?.getString(KEY_PUBLIC_KEY) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val myPublicKey = getIpv8().myPeer.publicKey.keyToBin().toHex() - binding.txtMyPublicKey.text = myPublicKey - lifecycleScope.launch { - val bitmap = withContext(Dispatchers.Default) { - qrCodeUtils.createQR(myPublicKey) - } - binding.qr.setImageBitmap(bitmap) - } - - binding.contactQr.setOnClickListener { - qrCodeUtils.startQRScanner(this) - } - - binding.btnContinue.setOnClickListener { - val args = Bundle() - - val publicKeyBin = publicKeyBin.value - if (publicKeyBin != null) { - try { - defaultCryptoProvider.keyFromPublicBin(publicKeyBin.hexToBytes()) - args.putString(AddContactFragment.ARG_PUBLIC_KEY, publicKeyBin) - findNavController().navigate( - R.id.action_addNearbyFragment_to_addContactFragment, - args - ) - } catch (e: Exception) { - e.printStackTrace() - Toast.makeText(requireContext(), "Invalid public key", Toast.LENGTH_LONG).show() - } - } else { - Toast.makeText(requireContext(), "Scan the QR code", Toast.LENGTH_LONG).show() - } - } - - publicKeyBin.observe( - viewLifecycleOwner, - Observer { publicKeyBin -> - binding.txtContactPublicKey.text = publicKeyBin - - lifecycleScope.launch { - val bitmap = if (publicKeyBin != null) withContext(Dispatchers.Default) { - qrCodeUtils.createQR(publicKeyBin) - } else null - binding.contactQr.setImageBitmap(bitmap) - } - } - ) - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - val publicKeyBin = qrCodeUtils.parseActivityResult(requestCode, resultCode, data) - if (publicKeyBin != null) { - try { - defaultCryptoProvider.keyFromPublicBin(publicKeyBin.hexToBytes()) - this.publicKeyBin.value = publicKeyBin - } catch (e: Exception) { - e.printStackTrace() - Toast.makeText(requireContext(), "Invalid public key", Toast.LENGTH_LONG).show() - } - } else { - Toast.makeText(requireContext(), "Scan failed", Toast.LENGTH_LONG).show() - } - } - - override fun onSaveInstanceState(outState: Bundle) { - super.onSaveInstanceState(outState) - outState.putString(KEY_PUBLIC_KEY, publicKeyBin.value) - } - - companion object { - private const val KEY_PUBLIC_KEY = "public_key" - } -} diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/addcontact/AddRemoteFragment.kt b/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/addcontact/AddRemoteFragment.kt deleted file mode 100644 index 08b381650..000000000 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/addcontact/AddRemoteFragment.kt +++ /dev/null @@ -1,53 +0,0 @@ -package nl.tudelft.trustchain.peerchat.ui.addcontact - -import android.content.ClipData -import android.content.ClipboardManager -import android.os.Bundle -import android.view.View -import android.widget.Toast -import androidx.core.content.ContextCompat -import androidx.navigation.fragment.findNavController -import kotlinx.android.synthetic.main.fragment_add_remote.* -import nl.tudelft.ipv8.keyvault.defaultCryptoProvider -import nl.tudelft.ipv8.util.hexToBytes -import nl.tudelft.ipv8.util.toHex -import nl.tudelft.trustchain.common.ui.BaseFragment -import nl.tudelft.trustchain.common.util.viewBinding -import nl.tudelft.trustchain.peerchat.R -import nl.tudelft.trustchain.peerchat.databinding.FragmentAddRemoteBinding - -class AddRemoteFragment : BaseFragment(R.layout.fragment_add_remote) { - private val binding by viewBinding(FragmentAddRemoteBinding::bind) - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val myPublicKey = getIpv8().myPeer.publicKey.keyToBin().toHex() - binding.txtMyPublicKey.text = myPublicKey - - btnCopy.setOnClickListener { - val clipboard = - ContextCompat.getSystemService(requireContext(), ClipboardManager::class.java) - val clip = ClipData.newPlainText("Public Key", myPublicKey) - clipboard?.setPrimaryClip(clip) - Toast.makeText(requireContext(), "Copied to clipboard", Toast.LENGTH_SHORT).show() - } - - binding.btnContinue.setOnClickListener { - val args = Bundle() - val publicKeyBin = edtContactPublicKey.text.toString() - - try { - defaultCryptoProvider.keyFromPublicBin(publicKeyBin.hexToBytes()) - args.putString(AddContactFragment.ARG_PUBLIC_KEY, publicKeyBin) - findNavController().navigate( - R.id.action_addRemoteFragment_to_addContactFragment, - args - ) - } catch (e: Exception) { - e.printStackTrace() - Toast.makeText(requireContext(), "Invalid public key", Toast.LENGTH_LONG).show() - } - } - } -} diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/contacts/AvatarView.kt b/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/contacts/AvatarView.kt deleted file mode 100644 index db4d1f098..000000000 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/contacts/AvatarView.kt +++ /dev/null @@ -1,31 +0,0 @@ -package nl.tudelft.trustchain.peerchat.ui.contacts - -import android.content.Context -import android.graphics.PorterDuff -import android.util.AttributeSet -import android.view.LayoutInflater -import android.widget.FrameLayout -import kotlinx.android.synthetic.main.view_avatar.view.* -import nl.tudelft.trustchain.common.util.getColorByHash -import nl.tudelft.trustchain.peerchat.R - -class AvatarView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : - FrameLayout(context, attrs, defStyleAttr) { - - init { - LayoutInflater.from(context).inflate(R.layout.view_avatar, this, true) - } - - fun setUser(id: String, name: String) { - val initials = name.split(" ") - .mapNotNull { it.firstOrNull() } - .take(2) - .joinToString("") - txtInitials.text = initials - imgAvatar.setColorFilter(getColorByHash(context, id), PorterDuff.Mode.MULTIPLY) - } -} diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/contacts/ContactItem.kt b/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/contacts/ContactItem.kt deleted file mode 100644 index d9985c599..000000000 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/contacts/ContactItem.kt +++ /dev/null @@ -1,16 +0,0 @@ -package nl.tudelft.trustchain.peerchat.ui.contacts - -import com.mattskala.itemadapter.Item -import nl.tudelft.trustchain.common.contacts.Contact -import nl.tudelft.trustchain.peerchat.entity.ChatMessage - -data class ContactItem( - val contact: Contact, - val lastMessage: ChatMessage?, - val isOnline: Boolean, - val isBluetooth: Boolean -) : Item() { - override fun areItemsTheSame(other: Item): Boolean { - return other is ContactItem && contact.mid == other.contact.mid - } -} diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/contacts/ContactItemRenderer.kt b/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/contacts/ContactItemRenderer.kt deleted file mode 100644 index 8fb76e3e7..000000000 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/contacts/ContactItemRenderer.kt +++ /dev/null @@ -1,46 +0,0 @@ -package nl.tudelft.trustchain.peerchat.ui.contacts - -import android.graphics.Typeface -import android.view.View -import androidx.core.view.isVisible -import com.mattskala.itemadapter.ItemLayoutRenderer -import kotlinx.android.synthetic.main.item_contact.view.* -import nl.tudelft.trustchain.common.contacts.Contact -import nl.tudelft.trustchain.peerchat.R -import java.text.SimpleDateFormat - -class ContactItemRenderer( - private val onItemClick: (Contact) -> Unit, - private val onItemLongClick: (Contact) -> Unit -) : ItemLayoutRenderer( - ContactItem::class.java -) { - private val dateFormat = SimpleDateFormat.getDateTimeInstance() - - override fun bindView(item: ContactItem, view: View) = with(view) { - txtName.text = item.contact.name - // txtName.isVisible = item.contact.name.isNotEmpty() - txtPeerId.text = item.contact.mid - val lastMessageDate = item.lastMessage?.timestamp - txtDate.isVisible = lastMessageDate != null - txtDate.text = if (lastMessageDate != null) dateFormat.format(lastMessageDate) else null - txtMessage.isVisible = item.lastMessage != null - txtMessage.text = item.lastMessage?.message - val textStyle = if (item.lastMessage?.read == false) Typeface.BOLD else Typeface.NORMAL - txtMessage.setTypeface(null, textStyle) - setOnClickListener { - onItemClick(item.contact) - } - imgWifi.isVisible = item.isOnline - imgBluetooth.isVisible = item.isBluetooth - avatar.setUser(item.contact.mid, item.contact.name) - setOnLongClickListener { - onItemLongClick(item.contact) - true - } - } - - override fun getLayoutResourceId(): Int { - return R.layout.item_contact - } -} diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/contacts/ContactsFragment.kt b/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/contacts/ContactsFragment.kt deleted file mode 100644 index 85b74af82..000000000 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/contacts/ContactsFragment.kt +++ /dev/null @@ -1,219 +0,0 @@ -package nl.tudelft.trustchain.peerchat.ui.contacts - -import android.bluetooth.BluetoothManager -import android.content.res.ColorStateList -import android.net.ConnectivityManager -import android.os.Build -import android.os.Bundle -import android.text.InputType -import android.view.View -import android.widget.EditText -import android.widget.LinearLayout -import androidx.appcompat.app.AlertDialog -import androidx.core.content.ContextCompat.getColor -import androidx.core.content.ContextCompat.getSystemService -import androidx.core.content.getSystemService -import androidx.core.view.isVisible -import androidx.lifecycle.LiveData -import androidx.lifecycle.Observer -import androidx.lifecycle.asLiveData -import androidx.lifecycle.lifecycleScope -import androidx.navigation.fragment.findNavController -import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.LinearLayoutManager -import com.mattskala.itemadapter.Item -import com.mattskala.itemadapter.ItemAdapter -import kotlinx.android.synthetic.main.fragment_contacts.* -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.isActive -import nl.tudelft.ipv8.Peer -import nl.tudelft.ipv8.util.toHex -import nl.tudelft.trustchain.common.contacts.Contact -import nl.tudelft.trustchain.common.ui.BaseFragment -import nl.tudelft.trustchain.common.util.viewBinding -import nl.tudelft.trustchain.peerchat.R -import nl.tudelft.trustchain.peerchat.community.PeerChatCommunity -import nl.tudelft.trustchain.peerchat.databinding.FragmentContactsBinding -import nl.tudelft.trustchain.peerchat.db.PeerChatStore -import nl.tudelft.trustchain.peerchat.entity.ChatMessage -import nl.tudelft.trustchain.peerchat.ui.conversation.ConversationFragment - -@OptIn(ExperimentalCoroutinesApi::class) -class ContactsFragment : BaseFragment(R.layout.fragment_contacts) { - private val binding by viewBinding(FragmentContactsBinding::bind) - - private val adapter = ItemAdapter() - - private val store by lazy { - PeerChatStore.getInstance(requireContext()) - } - - private val peers = MutableStateFlow>(listOf()) - - private val items: LiveData> by lazy { - combine(store.getContactsWithLastMessages(), peers) { contacts, peers -> - createItems(contacts, peers) - }.asLiveData() - } - - private fun getPeerChatCommunity(): PeerChatCommunity { - return getIpv8().getOverlay() - ?: throw java.lang.IllegalStateException("PeerChatCommunity is not configured") - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - adapter.registerRenderer( - ContactItemRenderer( - { - val args = Bundle() - args.putString(ConversationFragment.ARG_PUBLIC_KEY, it.publicKey.keyToBin().toHex()) - args.putString(ConversationFragment.ARG_NAME, it.name) - findNavController().navigate(R.id.action_contactsFragment_to_conversationFragment, args) - }, - { - showOptions(it) - } - ) - ) - - lifecycleScope.launchWhenResumed { - while (isActive) { - // Refresh peer status periodically - peers.value = getPeerChatCommunity().getPeers() - updateConnectivityStatus() - delay(1000L) - } - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.recyclerView.adapter = adapter - binding.recyclerView.layoutManager = LinearLayoutManager(context) - binding.recyclerView.addItemDecoration( - DividerItemDecoration( - context, - LinearLayout.VERTICAL - ) - ) - - binding.btnAddNearby.setOnClickListener { - findNavController().navigate(R.id.action_contactsFragment_to_addNearbyFragment) - fab.collapse() - } - - binding.btnAddRemote.setOnClickListener { - findNavController().navigate(R.id.action_contactsFragment_to_addRemoteFragment) - fab.collapse() - } - - items.observe( - viewLifecycleOwner, - Observer { - adapter.updateItems(it) - binding.imgEmpty.isVisible = it.isEmpty() - } - ) - } - - private fun createItems( - contacts: List>, - peers: List - ): List { - return contacts.filter { it.first.publicKey != getIpv8().myPeer.publicKey } - .map { contactWithMessage -> - val (contact, message) = contactWithMessage - val peer = peers.find { it.mid == contact.mid } - ContactItem( - contact, - message, - peer != null && !peer.address.isEmpty(), - peer?.bluetoothAddress != null - ) - } - } - - @Suppress("DEPRECATION") - private fun getConnectivityStatus(): Pair { - val cm = getSystemService(requireContext(), ConnectivityManager::class.java) - val activeNetwork = cm!!.activeNetworkInfo - return if (activeNetwork != null) { // connected to the internet - val type = if (activeNetwork.subtypeName.isNotBlank()) - activeNetwork.subtypeName else activeNetwork.typeName - Pair(type, activeNetwork.isConnected) - } else { - Pair("Internet", false) - } - } - - private fun getBluetoothStatus(): Boolean { - val bluetoothManager = requireContext().getSystemService() - ?: throw IllegalStateException("BluetoothManager not found") - val bluetoothAdapter = bluetoothManager.adapter - return bluetoothAdapter?.isEnabled ?: false - } - - private fun updateConnectivityStatus() { - val (networkType, isConnected) = getConnectivityStatus() - binding.btnInternet.text = when (networkType) { - "WIFI" -> "Wi-Fi" - else -> networkType - } - val green = getColor(requireContext(), R.color.green) - val red = getColor(requireContext(), R.color.red) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - val color = if (isConnected) green else red - binding.btnInternet.compoundDrawableTintList = ColorStateList.valueOf(color) - } - val bluetoothStatus = getBluetoothStatus() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - val color = if (bluetoothStatus) green else red - binding.btnBluetooth.compoundDrawableTintList = ColorStateList.valueOf(color) - } - } - - private fun showOptions(contact: Contact) { - val items = arrayOf("Rename", "Delete") - AlertDialog.Builder(requireContext()) - .setItems(items) { _, which -> - when (which) { - 0 -> renameContact(contact) - 1 -> deleteContact(contact) - } - } - .show() - } - - private fun renameContact(contact: Contact) { - val builder = AlertDialog.Builder(requireContext()) - builder.setTitle("Rename Contact") - - // Set up the input - val input = EditText(requireContext()) - input.inputType = InputType.TYPE_CLASS_TEXT - input.setText(contact.name) - builder.setView(input) - - // Set up the buttons - builder.setPositiveButton( - "Rename" - ) { _, _ -> - store.contactsStore.updateContact(contact.publicKey, input.text.toString()) - } - builder.setNegativeButton( - "Cancel" - ) { dialog, _ -> dialog.cancel() } - - builder.show() - } - - private fun deleteContact(contact: Contact) { - store.contactsStore.deleteContact(contact) - } -} diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/conversation/ChatMessageItem.kt b/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/conversation/ChatMessageItem.kt deleted file mode 100644 index 383cc0bb3..000000000 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/conversation/ChatMessageItem.kt +++ /dev/null @@ -1,17 +0,0 @@ -package nl.tudelft.trustchain.peerchat.ui.conversation - -import com.mattskala.itemadapter.Item -import nl.tudelft.ipv8.attestation.trustchain.TrustChainBlock -import nl.tudelft.trustchain.peerchat.entity.ChatMessage - -data class ChatMessageItem( - val chatMessage: ChatMessage, - val transaction: TrustChainBlock?, - val shouldShowAvatar: Boolean, - val shouldShowDate: Boolean, - val participantName: String -) : Item() { - override fun areItemsTheSame(other: Item): Boolean { - return other is ChatMessageItem && other.chatMessage.id == chatMessage.id - } -} diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/conversation/ChatMessageItemRenderer.kt b/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/conversation/ChatMessageItemRenderer.kt deleted file mode 100644 index 04ec1387a..000000000 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/conversation/ChatMessageItemRenderer.kt +++ /dev/null @@ -1,140 +0,0 @@ -package nl.tudelft.trustchain.peerchat.ui.conversation - -import android.content.res.ColorStateList -import android.graphics.Color -import android.text.format.DateUtils -import android.view.Gravity -import android.view.View -import android.widget.FrameLayout -import androidx.constraintlayout.widget.ConstraintSet -import androidx.core.view.isVisible -import androidx.core.view.updateLayoutParams -import com.bumptech.glide.Glide -import com.mattskala.itemadapter.ItemLayoutRenderer -import kotlinx.android.synthetic.main.item_message.view.* -import nl.tudelft.trustchain.common.eurotoken.TransactionRepository -import nl.tudelft.trustchain.common.util.getColorByHash -import nl.tudelft.trustchain.peerchat.R -import java.math.BigInteger -import java.text.SimpleDateFormat - -class ChatMessageItemRenderer : ItemLayoutRenderer( - ChatMessageItem::class.java -) { - - private val dateTimeFormat = SimpleDateFormat.getDateTimeInstance() - private val timeFormat = SimpleDateFormat.getTimeInstance() - private val constraintSet = ConstraintSet() - - override fun bindView(item: ChatMessageItem, view: View) = with(view) { - txtMessage.text = item.chatMessage.message - if (item.chatMessage.outgoing) { - txtMessage.gravity = Gravity.START - } else { - txtMessage.gravity = Gravity.END - } - item.transaction?.transaction?.let { - txtTransaction.text = - TransactionRepository.prettyAmount((item.transaction.transaction["amount"] as BigInteger).toLong()) - if (item.chatMessage.message.isEmpty()) { - txtMessage.visibility = View.GONE - } - } - val color = getColorByHash(context, item.chatMessage.sender.toString()) - val newColor = Color.argb(50, Color.red(color), Color.green(color), Color.blue(color)) - txtMessage.backgroundTintList = ColorStateList.valueOf(newColor) - txtDate.text = if (DateUtils.isToday(item.chatMessage.timestamp.time)) - timeFormat.format(item.chatMessage.timestamp) - else dateTimeFormat.format(item.chatMessage.timestamp) - txtDate.isVisible = item.shouldShowDate - avatar.setUser(item.chatMessage.sender.toString(), item.participantName) - avatar.isVisible = item.shouldShowAvatar - imgDelivered.isVisible = item.chatMessage.outgoing && item.chatMessage.ack - constraintSet.clone(constraintLayout) - constraintSet.removeFromHorizontalChain(content.id) - constraintSet.removeFromHorizontalChain(bottomContainer.id) - - val attachment = item.chatMessage.attachment - if (attachment != null && attachment.type == MessageAttachment.TYPE_IMAGE) { - val file = attachment.getFile(view.context) - if (file.exists()) { - Glide.with(view).load(file).into(image) - progress.isVisible = false - } else { - image.setImageBitmap(null) - progress.isVisible = true - } - image.isVisible = true - txtMessage.isVisible = false - txtTransaction.isVisible = false - } else if (item.chatMessage.transactionHash != null) { - progress.isVisible = - item.transaction == null // transaction not yet received via trustchain - - txtMessage.isVisible = item.chatMessage.message.isNotBlank() - - image.isVisible = false - txtTransaction.isVisible = true - } else { - image.isVisible = false - txtMessage.isVisible = true - progress.isVisible = false - txtTransaction.isVisible = false - } - - if (item.chatMessage.outgoing) { - innerContent.updateLayoutParams { - gravity = Gravity.END - } - constraintSet.connect( - content.id, - ConstraintSet.START, - ConstraintSet.PARENT_ID, - ConstraintSet.START - ) - constraintSet.connect( - content.id, - ConstraintSet.END, - ConstraintSet.PARENT_ID, - ConstraintSet.END - ) - constraintSet.connect( - bottomContainer.id, - ConstraintSet.END, - ConstraintSet.PARENT_ID, - ConstraintSet.END - ) - } else { - val avatarMargin = resources.getDimensionPixelSize(R.dimen.avatar_size) + - resources.getDimensionPixelSize(R.dimen.avatar_margin) - innerContent.updateLayoutParams { - gravity = Gravity.START - } - constraintSet.connect( - content.id, - ConstraintSet.START, - ConstraintSet.PARENT_ID, - ConstraintSet.START, - avatarMargin - ) - constraintSet.connect( - content.id, - ConstraintSet.END, - ConstraintSet.PARENT_ID, - ConstraintSet.END - ) - constraintSet.connect( - bottomContainer.id, - ConstraintSet.START, - ConstraintSet.PARENT_ID, - ConstraintSet.START, - avatarMargin - ) - } - constraintSet.applyTo(constraintLayout) - } - - override fun getLayoutResourceId(): Int { - return R.layout.item_message - } -} diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/conversation/ConversationFragment.kt b/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/conversation/ConversationFragment.kt deleted file mode 100644 index 2d01aa24f..000000000 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/conversation/ConversationFragment.kt +++ /dev/null @@ -1,218 +0,0 @@ -package nl.tudelft.trustchain.peerchat.ui.conversation - -import android.app.Activity -import android.content.Intent -import android.net.Uri -import android.os.Build -import android.os.Bundle -import android.util.Log -import android.view.View -import androidx.core.view.inputmethod.InputConnectionCompat -import androidx.lifecycle.LiveData -import androidx.lifecycle.Observer -import androidx.lifecycle.asLiveData -import androidx.navigation.fragment.findNavController -import androidx.recyclerview.widget.LinearLayoutManager -import com.mattskala.itemadapter.Item -import com.mattskala.itemadapter.ItemAdapter -import kotlinx.android.synthetic.main.fragment_conversation.* -import kotlinx.coroutines.flow.map -import nl.tudelft.ipv8.keyvault.defaultCryptoProvider -import nl.tudelft.ipv8.util.hexToBytes -import nl.tudelft.trustchain.common.eurotoken.GatewayStore -import nl.tudelft.trustchain.common.eurotoken.TransactionRepository -import nl.tudelft.trustchain.common.ui.BaseFragment -import nl.tudelft.trustchain.common.util.viewBinding -import nl.tudelft.trustchain.peerchat.R -import nl.tudelft.trustchain.peerchat.community.PeerChatCommunity -import nl.tudelft.trustchain.peerchat.databinding.FragmentConversationBinding -import nl.tudelft.trustchain.peerchat.db.PeerChatStore -import nl.tudelft.trustchain.peerchat.entity.ChatMessage -import nl.tudelft.trustchain.peerchat.util.saveFile - -class ConversationFragment : BaseFragment(R.layout.fragment_conversation) { - private val binding by viewBinding(FragmentConversationBinding::bind) - - private val adapter = ItemAdapter() - - private val items: LiveData> by lazy { - store.getAllByPublicKey(publicKey).map { messages -> - createItems(messages) - }.asLiveData() - } - - private val store by lazy { - PeerChatStore.getInstance(requireContext()) - } - - private val gatewayStore by lazy { - GatewayStore.getInstance(requireContext()) - } - - private val transactionRepository by lazy { - TransactionRepository(getTrustChainCommunity(), gatewayStore) - } - - private val publicKeyBin by lazy { - requireArguments().getString(ARG_PUBLIC_KEY)!! - } - - private val publicKey by lazy { - defaultCryptoProvider.keyFromPublicBin(publicKeyBin.hexToBytes()) - } - - private val name by lazy { - requireArguments().getString(ARG_NAME)!! - } - - private val onCommitContentListener = - InputConnectionCompat.OnCommitContentListener { inputContentInfo, flags, _ -> - val lacksPermission = ( - flags and - InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION - ) != 0 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1 && lacksPermission) { - try { - inputContentInfo.requestPermission() - } catch (e: Exception) { - return@OnCommitContentListener false // return false if failed - } - } - - val uri = inputContentInfo.contentUri - Log.d("ConversationFragment", "uri: $uri") - - sendImageFromUri(uri) - - true - } - - private fun getPeerChatCommunity(): PeerChatCommunity { - return getIpv8().getOverlay() - ?: throw java.lang.IllegalStateException("PeerChatCommunity is not configured") - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - adapter.registerRenderer(ChatMessageItemRenderer()) - - items.observe( - this, - Observer { - val oldCount = adapter.itemCount - adapter.updateItems(it) - if (adapter.itemCount != oldCount) { - // New message, scroll to the bottom - binding.recyclerView.scrollToPosition(adapter.itemCount - 1) - } - } - ) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.recyclerView.adapter = adapter - binding.recyclerView.layoutManager = LinearLayoutManager(context) - - binding.edtMessage.onCommitContentListener = onCommitContentListener - - edtMessage.setOnFocusChangeListener { _, hasFocus -> - if (hasFocus) { - fab.collapse() - } - } - - /* - btnRequestMoney.setOnClickListener { - val args = Bundle() - fab.collapse() - args.putString(TransferFragment.ARG_PUBLIC_KEY, publicKeyBin) - args.putString(TransferFragment.ARG_NAME, name) - args.putBoolean(TransferFragment.ARG_IS_REQUEST, true) - findNavController().navigate( - R.id.action_conversationFragment_to_transferFragment, - args - ) - } - */ - - btnSendMoney.setOnClickListener { - val args = Bundle() - fab.collapse() - args.putString(TransferFragment.ARG_PUBLIC_KEY, publicKeyBin) - args.putString(TransferFragment.ARG_NAME, name) - args.putBoolean(TransferFragment.ARG_IS_REQUEST, false) - findNavController().navigate( - R.id.action_conversationFragment_to_transferFragment, - args - ) - } - - btnSend.setOnClickListener { - val message = binding.edtMessage.text.toString() - if (message.isNotEmpty()) { - getPeerChatCommunity().sendMessage(message, publicKey) - binding.edtMessage.text = null - } - } - - btnAddImage.setOnClickListener { - val intent = Intent() - fab.collapse() - intent.type = "image/*" - intent.action = Intent.ACTION_GET_CONTENT - @Suppress("DEPRECATION") - startActivityForResult(Intent.createChooser(intent, "Select Picture"), PICK_IMAGE) - } - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - @Suppress("DEPRECATION") - when (requestCode) { - PICK_IMAGE -> if (resultCode == Activity.RESULT_OK && data != null) { - val uri = data.data - if (uri != null) { - sendImageFromUri(uri) - } - } - else -> super.onActivityResult(requestCode, resultCode, data) - } - } - - private fun createItems(messages: List): List { - return messages.mapIndexed { index, chatMessage -> - val shouldShowAvatar = !chatMessage.outgoing && ( - index == messages.size - 1 || - messages[index + 1].outgoing != chatMessage.outgoing - ) - /* - val shouldShowDate = (index == messages.size - 1 || - messages[index + 1].outgoing != chatMessage.outgoing || - (messages[index + 1].timestamp.time - chatMessage.timestamp.time > GROUP_TIME_LIMIT)) - */ - val shouldShowDate = true - ChatMessageItem( - chatMessage, - transactionRepository.getTransactionWithHash(chatMessage.transactionHash), - shouldShowAvatar, - shouldShowDate, - name - ) - } - } - - private fun sendImageFromUri(uri: Uri) { - val file = saveFile(requireContext(), uri) - getPeerChatCommunity().sendImage(file, publicKey) - } - - companion object { - const val ARG_PUBLIC_KEY = "public_key" - const val ARG_NAME = "name" - - private const val GROUP_TIME_LIMIT = 60 * 1000 - private const val PICK_IMAGE = 10 - } -} diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/conversation/RichEditText.kt b/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/conversation/RichEditText.kt deleted file mode 100644 index 17b7777da..000000000 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/conversation/RichEditText.kt +++ /dev/null @@ -1,30 +0,0 @@ -package nl.tudelft.trustchain.peerchat.ui.conversation - -import android.content.Context -import android.util.AttributeSet -import android.view.inputmethod.EditorInfo -import android.view.inputmethod.InputConnection -import androidx.appcompat.R -import androidx.core.view.inputmethod.EditorInfoCompat -import androidx.core.view.inputmethod.InputConnectionCompat - -class RichEditText @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = R.attr.editTextStyle -) : androidx.appcompat.widget.AppCompatEditText(context, attrs, defStyleAttr) { - var onCommitContentListener: InputConnectionCompat.OnCommitContentListener? = null - - override fun onCreateInputConnection(editorInfo: EditorInfo): InputConnection { - val ic: InputConnection = super.onCreateInputConnection(editorInfo) - EditorInfoCompat.setContentMimeTypes(editorInfo, arrayOf("image/*")) - - val callback = - InputConnectionCompat.OnCommitContentListener { inputContentInfo, flags, opts -> - onCommitContentListener?.onCommitContent(inputContentInfo, flags, opts) ?: false - } - - @Suppress("DEPRECATION") - return InputConnectionCompat.createWrapper(ic, editorInfo, callback) - } -} diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/conversation/TransferFragment.kt b/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/conversation/TransferFragment.kt deleted file mode 100644 index e06894021..000000000 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/conversation/TransferFragment.kt +++ /dev/null @@ -1,162 +0,0 @@ -package nl.tudelft.trustchain.peerchat.ui.conversation - -import android.os.Bundle -import android.text.Editable -import android.text.TextWatcher -import android.view.View -import android.widget.EditText -import android.widget.Toast -import androidx.navigation.fragment.findNavController -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import nl.tudelft.ipv8.keyvault.defaultCryptoProvider -import nl.tudelft.ipv8.util.hexToBytes -import nl.tudelft.ipv8.util.toHex -import nl.tudelft.trustchain.common.eurotoken.GatewayStore -import nl.tudelft.trustchain.common.eurotoken.TransactionRepository -import nl.tudelft.trustchain.common.ui.BaseFragment -import nl.tudelft.trustchain.common.util.viewBinding -import nl.tudelft.trustchain.peerchat.R -import nl.tudelft.trustchain.peerchat.community.PeerChatCommunity -import nl.tudelft.trustchain.peerchat.databinding.TransferFragmentBinding - -class TransferFragment : BaseFragment(R.layout.transfer_fragment) { - - private val binding by viewBinding(TransferFragmentBinding::bind) - - private fun getPeerChatCommunity(): PeerChatCommunity { - return getIpv8().getOverlay() - ?: throw java.lang.IllegalStateException("PeerChatCommunity is not configured") - } - - private val transactionRepository by lazy { - TransactionRepository(getTrustChainCommunity(), GatewayStore.getInstance(requireContext())) - } - - private val publicKeyBin by lazy { - requireArguments().getString(ARG_PUBLIC_KEY)!! - } - - private val publicKey by lazy { - defaultCryptoProvider.keyFromPublicBin(publicKeyBin.hexToBytes()) - } - - private val ownPublicKey by lazy { - defaultCryptoProvider.keyFromPublicBin( - getPeerChatCommunity().myPeer.publicKey.keyToBin().toHex().hexToBytes() - ) - } - - private val name by lazy { - requireArguments().getString(ARG_NAME)!! - } - - private val isRequest by lazy { - requireArguments().getString(ARG_IS_REQUEST) - } - - private fun sendMoneyMessage(amount: Long, message: String) { - CoroutineScope(Dispatchers.IO).launch { - val block = transactionRepository.sendTransferProposalSync(publicKey.keyToBin(), amount) - if (block == null) { - Toast.makeText(requireContext(), "Insufficient balance", Toast.LENGTH_LONG).show() - } else { - getPeerChatCommunity().sendMessageWithTransaction(message, block.calculateHash(), publicKey) - } - } - } - - private fun requestMoney(amount: Long) { - val message = "GIB MONEY, $amount" - if (message.isNotEmpty()) { - getPeerChatCommunity().sendMessage(message, publicKey) - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val isRequest = requireArguments().getBoolean(ARG_IS_REQUEST) - - binding.txtBalance.text = - TransactionRepository.prettyAmount(transactionRepository.getMyVerifiedBalance()) - - if (isRequest) { - binding.btnTransfer.text = "Request money" - } else { - binding.btnTransfer.text = "Transfer money" - } - - binding.txtOwnPublicKey.text = ownPublicKey.toString() - - binding.edtAmount.addDecimalLimiter() - - binding.btnTransfer.setOnClickListener { - val amount = getAmount(binding.edtAmount.text.toString()) - val message = binding.edtMessage.text.toString() - if (isRequest) { - requestMoney(amount) - } else { - sendMoneyMessage(amount, message) - } - findNavController().navigateUp() - } - - binding.txtContactName.text = name - binding.txtContactPublicKey.text = publicKey.toString() - binding.avatar.setUser(publicKey.toString(), name) - } - - companion object { - const val ARG_PUBLIC_KEY = "public_key" - const val ARG_NAME = "name" - const val ARG_IS_REQUEST = "is_request" - - fun getAmount(amount: String): Long { - val regex = """[^\d]""".toRegex() - return regex.replace(amount, "").toLong() - } - - fun EditText.decimalLimiter(string: String): String { - - var amount = getAmount(string) - - if (amount == 0L) { - return "" - } - - // val amount = string.replace("[^\\d]", "").toLong() - return (amount / 100).toString() + "." + (amount % 100).toString().padStart(2, '0') - } - - fun EditText.addDecimalLimiter() { - - this.addTextChangedListener(object : TextWatcher { - - override fun afterTextChanged(s: Editable?) { - val str = this@addDecimalLimiter.text!!.toString() - if (str.isEmpty()) return - val str2 = decimalLimiter(str) - - if (str2 != str) { - this@addDecimalLimiter.setText(str2) - val pos = this@addDecimalLimiter.text!!.length - this@addDecimalLimiter.setSelection(pos) - } - } - - override fun beforeTextChanged( - s: CharSequence?, - start: Int, - count: Int, - after: Int - ) { - } - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - } - }) - } - } -} diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/feed/FeedFragment.kt b/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/feed/FeedFragment.kt deleted file mode 100644 index 7c47599f3..000000000 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/feed/FeedFragment.kt +++ /dev/null @@ -1,99 +0,0 @@ -package nl.tudelft.trustchain.peerchat.ui.feed - -import android.os.Bundle -import android.view.View -import android.widget.LinearLayout -import android.widget.Toast -import androidx.core.view.isVisible -import androidx.lifecycle.LiveData -import androidx.lifecycle.Observer -import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.liveData -import androidx.navigation.fragment.findNavController -import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.LinearLayoutManager -import com.mattskala.itemadapter.Item -import com.mattskala.itemadapter.ItemAdapter -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.delay -import kotlinx.coroutines.isActive -import nl.tudelft.ipv8.util.toHex -import nl.tudelft.trustchain.common.ui.BaseFragment -import nl.tudelft.trustchain.common.util.viewBinding -import nl.tudelft.trustchain.peerchat.R -import nl.tudelft.trustchain.peerchat.databinding.FragmentFeedBinding -import nl.tudelft.trustchain.peerchat.db.PeerChatStore - -@OptIn(ExperimentalCoroutinesApi::class) -class FeedFragment : BaseFragment(R.layout.fragment_feed) { - private val binding by viewBinding(FragmentFeedBinding::bind) - - private val store by lazy { - PeerChatStore.getInstance(requireContext()) - } - - private val postRepository by lazy { - PostRepository(getIpv8().getOverlay()!!, store) - } - - private val adapter = ItemAdapter() - - private val items: LiveData> by lazy { - liveData { emit(listOf()) } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - adapter.registerRenderer( - PostItemRenderer( - { - if (!postRepository.likePost(it.block)) { - Toast.makeText(requireContext(), "You already liked this post", Toast.LENGTH_SHORT) - .show() - } - }, - { - val args = Bundle() - args.putString(NewPostFragment.ARG_HASH, it.block.calculateHash().toHex()) - findNavController().navigate(R.id.action_feedFragment_to_newPostFragment, args) - } - ) - ) - - lifecycleScope.launchWhenResumed { - while (isActive) { - // Refresh peer status periodically - val items = postRepository.getPostsByFriends() - adapter.updateItems(items) - binding.imgEmpty.isVisible = items.isEmpty() - delay(1000L) - } - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.recyclerView.adapter = adapter - binding.recyclerView.layoutManager = LinearLayoutManager(context) - binding.recyclerView.addItemDecoration( - DividerItemDecoration( - context, - LinearLayout.VERTICAL - ) - ) - - binding.fab.setOnClickListener { - findNavController().navigate(R.id.action_feedFragment_to_newPostFragment) - } - - items.observe( - viewLifecycleOwner, - Observer { - adapter.updateItems(it) - binding.imgEmpty.isVisible = it.isEmpty() - } - ) - } -} diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/feed/NewPostFragment.kt b/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/feed/NewPostFragment.kt deleted file mode 100644 index 1da711a21..000000000 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/feed/NewPostFragment.kt +++ /dev/null @@ -1,67 +0,0 @@ -package nl.tudelft.trustchain.peerchat.ui.feed - -import android.os.Bundle -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem -import android.widget.Toast -import androidx.navigation.fragment.findNavController -import kotlinx.coroutines.ExperimentalCoroutinesApi -import nl.tudelft.ipv8.util.hexToBytes -import nl.tudelft.trustchain.common.ui.BaseFragment -import nl.tudelft.trustchain.common.util.viewBinding -import nl.tudelft.trustchain.peerchat.R -import nl.tudelft.trustchain.peerchat.databinding.FragmentNewPostBinding -import nl.tudelft.trustchain.peerchat.db.PeerChatStore - -@OptIn(ExperimentalCoroutinesApi::class) -class NewPostFragment : BaseFragment(R.layout.fragment_new_post) { - private val binding by viewBinding(FragmentNewPostBinding::bind) - - private val store by lazy { - PeerChatStore.getInstance(requireContext()) - } - - private val postRepository by lazy { - PostRepository(getIpv8().getOverlay()!!, store) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setHasOptionsMenu(true) - } - - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.options_new_post, menu) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - return when (item.itemId) { - R.id.post -> { - val text = binding.edtPost.text.toString() - if (text.isBlank()) { - Toast.makeText(context, "Your post is empty", Toast.LENGTH_SHORT).show() - } else { - val hash = arguments?.getString(ARG_HASH)?.hexToBytes() - if (hash != null) { - postRepository.createReply(hash, text) - Toast.makeText(context, "Your reply has been created", Toast.LENGTH_SHORT) - .show() - } else { - postRepository.createPost(text) - Toast.makeText(context, "Your post has been created", Toast.LENGTH_SHORT) - .show() - } - findNavController().navigateUp() - } - true - } - else -> super.onOptionsItemSelected(item) - } - } - - companion object { - val ARG_HASH = "hash" - } -} diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/feed/PostItem.kt b/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/feed/PostItem.kt deleted file mode 100644 index fa68d0ff5..000000000 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/feed/PostItem.kt +++ /dev/null @@ -1,18 +0,0 @@ -package nl.tudelft.trustchain.peerchat.ui.feed - -import com.mattskala.itemadapter.Item -import nl.tudelft.ipv8.attestation.trustchain.TrustChainBlock -import nl.tudelft.trustchain.common.contacts.Contact - -data class PostItem( - val block: TrustChainBlock, - val contact: Contact?, - val linkedBlock: TrustChainBlock?, - val linkedContact: Contact?, - val replies: List, - val likes: List, - /** - * True if I liked this post. - */ - val liked: Boolean -) : Item() diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/feed/PostItemRenderer.kt b/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/feed/PostItemRenderer.kt deleted file mode 100644 index 00b289be0..000000000 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/feed/PostItemRenderer.kt +++ /dev/null @@ -1,79 +0,0 @@ -package nl.tudelft.trustchain.peerchat.ui.feed - -import android.view.View -import androidx.core.content.res.ResourcesCompat -import androidx.core.view.isVisible -import com.mattskala.itemadapter.ItemLayoutRenderer -import kotlinx.android.synthetic.main.item_post.view.* -import nl.tudelft.trustchain.peerchat.R -import java.text.SimpleDateFormat - -class PostItemRenderer( - private val onLike: (PostItem) -> Unit, - private val onComment: (PostItem) -> Unit -) : ItemLayoutRenderer( - PostItem::class.java -) { - private val dateFormat = SimpleDateFormat.getDateTimeInstance() - - override fun bindView(item: PostItem, view: View) = with(view) { - val prefix = if (item.linkedContact != null) "@" + item.linkedContact.name + ": " else "" - val text = item.block.transaction[PostRepository.KEY_TEXT] as? String - txtMessage.text = prefix + text - - val lastMessageDate = item.block.timestamp - txtDate.text = dateFormat.format(lastMessageDate) - - txtName.text = item.contact?.name - - txtPeerId.text = item.contact?.mid - - if (item.contact != null) { - avatar.setUser(item.contact.mid, item.contact.name) - } - - btnLike.setOnClickListener { - onLike(item) - } - - btnComment.setOnClickListener { - onComment(item) - } - - txtCommentCount.text = item.replies.size.toString() - txtLikeCount.text = item.likes.size.toString() - - val likeColor = if (item.liked) - ResourcesCompat.getColor(view.resources, R.color.colorPrimary, null) else - ResourcesCompat.getColor(view.resources, R.color.text_secondary, null) - - btnLike.setColorFilter(likeColor) - txtLikeCount.setTextColor(likeColor) - - btnComment.isVisible = item.block.isProposal - txtCommentCount.isVisible = item.block.isProposal - - /* - // txtName.isVisible = item.contact.name.isNotEmpty() - txtPeerId.text = item.contact.mid - txtMessage.isVisible = item.lastMessage != null - txtMessage.text = item.lastMessage?.message - val textStyle = if (item.lastMessage?.read == false) Typeface.BOLD else Typeface.NORMAL - txtMessage.setTypeface(null, textStyle) - setOnClickListener { - onItemClick(item.contact) - } - imgWifi.isVisible = item.isOnline - imgBluetooth.isVisible = item.isBluetooth - avatar.setUser(item.contact.mid, item.contact.name) - setOnLongClickListener { - onItemLongClick(item.contact) - true - } - */ - } - - override fun getLayoutResourceId(): Int { - return R.layout.item_post - } -} diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/feed/PostRepository.kt b/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/feed/PostRepository.kt deleted file mode 100644 index 4fb0f21a7..000000000 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/feed/PostRepository.kt +++ /dev/null @@ -1,113 +0,0 @@ -package nl.tudelft.trustchain.peerchat.ui.feed - -import kotlinx.coroutines.flow.first -import nl.tudelft.ipv8.Peer -import nl.tudelft.ipv8.android.IPv8Android -import nl.tudelft.ipv8.attestation.trustchain.* -import nl.tudelft.ipv8.attestation.trustchain.store.TrustChainStore -import nl.tudelft.trustchain.common.contacts.Contact -import nl.tudelft.trustchain.peerchat.db.PeerChatStore - -class PostRepository( - private val trustChainCommunity: TrustChainCommunity, - private val peerChatStore: PeerChatStore -) { - fun createPost(text: String): TrustChainBlock { - val transaction = mapOf( - KEY_TEXT to text - ) - return trustChainCommunity.createProposalBlock( - BLOCK_TYPE_POST, transaction, - ANY_COUNTERPARTY_PK - ) - } - - fun createReply(blockHash: ByteArray, text: String): TrustChainBlock? { - val transaction = mapOf( - KEY_TEXT to text - ) - val block = trustChainCommunity.database.getBlockWithHash(blockHash) - return if (block != null) { - trustChainCommunity.createAgreementBlock(block, transaction) - } else { - null - } - } - - fun likePost(block: TrustChainBlock): Boolean { - return if (!hasLikedPost(block)) { - LikeBlockBuilder( - trustChainCommunity.myPeer, - trustChainCommunity.database, block - ).sign() -// trustChainCommunity.onBlockCreated(likeBlock) TODO create a public method for this - true - } else { - false - } - } - - /** - * Returns true if the current user has liked the post. - */ - fun hasLikedPost(block: TrustChainBlock): Boolean { - val myPeer = IPv8Android.getInstance().myPeer - val linkedBlocks = trustChainCommunity.database - .getAllLinked(block) - return linkedBlocks.find { - it.type == BLOCK_TYPE_LIKE && - it.publicKey.contentEquals(myPeer.publicKey.keyToBin()) - } != null - } - - private fun findContactByPublicKey(contacts: List, publicKeyBin: ByteArray): Contact? { - val myPeer = IPv8Android.getInstance().myPeer - val myContact = Contact("You", myPeer.publicKey) - return if (publicKeyBin.contentEquals(myPeer.publicKey.keyToBin())) - myContact - else contacts.find { - it.publicKey.keyToBin().contentEquals(publicKeyBin) - } - } - - suspend fun getPostsByFriends(): List { - val myPeer = IPv8Android.getInstance().myPeer - val contacts = peerChatStore.contactsStore.getContacts().first() - val posts = trustChainCommunity.database - .getBlocksWithType(BLOCK_TYPE_POST) - .sortedByDescending { it.insertTime } - val likes = trustChainCommunity.database - .getBlocksWithType(BLOCK_TYPE_LIKE) - .sortedByDescending { it.insertTime } - return posts.map { post -> - val contact = findContactByPublicKey(contacts, post.publicKey) - val replies = posts.filter { it.linkedBlockId == post.blockId } - val postLikes = likes.filter { it.linkedBlockId == post.blockId } - val liked = postLikes.find { - it.publicKey.contentEquals(myPeer.publicKey.keyToBin()) - } != null - val linkedBlock = posts.find { it.blockId == post.linkedBlockId } - val linkedContact = findContactByPublicKey(contacts, post.linkPublicKey) - PostItem(post, contact, linkedBlock, linkedContact, replies, postLikes, liked) - } - } - - companion object { - private const val BLOCK_TYPE_POST = "post" - private const val BLOCK_TYPE_LIKE = "like" - const val KEY_TEXT = "text" - } - - class LikeBlockBuilder( - myPeer: Peer, - database: TrustChainStore, - private val link: TrustChainBlock - ) : BlockBuilder(myPeer, database) { - override fun update(builder: TrustChainBlock.Builder) { - builder.type = BLOCK_TYPE_LIKE - builder.rawTransaction = TransactionEncoding.encode(mapOf()) - builder.linkPublicKey = link.publicKey - builder.linkSequenceNumber = link.sequenceNumber - } - } -} diff --git a/peerchat/src/main/res/drawable-hdpi/ic_delivered.png b/peerchat/src/main/res/drawable-hdpi/ic_delivered.png deleted file mode 100644 index f8294b69d..000000000 Binary files a/peerchat/src/main/res/drawable-hdpi/ic_delivered.png and /dev/null differ diff --git a/peerchat/src/main/res/drawable-hdpi/ic_sent.png b/peerchat/src/main/res/drawable-hdpi/ic_sent.png deleted file mode 100644 index 437d9d778..000000000 Binary files a/peerchat/src/main/res/drawable-hdpi/ic_sent.png and /dev/null differ diff --git a/peerchat/src/main/res/drawable-mdpi/ic_delivered.png b/peerchat/src/main/res/drawable-mdpi/ic_delivered.png deleted file mode 100644 index dccbdb68c..000000000 Binary files a/peerchat/src/main/res/drawable-mdpi/ic_delivered.png and /dev/null differ diff --git a/peerchat/src/main/res/drawable-mdpi/ic_sent.png b/peerchat/src/main/res/drawable-mdpi/ic_sent.png deleted file mode 100644 index 8bc3a5d68..000000000 Binary files a/peerchat/src/main/res/drawable-mdpi/ic_sent.png and /dev/null differ diff --git a/peerchat/src/main/res/drawable-xhdpi/ic_delivered.png b/peerchat/src/main/res/drawable-xhdpi/ic_delivered.png deleted file mode 100644 index 2950eb98d..000000000 Binary files a/peerchat/src/main/res/drawable-xhdpi/ic_delivered.png and /dev/null differ diff --git a/peerchat/src/main/res/drawable-xhdpi/ic_sent.png b/peerchat/src/main/res/drawable-xhdpi/ic_sent.png deleted file mode 100644 index 693b584ed..000000000 Binary files a/peerchat/src/main/res/drawable-xhdpi/ic_sent.png and /dev/null differ diff --git a/peerchat/src/main/res/drawable-xxhdpi/ic_delivered.png b/peerchat/src/main/res/drawable-xxhdpi/ic_delivered.png deleted file mode 100644 index 6ac1e717f..000000000 Binary files a/peerchat/src/main/res/drawable-xxhdpi/ic_delivered.png and /dev/null differ diff --git a/peerchat/src/main/res/drawable-xxhdpi/ic_sent.png b/peerchat/src/main/res/drawable-xxhdpi/ic_sent.png deleted file mode 100644 index bdbf5f28f..000000000 Binary files a/peerchat/src/main/res/drawable-xxhdpi/ic_sent.png and /dev/null differ diff --git a/peerchat/src/main/res/drawable-xxxhdpi/ic_delivered.png b/peerchat/src/main/res/drawable-xxxhdpi/ic_delivered.png deleted file mode 100644 index 7a4a928ec..000000000 Binary files a/peerchat/src/main/res/drawable-xxxhdpi/ic_delivered.png and /dev/null differ diff --git a/peerchat/src/main/res/drawable-xxxhdpi/ic_sent.png b/peerchat/src/main/res/drawable-xxxhdpi/ic_sent.png deleted file mode 100644 index 0d5f423dc..000000000 Binary files a/peerchat/src/main/res/drawable-xxxhdpi/ic_sent.png and /dev/null differ diff --git a/peerchat/src/main/res/drawable/bg_field.xml b/peerchat/src/main/res/drawable/bg_field.xml deleted file mode 100644 index 64ae6cbab..000000000 --- a/peerchat/src/main/res/drawable/bg_field.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - diff --git a/peerchat/src/main/res/drawable/bg_message.xml b/peerchat/src/main/res/drawable/bg_message.xml deleted file mode 100644 index c979c4b47..000000000 --- a/peerchat/src/main/res/drawable/bg_message.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/peerchat/src/main/res/drawable/circle_step.xml b/peerchat/src/main/res/drawable/circle_step.xml deleted file mode 100644 index a97fcb117..000000000 --- a/peerchat/src/main/res/drawable/circle_step.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - diff --git a/peerchat/src/main/res/drawable/ic_baseline_add_photo_alternate_24.xml b/peerchat/src/main/res/drawable/ic_baseline_add_photo_alternate_24.xml deleted file mode 100644 index 190b26813..000000000 --- a/peerchat/src/main/res/drawable/ic_baseline_add_photo_alternate_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/peerchat/src/main/res/drawable/ic_baseline_dynamic_feed_24.xml b/peerchat/src/main/res/drawable/ic_baseline_dynamic_feed_24.xml deleted file mode 100644 index 71d8fc5ab..000000000 --- a/peerchat/src/main/res/drawable/ic_baseline_dynamic_feed_24.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - diff --git a/peerchat/src/main/res/drawable/ic_baseline_mode_comment_24.xml b/peerchat/src/main/res/drawable/ic_baseline_mode_comment_24.xml deleted file mode 100644 index 84c28ee32..000000000 --- a/peerchat/src/main/res/drawable/ic_baseline_mode_comment_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/peerchat/src/main/res/drawable/ic_baseline_people_24.xml b/peerchat/src/main/res/drawable/ic_baseline_people_24.xml deleted file mode 100644 index 71da7d97d..000000000 --- a/peerchat/src/main/res/drawable/ic_baseline_people_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/peerchat/src/main/res/drawable/ic_baseline_person_24.xml b/peerchat/src/main/res/drawable/ic_baseline_person_24.xml deleted file mode 100644 index 07eeb5ad8..000000000 --- a/peerchat/src/main/res/drawable/ic_baseline_person_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/peerchat/src/main/res/drawable/ic_baseline_reply_24.xml b/peerchat/src/main/res/drawable/ic_baseline_reply_24.xml deleted file mode 100644 index bd35ee9e7..000000000 --- a/peerchat/src/main/res/drawable/ic_baseline_reply_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/peerchat/src/main/res/drawable/ic_baseline_request_money_24.xml b/peerchat/src/main/res/drawable/ic_baseline_request_money_24.xml deleted file mode 100644 index 899ac532f..000000000 --- a/peerchat/src/main/res/drawable/ic_baseline_request_money_24.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - > - diff --git a/peerchat/src/main/res/drawable/ic_baseline_send_money_24.xml b/peerchat/src/main/res/drawable/ic_baseline_send_money_24.xml deleted file mode 100644 index b5b3a0bb1..000000000 --- a/peerchat/src/main/res/drawable/ic_baseline_send_money_24.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - diff --git a/peerchat/src/main/res/drawable/ic_baseline_thumb_up_24.xml b/peerchat/src/main/res/drawable/ic_baseline_thumb_up_24.xml deleted file mode 100644 index 146f85cee..000000000 --- a/peerchat/src/main/res/drawable/ic_baseline_thumb_up_24.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/peerchat/src/main/res/drawable/ic_bluetooth_black_24dp.xml b/peerchat/src/main/res/drawable/ic_bluetooth_black_24dp.xml deleted file mode 100644 index 4b0b71eaf..000000000 --- a/peerchat/src/main/res/drawable/ic_bluetooth_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/peerchat/src/main/res/drawable/ic_camera_alt_black_24dp.xml b/peerchat/src/main/res/drawable/ic_camera_alt_black_24dp.xml deleted file mode 100644 index 946934cf0..000000000 --- a/peerchat/src/main/res/drawable/ic_camera_alt_black_24dp.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - diff --git a/peerchat/src/main/res/drawable/ic_cloud_off_black_24dp.xml b/peerchat/src/main/res/drawable/ic_cloud_off_black_24dp.xml deleted file mode 100644 index 94909d21c..000000000 --- a/peerchat/src/main/res/drawable/ic_cloud_off_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/peerchat/src/main/res/drawable/ic_content_copy_black_24dp.xml b/peerchat/src/main/res/drawable/ic_content_copy_black_24dp.xml deleted file mode 100644 index 380c8783e..000000000 --- a/peerchat/src/main/res/drawable/ic_content_copy_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/peerchat/src/main/res/drawable/ic_link_white_24dp.xml b/peerchat/src/main/res/drawable/ic_link_white_24dp.xml deleted file mode 100644 index c5acaaa98..000000000 --- a/peerchat/src/main/res/drawable/ic_link_white_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/peerchat/src/main/res/drawable/ic_person_add_black_24dp.xml b/peerchat/src/main/res/drawable/ic_person_add_black_24dp.xml deleted file mode 100644 index 9c533518c..000000000 --- a/peerchat/src/main/res/drawable/ic_person_add_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/peerchat/src/main/res/drawable/ic_photo_camera_white_24dp.xml b/peerchat/src/main/res/drawable/ic_photo_camera_white_24dp.xml deleted file mode 100644 index d7a3c0f9b..000000000 --- a/peerchat/src/main/res/drawable/ic_photo_camera_white_24dp.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - diff --git a/peerchat/src/main/res/drawable/ic_send_black_24dp.xml b/peerchat/src/main/res/drawable/ic_send_black_24dp.xml deleted file mode 100644 index fdf1c9009..000000000 --- a/peerchat/src/main/res/drawable/ic_send_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/peerchat/src/main/res/drawable/ic_wifi_black_24dp.xml b/peerchat/src/main/res/drawable/ic_wifi_black_24dp.xml deleted file mode 100644 index f0f6f477a..000000000 --- a/peerchat/src/main/res/drawable/ic_wifi_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/peerchat/src/main/res/drawable/indicator_offline.xml b/peerchat/src/main/res/drawable/indicator_offline.xml deleted file mode 100644 index e9424ceaa..000000000 --- a/peerchat/src/main/res/drawable/indicator_offline.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - diff --git a/peerchat/src/main/res/drawable/indicator_online.xml b/peerchat/src/main/res/drawable/indicator_online.xml deleted file mode 100644 index 8f42993a9..000000000 --- a/peerchat/src/main/res/drawable/indicator_online.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - diff --git a/peerchat/src/main/res/layout/fragment_add_contact.xml b/peerchat/src/main/res/layout/fragment_add_contact.xml deleted file mode 100644 index c56ea2be7..000000000 --- a/peerchat/src/main/res/layout/fragment_add_contact.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/peerchat/src/main/res/layout/fragment_add_nearby.xml b/peerchat/src/main/res/layout/fragment_add_nearby.xml deleted file mode 100644 index 674f43047..000000000 --- a/peerchat/src/main/res/layout/fragment_add_nearby.xml +++ /dev/null @@ -1,106 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/peerchat/src/main/res/layout/fragment_add_remote.xml b/peerchat/src/main/res/layout/fragment_add_remote.xml deleted file mode 100644 index 0c6676302..000000000 --- a/peerchat/src/main/res/layout/fragment_add_remote.xml +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/peerchat/src/main/res/layout/fragment_contacts.xml b/peerchat/src/main/res/layout/fragment_contacts.xml deleted file mode 100644 index f305d8095..000000000 --- a/peerchat/src/main/res/layout/fragment_contacts.xml +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/peerchat/src/main/res/layout/fragment_conversation.xml b/peerchat/src/main/res/layout/fragment_conversation.xml deleted file mode 100644 index 3e185c159..000000000 --- a/peerchat/src/main/res/layout/fragment_conversation.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/peerchat/src/main/res/layout/fragment_feed.xml b/peerchat/src/main/res/layout/fragment_feed.xml deleted file mode 100644 index f3a4150ac..000000000 --- a/peerchat/src/main/res/layout/fragment_feed.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - diff --git a/peerchat/src/main/res/layout/fragment_new_post.xml b/peerchat/src/main/res/layout/fragment_new_post.xml deleted file mode 100644 index 4e0a1c1d5..000000000 --- a/peerchat/src/main/res/layout/fragment_new_post.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - diff --git a/peerchat/src/main/res/layout/item_contact.xml b/peerchat/src/main/res/layout/item_contact.xml deleted file mode 100644 index bb0f18461..000000000 --- a/peerchat/src/main/res/layout/item_contact.xml +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/peerchat/src/main/res/layout/item_message.xml b/peerchat/src/main/res/layout/item_message.xml deleted file mode 100644 index e06d84601..000000000 --- a/peerchat/src/main/res/layout/item_message.xml +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/peerchat/src/main/res/layout/item_post.xml b/peerchat/src/main/res/layout/item_post.xml deleted file mode 100644 index c38dca3f9..000000000 --- a/peerchat/src/main/res/layout/item_post.xml +++ /dev/null @@ -1,130 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/peerchat/src/main/res/layout/transfer_fragment.xml b/peerchat/src/main/res/layout/transfer_fragment.xml deleted file mode 100644 index 48e1d8591..000000000 --- a/peerchat/src/main/res/layout/transfer_fragment.xml +++ /dev/null @@ -1,197 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/peerchat/src/main/res/layout/view_avatar.xml b/peerchat/src/main/res/layout/view_avatar.xml deleted file mode 100644 index de93797dc..000000000 --- a/peerchat/src/main/res/layout/view_avatar.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - diff --git a/peerchat/src/main/res/menu/options_new_post.xml b/peerchat/src/main/res/menu/options_new_post.xml deleted file mode 100644 index 1f991382f..000000000 --- a/peerchat/src/main/res/menu/options_new_post.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - diff --git a/peerchat/src/main/res/menu/peerchat_navigation_menu.xml b/peerchat/src/main/res/menu/peerchat_navigation_menu.xml deleted file mode 100644 index 5b052932d..000000000 --- a/peerchat/src/main/res/menu/peerchat_navigation_menu.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - diff --git a/peerchat/src/main/res/navigation/nav_graph_peerchat.xml b/peerchat/src/main/res/navigation/nav_graph_peerchat.xml deleted file mode 100644 index a25473b63..000000000 --- a/peerchat/src/main/res/navigation/nav_graph_peerchat.xml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/peerchat/src/main/res/values/dimens.xml b/peerchat/src/main/res/values/dimens.xml deleted file mode 100644 index 2f5fa2fab..000000000 --- a/peerchat/src/main/res/values/dimens.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - 52dp - 16dp - diff --git a/peerchat/src/main/res/values/strings.xml b/peerchat/src/main/res/values/strings.xml deleted file mode 100644 index 88913309f..000000000 --- a/peerchat/src/main/res/values/strings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/peerchat/src/main/res/values/styles.xml b/peerchat/src/main/res/values/styles.xml deleted file mode 100644 index e54ac6739..000000000 --- a/peerchat/src/main/res/values/styles.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - diff --git a/peerchat/src/main/sqldelight/2.sqm b/peerchat/src/main/sqldelight/2.sqm deleted file mode 100644 index 290d10806..000000000 --- a/peerchat/src/main/sqldelight/2.sqm +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE messages ADD COLUMN transaction_hash BLOB; diff --git a/peerchat/src/test/java/nl/tudelft/trustchain/debug/ExampleUnitTest.kt b/peerchat/src/test/java/nl/tudelft/trustchain/debug/ExampleUnitTest.kt deleted file mode 100644 index 487585659..000000000 --- a/peerchat/src/test/java/nl/tudelft/trustchain/debug/ExampleUnitTest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package nl.tudelft.trustchain.debug - -import org.junit.Assert.assertEquals -import org.junit.Test - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, (2 + 2)) - } -} diff --git a/settings.gradle b/settings.gradle index 0cc8bc75d..80c4d8a9d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -21,7 +21,5 @@ include ':eurotoken' include ':freedomOfComputing' include ':musicdao' include ':musicdao-datafeeder' -include ':gossipML' -include ':peerchat' include ':valuetransfer' include ':geth-android' diff --git a/valuetransfer/build.gradle b/valuetransfer/build.gradle index 5ee702373..bd59b814c 100644 --- a/valuetransfer/build.gradle +++ b/valuetransfer/build.gradle @@ -97,7 +97,6 @@ dependencies { implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation "androidx.room:room-runtime:$room_version" - implementation project(path: ':peerchat') implementation project(path: ':eurotoken') implementation 'com.google.android.gms:play-services-maps:18.0.0' implementation 'com.google.android.gms:play-services-location:18.0.0' diff --git a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ValueTransferMainActivity.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ValueTransferMainActivity.kt index b643b8a3c..bc340d81a 100644 --- a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ValueTransferMainActivity.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ValueTransferMainActivity.kt @@ -62,11 +62,11 @@ import nl.tudelft.trustchain.common.valuetransfer.extensions.decodeBytes import nl.tudelft.trustchain.common.valuetransfer.extensions.imageBytes import nl.tudelft.trustchain.common.valuetransfer.extensions.resize import nl.tudelft.trustchain.eurotoken.community.EuroTokenCommunity -import nl.tudelft.trustchain.peerchat.community.PeerChatCommunity -import nl.tudelft.trustchain.peerchat.db.PeerChatStore -import nl.tudelft.trustchain.peerchat.entity.ChatMessage -import nl.tudelft.trustchain.peerchat.entity.ContactImage -import nl.tudelft.trustchain.peerchat.ui.conversation.MessageAttachment +import nl.tudelft.trustchain.valuetransfer.community.PeerChatCommunity +import nl.tudelft.trustchain.valuetransfer.util.PeerChatStore +import nl.tudelft.trustchain.valuetransfer.util.ChatMessage +import nl.tudelft.trustchain.valuetransfer.util.ContactImage +import nl.tudelft.trustchain.valuetransfer.util.MessageAttachment import nl.tudelft.trustchain.valuetransfer.community.IdentityCommunity import nl.tudelft.trustchain.valuetransfer.db.IdentityStore import nl.tudelft.trustchain.valuetransfer.dialogs.IdentityAttestationConfirmDialog diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/AckPayload.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/AckPayload.kt similarity index 93% rename from peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/AckPayload.kt rename to valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/AckPayload.kt index 241b6e1e7..7ad475076 100644 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/AckPayload.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/AckPayload.kt @@ -1,4 +1,4 @@ -package nl.tudelft.trustchain.peerchat.community +package nl.tudelft.trustchain.valuetransfer.community import nl.tudelft.ipv8.messaging.Deserializable import nl.tudelft.ipv8.messaging.Serializable diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/AttachmentPayload.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/AttachmentPayload.kt similarity index 95% rename from peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/AttachmentPayload.kt rename to valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/AttachmentPayload.kt index 1854c3cfb..dcb94666a 100644 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/AttachmentPayload.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/AttachmentPayload.kt @@ -1,4 +1,4 @@ -package nl.tudelft.trustchain.peerchat.community +package nl.tudelft.trustchain.valuetransfer.community import nl.tudelft.ipv8.messaging.Deserializable import nl.tudelft.ipv8.messaging.Serializable diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/AttachmentRequestPayload.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/AttachmentRequestPayload.kt similarity index 94% rename from peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/AttachmentRequestPayload.kt rename to valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/AttachmentRequestPayload.kt index 1071a945e..0ee66c444 100644 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/AttachmentRequestPayload.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/AttachmentRequestPayload.kt @@ -1,4 +1,4 @@ -package nl.tudelft.trustchain.peerchat.community +package nl.tudelft.trustchain.valuetransfer.community import nl.tudelft.ipv8.messaging.Deserializable import nl.tudelft.ipv8.messaging.Serializable diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/ContactConnectPayload.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/ContactConnectPayload.kt similarity index 97% rename from peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/ContactConnectPayload.kt rename to valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/ContactConnectPayload.kt index 04ecd3afd..8d18fbe85 100644 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/ContactConnectPayload.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/ContactConnectPayload.kt @@ -1,4 +1,4 @@ -package nl.tudelft.trustchain.peerchat.community +package nl.tudelft.trustchain.valuetransfer.community import nl.tudelft.ipv8.keyvault.PublicKey import nl.tudelft.ipv8.keyvault.defaultCryptoProvider diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/ContactImagePayload.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/ContactImagePayload.kt similarity index 96% rename from peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/ContactImagePayload.kt rename to valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/ContactImagePayload.kt index 0dbe6098c..fcacf9f93 100644 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/ContactImagePayload.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/ContactImagePayload.kt @@ -1,4 +1,4 @@ -package nl.tudelft.trustchain.peerchat.community +package nl.tudelft.trustchain.valuetransfer.community import android.graphics.Bitmap import nl.tudelft.ipv8.keyvault.PublicKey diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/ContactImageRequestPayload.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/ContactImageRequestPayload.kt similarity index 95% rename from peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/ContactImageRequestPayload.kt rename to valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/ContactImageRequestPayload.kt index 5a3f27c16..b18cedffd 100644 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/ContactImageRequestPayload.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/ContactImageRequestPayload.kt @@ -1,4 +1,4 @@ -package nl.tudelft.trustchain.peerchat.community +package nl.tudelft.trustchain.valuetransfer.community import nl.tudelft.ipv8.keyvault.PublicKey import nl.tudelft.ipv8.keyvault.defaultCryptoProvider diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/MessagePayload.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/MessagePayload.kt similarity index 98% rename from peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/MessagePayload.kt rename to valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/MessagePayload.kt index ae2e3beb7..d0a4b9fdd 100644 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/MessagePayload.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/MessagePayload.kt @@ -1,4 +1,4 @@ -package nl.tudelft.trustchain.peerchat.community +package nl.tudelft.trustchain.valuetransfer.community import mu.KotlinLogging import nl.tudelft.ipv8.messaging.* diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/PeerChatCommunity.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/PeerChatCommunity.kt similarity index 94% rename from peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/PeerChatCommunity.kt rename to valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/PeerChatCommunity.kt index d13432b30..10d1bed7d 100644 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/community/PeerChatCommunity.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/community/PeerChatCommunity.kt @@ -1,4 +1,4 @@ -package nl.tudelft.trustchain.peerchat.community +package nl.tudelft.trustchain.valuetransfer.community import android.content.Context import android.database.sqlite.SQLiteConstraintException @@ -21,10 +21,10 @@ import nl.tudelft.trustchain.common.contacts.Contact import nl.tudelft.trustchain.common.valuetransfer.entity.IdentityAttribute import nl.tudelft.trustchain.common.valuetransfer.entity.IdentityInfo import nl.tudelft.trustchain.common.valuetransfer.entity.TransferRequest -import nl.tudelft.trustchain.peerchat.db.PeerChatStore -import nl.tudelft.trustchain.peerchat.entity.ChatMessage -import nl.tudelft.trustchain.peerchat.entity.ContactImage -import nl.tudelft.trustchain.peerchat.ui.conversation.MessageAttachment +import nl.tudelft.trustchain.valuetransfer.util.PeerChatStore +import nl.tudelft.trustchain.valuetransfer.util.ChatMessage +import nl.tudelft.trustchain.valuetransfer.util.ContactImage +import nl.tudelft.trustchain.valuetransfer.util.MessageAttachment import org.json.JSONObject import java.io.File import java.io.FileOutputStream @@ -245,7 +245,8 @@ class PeerChatCommunity( json.put("address_line", addressLine) val serialized = json.toString().toByteArray() - val attachment = MessageAttachment(MessageAttachment.TYPE_LOCATION, serialized.size.toLong(), serialized) + val attachment = + MessageAttachment(MessageAttachment.TYPE_LOCATION, serialized.size.toLong(), serialized) val chatMessage = createOutgoingChatMessage(addressLine, attachment, null, recipient, identityInfo) database.addMessage(chatMessage) @@ -260,7 +261,11 @@ class PeerChatCommunity( ) { val serialized = transferRequest.serialize() - val attachment = MessageAttachment(MessageAttachment.TYPE_TRANSFER_REQUEST, serialized.size.toLong(), serialized) + val attachment = MessageAttachment( + MessageAttachment.TYPE_TRANSFER_REQUEST, + serialized.size.toLong(), + serialized + ) val chatMessage = createOutgoingChatMessage(description ?: "", attachment, null, recipient, identityInfo) database.addMessage(chatMessage) @@ -274,7 +279,8 @@ class PeerChatCommunity( ) { val serialized = contact.serialize() val contactMessage = "${contact.name} ${contact.publicKey.keyToBin().toHex()}" - val attachment = MessageAttachment(MessageAttachment.TYPE_CONTACT, serialized.size.toLong(), serialized) + val attachment = + MessageAttachment(MessageAttachment.TYPE_CONTACT, serialized.size.toLong(), serialized) val chatMessage = createOutgoingChatMessage(contactMessage, attachment, null, recipient, identityInfo) database.addMessage(chatMessage) @@ -358,7 +364,8 @@ class PeerChatCommunity( fun sendContactImage(peer: Peer, contactImage: ContactImage) { Log.d("VTLOG", "SEND CONTACT IMAGE") - val payload = ContactImagePayload(contactImage.publicKey, contactImage.imageHash, contactImage.image) + val payload = + ContactImagePayload(contactImage.publicKey, contactImage.imageHash, contactImage.image) val packet = serializePacket(MessageId.CONTACT_IMAGE, payload, encrypt = true, recipient = peer) logger.debug { "-> $payload" } send(peer, packet) @@ -369,34 +376,34 @@ class PeerChatCommunity( */ private fun onMessagePacket(packet: Packet) { val (peer, payload) = packet.getDecryptedAuthPayload( - MessagePayload.Deserializer, myPeer.key as PrivateKey + MessagePayload, myPeer.key as PrivateKey ) logger.debug { "<- $payload, ${payload.transactionHash}" } onMessage(peer, payload) } private fun onAckPacket(packet: Packet) { - val (peer, payload) = packet.getAuthPayload(AckPayload.Deserializer) + val (peer, payload) = packet.getAuthPayload(AckPayload) logger.debug { "<- $payload" } onAck(peer, payload) } private fun onAttachmentRequestPacket(packet: Packet) { - val (peer, payload) = packet.getAuthPayload(AttachmentRequestPayload.Deserializer) + val (peer, payload) = packet.getAuthPayload(AttachmentRequestPayload) logger.debug { "<- $payload" } onAttachmentRequest(peer, payload) } private fun onAttachmentPacket(packet: Packet) { val (_, payload) = packet.getDecryptedAuthPayload( - AttachmentPayload.Deserializer, myPeer.key as PrivateKey + AttachmentPayload, myPeer.key as PrivateKey ) logger.debug { "<- $payload" } onAttachment(payload) } private fun onContactImageRequestPacket(packet: Packet) { - val (peer, _) = packet.getAuthPayload(ContactImageRequestPayload.Deserializer) + val (peer, _) = packet.getAuthPayload(ContactImageRequestPayload) logger.debug { "<- $peer" } onContactImageRequest(peer) @@ -406,7 +413,7 @@ class PeerChatCommunity( Log.d("VTLOG", "CONTACT IMAGE PACKET RECEIVED") val (peer, payload) = packet.getDecryptedAuthPayload( - ContactImagePayload.Deserializer, myPeer.key as PrivateKey + ContactImagePayload, myPeer.key as PrivateKey ) Log.d("VTLOG", "CONTACT IMAGE CONTENTS PEER: $peer") @@ -500,7 +507,13 @@ class PeerChatCommunity( private fun onContactImage(payload: ContactImagePayload) { Log.d("VTLOG", "ON CONTACT IMAGE with Payload image hash: ${payload.imageHash}") if (this::onContactImageCallback.isInitialized) { - this.onContactImageCallback(ContactImage(payload.publicKey, payload.imageHash, payload.image)) + this.onContactImageCallback( + ContactImage( + payload.publicKey, + payload.imageHash, + payload.image + ) + ) return } diff --git a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/dialogs/ChatMediaDialog.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/dialogs/ChatMediaDialog.kt index d7f094b8e..dc6a3f5e4 100644 --- a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/dialogs/ChatMediaDialog.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/dialogs/ChatMediaDialog.kt @@ -12,8 +12,8 @@ import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.mattskala.itemadapter.ItemAdapter import nl.tudelft.ipv8.keyvault.PublicKey -import nl.tudelft.trustchain.peerchat.entity.ChatMessage -import nl.tudelft.trustchain.peerchat.ui.conversation.MessageAttachment +import nl.tudelft.trustchain.valuetransfer.util.ChatMessage +import nl.tudelft.trustchain.valuetransfer.util.MessageAttachment import nl.tudelft.trustchain.valuetransfer.R import nl.tudelft.trustchain.valuetransfer.ui.VTDialogFragment import java.text.SimpleDateFormat diff --git a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/dialogs/ContactInfoDialog.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/dialogs/ContactInfoDialog.kt index ad0672126..c56c7a156 100644 --- a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/dialogs/ContactInfoDialog.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/dialogs/ContactInfoDialog.kt @@ -30,9 +30,9 @@ import nl.tudelft.trustchain.common.contacts.Contact import nl.tudelft.trustchain.common.eurotoken.Transaction import nl.tudelft.trustchain.common.util.getColorByHash import nl.tudelft.trustchain.common.valuetransfer.entity.IdentityAttribute -import nl.tudelft.trustchain.peerchat.entity.ContactImage -import nl.tudelft.trustchain.peerchat.entity.ContactState -import nl.tudelft.trustchain.peerchat.ui.conversation.MessageAttachment +import nl.tudelft.trustchain.valuetransfer.util.ContactImage +import nl.tudelft.trustchain.valuetransfer.util.ContactState +import nl.tudelft.trustchain.valuetransfer.util.MessageAttachment import nl.tudelft.trustchain.valuetransfer.R import nl.tudelft.trustchain.valuetransfer.ui.VTDialogFragment import nl.tudelft.trustchain.valuetransfer.ui.contacts.ContactChatFragment diff --git a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/VTDialogFragment.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/VTDialogFragment.kt index b03c556bf..a48a5d48f 100644 --- a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/VTDialogFragment.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/VTDialogFragment.kt @@ -8,8 +8,8 @@ import nl.tudelft.trustchain.common.eurotoken.GatewayStore import nl.tudelft.trustchain.common.eurotoken.TransactionRepository import nl.tudelft.trustchain.common.util.TrustChainHelper import nl.tudelft.trustchain.eurotoken.community.EuroTokenCommunity -import nl.tudelft.trustchain.peerchat.community.PeerChatCommunity -import nl.tudelft.trustchain.peerchat.db.PeerChatStore +import nl.tudelft.trustchain.valuetransfer.community.PeerChatCommunity +import nl.tudelft.trustchain.valuetransfer.util.PeerChatStore import nl.tudelft.trustchain.valuetransfer.ValueTransferMainActivity import nl.tudelft.trustchain.valuetransfer.community.IdentityCommunity import nl.tudelft.trustchain.valuetransfer.db.IdentityStore diff --git a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/VTFragment.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/VTFragment.kt index 2c8fdf28f..bd97dfa04 100644 --- a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/VTFragment.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/VTFragment.kt @@ -8,8 +8,8 @@ import nl.tudelft.trustchain.common.eurotoken.TransactionRepository import nl.tudelft.trustchain.common.ui.BaseFragment import nl.tudelft.trustchain.common.util.TrustChainHelper import nl.tudelft.trustchain.eurotoken.community.EuroTokenCommunity -import nl.tudelft.trustchain.peerchat.community.PeerChatCommunity -import nl.tudelft.trustchain.peerchat.db.PeerChatStore +import nl.tudelft.trustchain.valuetransfer.community.PeerChatCommunity +import nl.tudelft.trustchain.valuetransfer.util.PeerChatStore import nl.tudelft.trustchain.valuetransfer.ValueTransferMainActivity import nl.tudelft.trustchain.valuetransfer.community.IdentityCommunity import nl.tudelft.trustchain.valuetransfer.db.IdentityStore diff --git a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ChatItem.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ChatItem.kt index 5e694a1b2..9d9b076f1 100644 --- a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ChatItem.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ChatItem.kt @@ -2,9 +2,9 @@ package nl.tudelft.trustchain.valuetransfer.ui.contacts import com.mattskala.itemadapter.Item import nl.tudelft.trustchain.common.contacts.Contact -import nl.tudelft.trustchain.peerchat.entity.ChatMessage -import nl.tudelft.trustchain.peerchat.entity.ContactImage -import nl.tudelft.trustchain.peerchat.entity.ContactState +import nl.tudelft.trustchain.valuetransfer.util.ChatMessage +import nl.tudelft.trustchain.valuetransfer.util.ContactImage +import nl.tudelft.trustchain.valuetransfer.util.ContactState data class ChatItem( val contact: Contact, diff --git a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ChatItemRenderer.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ChatItemRenderer.kt index 499fc49f6..096410acc 100644 --- a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ChatItemRenderer.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ChatItemRenderer.kt @@ -8,7 +8,7 @@ import kotlinx.android.synthetic.main.item_contacts_chat.view.ivIdenticon import nl.tudelft.ipv8.util.toHex import nl.tudelft.trustchain.common.contacts.Contact import nl.tudelft.trustchain.common.util.getColorByHash -import nl.tudelft.trustchain.peerchat.ui.conversation.MessageAttachment +import nl.tudelft.trustchain.valuetransfer.util.MessageAttachment import nl.tudelft.trustchain.valuetransfer.R import nl.tudelft.trustchain.valuetransfer.util.generateIdenticon import java.text.SimpleDateFormat diff --git a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ChatMediaDetailAdapter.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ChatMediaDetailAdapter.kt index 821ed9ad6..ddf4e054a 100644 --- a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ChatMediaDetailAdapter.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ChatMediaDetailAdapter.kt @@ -11,7 +11,7 @@ import android.widget.TextView import androidx.viewpager.widget.PagerAdapter import com.bumptech.glide.Glide import com.jsibbold.zoomage.ZoomageView -import nl.tudelft.trustchain.peerchat.ui.conversation.MessageAttachment +import nl.tudelft.trustchain.valuetransfer.util.MessageAttachment import nl.tudelft.trustchain.valuetransfer.R import nl.tudelft.trustchain.valuetransfer.util.OnSwipeTouchListener import nl.tudelft.trustchain.valuetransfer.util.getFormattedSize diff --git a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ChatMediaItemRenderer.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ChatMediaItemRenderer.kt index 26e89c68a..c08af281e 100644 --- a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ChatMediaItemRenderer.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ChatMediaItemRenderer.kt @@ -8,7 +8,7 @@ import androidx.core.view.isVisible import com.bumptech.glide.Glide import com.mattskala.itemadapter.ItemLayoutRenderer import kotlinx.android.synthetic.main.item_contact_chat_media_gallery.view.* -import nl.tudelft.trustchain.peerchat.ui.conversation.MessageAttachment +import nl.tudelft.trustchain.valuetransfer.util.MessageAttachment import nl.tudelft.trustchain.valuetransfer.R class ChatMediaItemRenderer( diff --git a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ContactChatFragment.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ContactChatFragment.kt index 61a4cf2c4..5be21bfab 100644 --- a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ContactChatFragment.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ContactChatFragment.kt @@ -40,18 +40,18 @@ import nl.tudelft.ipv8.util.toHex import nl.tudelft.trustchain.common.contacts.Contact import nl.tudelft.trustchain.common.util.getColorByHash import nl.tudelft.trustchain.common.util.viewBinding -import nl.tudelft.trustchain.peerchat.db.PeerChatStore -import nl.tudelft.trustchain.peerchat.entity.ChatMessage -import nl.tudelft.trustchain.peerchat.ui.conversation.MessageAttachment -import nl.tudelft.trustchain.peerchat.util.saveFile +import nl.tudelft.trustchain.valuetransfer.util.PeerChatStore +import nl.tudelft.trustchain.valuetransfer.util.ChatMessage +import nl.tudelft.trustchain.valuetransfer.util.MessageAttachment +import nl.tudelft.trustchain.valuetransfer.util.saveFile import nl.tudelft.trustchain.valuetransfer.R import nl.tudelft.trustchain.valuetransfer.ui.VTFragment import nl.tudelft.trustchain.valuetransfer.ValueTransferMainActivity import nl.tudelft.trustchain.valuetransfer.databinding.FragmentContactsChatBinding import nl.tudelft.trustchain.valuetransfer.dialogs.* import nl.tudelft.trustchain.common.valuetransfer.entity.IdentityAttribute -import nl.tudelft.trustchain.peerchat.entity.ContactImage -import nl.tudelft.trustchain.peerchat.entity.ContactState +import nl.tudelft.trustchain.valuetransfer.util.ContactImage +import nl.tudelft.trustchain.valuetransfer.util.ContactState import nl.tudelft.trustchain.valuetransfer.ui.QRScanController import nl.tudelft.trustchain.valuetransfer.ui.settings.AppPreferences import nl.tudelft.trustchain.valuetransfer.util.* diff --git a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ContactChatItem.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ContactChatItem.kt index 2a28b5ae4..8760e374f 100644 --- a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ContactChatItem.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ContactChatItem.kt @@ -3,7 +3,7 @@ package nl.tudelft.trustchain.valuetransfer.ui.contacts import com.mattskala.itemadapter.Item import nl.tudelft.ipv8.attestation.trustchain.TrustChainBlock import nl.tudelft.ipv8.messaging.eva.TransferProgress -import nl.tudelft.trustchain.peerchat.entity.ChatMessage +import nl.tudelft.trustchain.valuetransfer.util.ChatMessage data class ContactChatItem( val chatMessage: ChatMessage, diff --git a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ContactChatItemRenderer.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ContactChatItemRenderer.kt index 809082c4b..a1e56ffba 100644 --- a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ContactChatItemRenderer.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ContactChatItemRenderer.kt @@ -15,12 +15,12 @@ import nl.tudelft.ipv8.messaging.eva.TransferState import nl.tudelft.ipv8.util.toHex import nl.tudelft.trustchain.common.contacts.Contact import nl.tudelft.trustchain.common.util.getColorByHash -import nl.tudelft.trustchain.peerchat.ui.conversation.MessageAttachment +import nl.tudelft.trustchain.valuetransfer.util.MessageAttachment import nl.tudelft.trustchain.valuetransfer.R import nl.tudelft.trustchain.valuetransfer.ValueTransferMainActivity import nl.tudelft.trustchain.common.valuetransfer.entity.IdentityAttribute import nl.tudelft.trustchain.common.valuetransfer.entity.TransferRequest -import nl.tudelft.trustchain.peerchat.community.PeerChatCommunity +import nl.tudelft.trustchain.valuetransfer.community.PeerChatCommunity import nl.tudelft.trustchain.valuetransfer.util.formatBalance import nl.tudelft.trustchain.valuetransfer.util.generateIdenticon import nl.tudelft.trustchain.valuetransfer.util.getColorIDFromThemeAttribute diff --git a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ContactsFragment.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ContactsFragment.kt index 62642c0a7..853751a20 100644 --- a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ContactsFragment.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/contacts/ContactsFragment.kt @@ -20,9 +20,9 @@ import nl.tudelft.ipv8.util.toHex import nl.tudelft.trustchain.common.contacts.Contact import nl.tudelft.trustchain.common.util.viewBinding import nl.tudelft.trustchain.common.valuetransfer.extensions.exitEnterView -import nl.tudelft.trustchain.peerchat.entity.ChatMessage -import nl.tudelft.trustchain.peerchat.entity.ContactImage -import nl.tudelft.trustchain.peerchat.entity.ContactState +import nl.tudelft.trustchain.valuetransfer.util.ChatMessage +import nl.tudelft.trustchain.valuetransfer.util.ContactImage +import nl.tudelft.trustchain.valuetransfer.util.ContactState import nl.tudelft.trustchain.valuetransfer.R import nl.tudelft.trustchain.valuetransfer.ui.VTFragment import nl.tudelft.trustchain.valuetransfer.ValueTransferMainActivity diff --git a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/exchange/ExchangeTransactionItemRenderer.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/exchange/ExchangeTransactionItemRenderer.kt index 08176c862..80963c333 100644 --- a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/exchange/ExchangeTransactionItemRenderer.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/exchange/ExchangeTransactionItemRenderer.kt @@ -8,7 +8,7 @@ import com.mattskala.itemadapter.ItemLayoutRenderer import nl.tudelft.ipv8.keyvault.PublicKey import nl.tudelft.trustchain.common.contacts.ContactStore import nl.tudelft.trustchain.common.eurotoken.TransactionRepository -import nl.tudelft.trustchain.peerchat.db.PeerChatStore +import nl.tudelft.trustchain.valuetransfer.util.PeerChatStore import nl.tudelft.trustchain.valuetransfer.R import nl.tudelft.trustchain.valuetransfer.ValueTransferMainActivity import nl.tudelft.trustchain.valuetransfer.util.formatBalance diff --git a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/settings/NotificationHandler.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/settings/NotificationHandler.kt index c9f8871b6..a96c4d538 100644 --- a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/settings/NotificationHandler.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/settings/NotificationHandler.kt @@ -21,9 +21,9 @@ import nl.tudelft.trustchain.common.eurotoken.TransactionRepository import nl.tudelft.trustchain.common.util.getColorByHash import nl.tudelft.trustchain.common.valuetransfer.extensions.setPadding import nl.tudelft.trustchain.common.valuetransfer.extensions.toSquare -import nl.tudelft.trustchain.peerchat.db.PeerChatStore -import nl.tudelft.trustchain.peerchat.entity.ChatMessage -import nl.tudelft.trustchain.peerchat.ui.conversation.MessageAttachment +import nl.tudelft.trustchain.valuetransfer.util.PeerChatStore +import nl.tudelft.trustchain.valuetransfer.util.ChatMessage +import nl.tudelft.trustchain.valuetransfer.util.MessageAttachment import nl.tudelft.trustchain.valuetransfer.R import nl.tudelft.trustchain.valuetransfer.ValueTransferMainActivity import nl.tudelft.trustchain.valuetransfer.ui.QRScanController diff --git a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/walletoverview/WalletOverviewFragment.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/walletoverview/WalletOverviewFragment.kt index 4eb64c526..d65107a21 100644 --- a/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/walletoverview/WalletOverviewFragment.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/ui/walletoverview/WalletOverviewFragment.kt @@ -21,9 +21,9 @@ import nl.tudelft.trustchain.common.contacts.Contact import nl.tudelft.trustchain.common.util.QRCodeUtils import nl.tudelft.trustchain.common.util.viewBinding import nl.tudelft.trustchain.common.valuetransfer.extensions.decodeImage -import nl.tudelft.trustchain.peerchat.entity.ChatMessage -import nl.tudelft.trustchain.peerchat.entity.ContactImage -import nl.tudelft.trustchain.peerchat.entity.ContactState +import nl.tudelft.trustchain.valuetransfer.util.ChatMessage +import nl.tudelft.trustchain.valuetransfer.util.ContactImage +import nl.tudelft.trustchain.valuetransfer.util.ContactState import nl.tudelft.trustchain.valuetransfer.R import nl.tudelft.trustchain.valuetransfer.ValueTransferMainActivity import nl.tudelft.trustchain.valuetransfer.databinding.FragmentWalletVtBinding diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/entity/ChatMessage.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/util/ChatMessage.kt similarity index 92% rename from peerchat/src/main/java/nl/tudelft/trustchain/peerchat/entity/ChatMessage.kt rename to valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/util/ChatMessage.kt index d2d394857..f58f39ce8 100644 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/entity/ChatMessage.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/util/ChatMessage.kt @@ -1,8 +1,8 @@ -package nl.tudelft.trustchain.peerchat.entity +package nl.tudelft.trustchain.valuetransfer.util import nl.tudelft.ipv8.keyvault.PublicKey import nl.tudelft.trustchain.common.valuetransfer.entity.IdentityInfo -import nl.tudelft.trustchain.peerchat.ui.conversation.MessageAttachment +import nl.tudelft.trustchain.valuetransfer.util.MessageAttachment import java.util.* data class ChatMessage( diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/entity/ContactImage.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/util/ContactImage.kt similarity index 97% rename from peerchat/src/main/java/nl/tudelft/trustchain/peerchat/entity/ContactImage.kt rename to valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/util/ContactImage.kt index 3e16d9a13..dc8872a41 100644 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/entity/ContactImage.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/util/ContactImage.kt @@ -1,4 +1,4 @@ -package nl.tudelft.trustchain.peerchat.entity +package nl.tudelft.trustchain.valuetransfer.util import android.graphics.Bitmap import nl.tudelft.ipv8.keyvault.PublicKey diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/entity/ContactState.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/util/ContactState.kt similarity index 91% rename from peerchat/src/main/java/nl/tudelft/trustchain/peerchat/entity/ContactState.kt rename to valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/util/ContactState.kt index 830ce6d1e..b1b2a3f48 100644 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/entity/ContactState.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/util/ContactState.kt @@ -1,4 +1,4 @@ -package nl.tudelft.trustchain.peerchat.entity +package nl.tudelft.trustchain.valuetransfer.util import nl.tudelft.ipv8.keyvault.PublicKey import nl.tudelft.trustchain.common.valuetransfer.entity.IdentityInfo diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/util/FileUtils.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/util/FileUtils.kt similarity index 84% rename from peerchat/src/main/java/nl/tudelft/trustchain/peerchat/util/FileUtils.kt rename to valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/util/FileUtils.kt index 960f4cbd0..b07e7697d 100644 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/util/FileUtils.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/util/FileUtils.kt @@ -1,10 +1,9 @@ -package nl.tudelft.trustchain.peerchat.util +package nl.tudelft.trustchain.valuetransfer.util import android.content.Context import android.net.Uri import nl.tudelft.ipv8.util.sha256 import nl.tudelft.ipv8.util.toHex -import nl.tudelft.trustchain.peerchat.ui.conversation.MessageAttachment import java.io.File import java.io.FileOutputStream diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/conversation/MessageAttachment.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/util/MessageAttachment.kt similarity index 96% rename from peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/conversation/MessageAttachment.kt rename to valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/util/MessageAttachment.kt index 49d448f40..76ae9d29f 100644 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/ui/conversation/MessageAttachment.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/util/MessageAttachment.kt @@ -1,4 +1,4 @@ -package nl.tudelft.trustchain.peerchat.ui.conversation +package nl.tudelft.trustchain.valuetransfer.util import android.content.Context import nl.tudelft.ipv8.util.toHex diff --git a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/db/PeerChatStore.kt b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/util/PeerChatStore.kt similarity index 98% rename from peerchat/src/main/java/nl/tudelft/trustchain/peerchat/db/PeerChatStore.kt rename to valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/util/PeerChatStore.kt index 196c3f8ff..1790f7249 100644 --- a/peerchat/src/main/java/nl/tudelft/trustchain/peerchat/db/PeerChatStore.kt +++ b/valuetransfer/src/main/java/nl/tudelft/trustchain/valuetransfer/util/PeerChatStore.kt @@ -1,4 +1,4 @@ -package nl.tudelft.trustchain.peerchat.db +package nl.tudelft.trustchain.valuetransfer.util import android.content.Context import android.graphics.BitmapFactory @@ -11,14 +11,10 @@ import kotlinx.coroutines.flow.combine import nl.tudelft.ipv8.keyvault.PublicKey import nl.tudelft.ipv8.keyvault.defaultCryptoProvider import nl.tudelft.ipv8.util.hexToBytes -import nl.tudelft.peerchat.sqldelight.Database +import nl.tudelft.valuetransfer.sqldelight.Database import nl.tudelft.trustchain.common.contacts.Contact import nl.tudelft.trustchain.common.contacts.ContactStore import nl.tudelft.trustchain.common.valuetransfer.entity.IdentityInfo -import nl.tudelft.trustchain.peerchat.entity.ChatMessage -import nl.tudelft.trustchain.peerchat.entity.ContactImage -import nl.tudelft.trustchain.peerchat.entity.ContactState -import nl.tudelft.trustchain.peerchat.ui.conversation.MessageAttachment import java.util.* class PeerChatStore(context: Context) { diff --git a/valuetransfer/src/main/res/values/styles.xml b/valuetransfer/src/main/res/values/styles.xml index a50d7a2aa..09d8f2b4e 100644 --- a/valuetransfer/src/main/res/values/styles.xml +++ b/valuetransfer/src/main/res/values/styles.xml @@ -1,6 +1,11 @@ + + // ACTION BAR TEXT STYLE