├── .gitignore ├── .idea ├── .name ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── misc.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── io │ │ └── fullsend │ │ └── tardigrade │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── io │ │ │ └── fullsend │ │ │ └── tardigrade │ │ │ ├── MainActivity.kt │ │ │ ├── adapter │ │ │ ├── BucketsAdapter.kt │ │ │ └── ObjectsAdapter.kt │ │ │ ├── fragment │ │ │ ├── BucketsFragment.kt │ │ │ ├── MainFragment.kt │ │ │ └── ObjectsFragment.kt │ │ │ └── viewmodel │ │ │ ├── BucketsViewModel.kt │ │ │ ├── MainViewModel.kt │ │ │ └── ObjectsViewModel.kt │ └── res │ │ ├── drawable-v24 │ │ ├── ic_launcher_foreground.xml │ │ └── tardigrade.png │ │ ├── drawable │ │ ├── ic_arrow_back_black_24dp.xml │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── bucket_item.xml │ │ ├── fragment_buckets.xml │ │ ├── fragment_main.xml │ │ ├── fragment_objects.xml │ │ └── object_item.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── navigation │ │ └── nav_graph.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── io │ └── fullsend │ └── tardigrade │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── tardigrade-demo.gif /.gitignore: -------------------------------------------------------------------------------- 1 | ## Default Android Studio gitignore Rules ## 2 | *.iml 3 | .gradle 4 | /local.properties 5 | /.idea/caches 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | /.idea/navEditor.xml 10 | /.idea/assetWizardSettings.xml 11 | .DS_Store 12 | /build 13 | /captures 14 | .externalNativeBuild 15 | .cxx 16 | 17 | ## Default GitHub gitignore Rules ## 18 | # Built application files 19 | *.apk 20 | *.aar 21 | *.ap_ 22 | *.aab 23 | 24 | # Files for the ART/Dalvik VM 25 | *.dex 26 | 27 | # Java class files 28 | *.class 29 | 30 | # Generated files 31 | bin/ 32 | gen/ 33 | out/ 34 | # Uncomment the following line in case you need and you don't have the release build type files in your app 35 | # release/ 36 | 37 | # Gradle files 38 | .gradle/ 39 | build/ 40 | 41 | # Local configuration file (sdk path, etc) 42 | local.properties 43 | 44 | # Proguard folder generated by Eclipse 45 | proguard/ 46 | 47 | # Log Files 48 | *.log 49 | 50 | # Android Studio Navigation editor temp files 51 | .navigation/ 52 | 53 | # Android Studio captures folder 54 | captures/ 55 | 56 | # IntelliJ 57 | *.iml 58 | .idea/workspace.xml 59 | .idea/tasks.xml 60 | .idea/gradle.xml 61 | .idea/assetWizardSettings.xml 62 | .idea/dictionaries 63 | .idea/libraries 64 | # Android Studio 3 in .gitignore file. 65 | .idea/caches 66 | .idea/modules.xml 67 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 68 | .idea/navEditor.xml 69 | 70 | # Keystore files 71 | # Uncomment the following lines if you do not want to check your keystore files in. 72 | #*.jks 73 | #*.keystore 74 | 75 | # External native build folder generated in Android Studio 2.2 and later 76 | .externalNativeBuild 77 | .cxx/ 78 | 79 | # Google Services (e.g. APIs or Firebase) 80 | # google-services.json 81 | 82 | # Freeline 83 | freeline.py 84 | freeline/ 85 | freeline_project_description.json 86 | 87 | # fastlane 88 | fastlane/report.xml 89 | fastlane/Preview.html 90 | fastlane/screenshots 91 | fastlane/test_output 92 | fastlane/readme.md 93 | 94 | # Version control 95 | vcs.xml 96 | 97 | # lint 98 | lint/intermediates/ 99 | lint/generated/ 100 | lint/outputs/ 101 | lint/tmp/ 102 | # lint/reports/ 103 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Tardigrade -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | xmlns:android 17 | 18 | ^$ 19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 | xmlns:.* 28 | 29 | ^$ 30 | 31 | 32 | BY_NAME 33 | 34 |
35 |
36 | 37 | 38 | 39 | .*:id 40 | 41 | http://schemas.android.com/apk/res/android 42 | 43 | 44 | 45 |
46 |
47 | 48 | 49 | 50 | .*:name 51 | 52 | http://schemas.android.com/apk/res/android 53 | 54 | 55 | 56 |
57 |
58 | 59 | 60 | 61 | name 62 | 63 | ^$ 64 | 65 | 66 | 67 |
68 |
69 | 70 | 71 | 72 | style 73 | 74 | ^$ 75 | 76 | 77 | 78 |
79 |
80 | 81 | 82 | 83 | .* 84 | 85 | ^$ 86 | 87 | 88 | BY_NAME 89 | 90 |
91 |
92 | 93 | 94 | 95 | .* 96 | 97 | http://schemas.android.com/apk/res/android 98 | 99 | 100 | ANDROID_ATTRIBUTE_ORDER 101 | 102 |
103 |
104 | 105 | 106 | 107 | .* 108 | 109 | .* 110 | 111 | 112 | BY_NAME 113 | 114 |
115 |
116 |
117 |
118 | 119 | 121 |
122 |
-------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tardigrade-Android 2 | This is a proof of concept app demonstrating some capabilities of [Tardigrade](https://tardigrade.io/)- the world's first S3-compatible SLA-backed decentralized cloud storage solution. Tardigrade is a product of [Storj Labs](https://storj.io/) which has built client libraries to allow Tardigrade access. This app uses the [uplink-android](https://github.com/storj/uplink-android) library but there are also libraries available in [Go](https://github.com/storj/uplink), [Python](https://pypi.org/project/storjPython/), [Swift](https://github.com/utropicmedia/storj-swift), and [Node.js](https://github.com/utropicmedia/storj-nodejs). 3 | 4 | ## Disclaimer 5 | This app is no way affiliated with Storj Labs. The Fullsend team built this as an opportunity to play with new tech and support an interesting project. This app only allows users to explore buckets and download files. It is very limited and is missing features like basic error handling. 6 | 7 | ## Demo 8 | ![](tardigrade-demo.gif) 9 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: "androidx.navigation.safeargs.kotlin" 5 | 6 | android { 7 | compileSdkVersion 29 8 | buildToolsVersion "29.0.2" 9 | 10 | compileOptions { 11 | sourceCompatibility JavaVersion.VERSION_1_8 12 | targetCompatibility JavaVersion.VERSION_1_8 13 | } 14 | 15 | kotlinOptions { 16 | jvmTarget = JavaVersion.VERSION_1_8.toString() 17 | } 18 | 19 | defaultConfig { 20 | applicationId "io.fullsend.tardigrade" 21 | minSdkVersion 29 22 | targetSdkVersion 29 23 | versionCode 1 24 | versionName "1.0" 25 | 26 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 27 | } 28 | 29 | buildTypes { 30 | release { 31 | minifyEnabled false 32 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 33 | } 34 | } 35 | 36 | } 37 | 38 | dependencies { 39 | implementation fileTree(dir: 'libs', include: ['*.jar']) 40 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 41 | implementation 'androidx.appcompat:appcompat:1.1.0' 42 | implementation 'androidx.core:core-ktx:1.2.0' 43 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 44 | implementation 'io.storj:libuplink-android:0.11.0' 45 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.5" 46 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5" 47 | 48 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0" 49 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' 50 | implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' 51 | implementation "androidx.fragment:fragment-ktx:1.2.3" 52 | 53 | implementation "androidx.navigation:navigation-fragment-ktx:2.2.1" 54 | implementation "androidx.navigation:navigation-ui-ktx:2.2.1" 55 | 56 | implementation 'com.google.android.material:material:1.1.0' 57 | 58 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 59 | testImplementation 'junit:junit:4.12' 60 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 61 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 62 | } 63 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/io/fullsend/tardigrade/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package io.fullsend.tardigrade 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("io.fullsend.tardigrade", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/io/fullsend/tardigrade/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package io.fullsend.tardigrade 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | 6 | class MainActivity : AppCompatActivity() { 7 | 8 | override fun onCreate(savedInstanceState: Bundle?) { 9 | super.onCreate(savedInstanceState) 10 | setContentView(R.layout.activity_main) 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/io/fullsend/tardigrade/adapter/BucketsAdapter.kt: -------------------------------------------------------------------------------- 1 | package io.fullsend.tardigrade.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.TextView 7 | import androidx.navigation.Navigation 8 | import androidx.recyclerview.widget.RecyclerView 9 | import io.fullsend.tardigrade.R 10 | import io.fullsend.tardigrade.fragment.BucketsFragmentArgs 11 | import io.fullsend.tardigrade.fragment.BucketsFragmentDirections 12 | import io.storj.BucketInfo 13 | 14 | 15 | class BucketsAdapter( 16 | private val dataset: ArrayList, 17 | private val args: BucketsFragmentArgs 18 | ) : 19 | RecyclerView.Adapter() { 20 | 21 | fun setDataset(newDataset: List) { 22 | dataset.clear() 23 | dataset.addAll(newDataset) 24 | notifyDataSetChanged() 25 | } 26 | 27 | open class BucketViewHolder(open var layout: View) : 28 | RecyclerView.ViewHolder(layout) 29 | 30 | class BucketItemViewHolder( 31 | override var layout: View, val bucketName: TextView 32 | ) : 33 | BucketViewHolder(layout) 34 | 35 | override fun onCreateViewHolder( 36 | parent: ViewGroup, 37 | viewType: Int 38 | ): BucketViewHolder { 39 | 40 | var rootView = LayoutInflater.from(parent.context).inflate(R.layout.bucket_item, parent, false) 41 | val textView = rootView.findViewById(R.id.bucketName) 42 | 43 | return BucketItemViewHolder( 44 | rootView, 45 | textView 46 | ) 47 | } 48 | 49 | override fun onBindViewHolder(holder: BucketViewHolder, position: Int) { 50 | 51 | val item = dataset[position] 52 | 53 | val bucketName = item.name 54 | val bucketItemView = holder as BucketItemViewHolder 55 | bucketItemView.bucketName.text = bucketName 56 | 57 | val action = 58 | BucketsFragmentDirections.actionBucketsFragmentToObjectsFragment(args.satelliteAddress, args.apiKey, 59 | args.passphrase, bucketName) 60 | 61 | holder.bucketName.setOnClickListener( 62 | Navigation.createNavigateOnClickListener(action) 63 | ) 64 | 65 | } 66 | 67 | override fun getItemCount() = dataset.size 68 | 69 | override fun getItemViewType(position: Int): Int { 70 | return 1 71 | } 72 | } -------------------------------------------------------------------------------- /app/src/main/java/io/fullsend/tardigrade/adapter/ObjectsAdapter.kt: -------------------------------------------------------------------------------- 1 | package io.fullsend.tardigrade.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import android.widget.TextView 7 | import androidx.recyclerview.widget.RecyclerView 8 | import io.fullsend.tardigrade.R 9 | import io.fullsend.tardigrade.fragment.ObjectsFragment 10 | import io.storj.ObjectInfo 11 | 12 | 13 | class ObjectsAdapter( 14 | private val dataset: ArrayList, 15 | private val fragment: ObjectsFragment 16 | ) : 17 | RecyclerView.Adapter() { 18 | 19 | fun setDataset(newDataset: List) { 20 | dataset.clear() 21 | dataset.addAll(newDataset) 22 | notifyDataSetChanged() 23 | } 24 | 25 | open class ObjectViewHolder(open var layout: View) : 26 | RecyclerView.ViewHolder(layout) 27 | 28 | class ObjectItemViewHolder( 29 | override var layout: View, val objectName: TextView 30 | ) : 31 | ObjectsAdapter.ObjectViewHolder(layout) 32 | 33 | override fun onCreateViewHolder( 34 | parent: ViewGroup, 35 | viewType: Int 36 | ): ObjectViewHolder { 37 | 38 | var rootView = 39 | LayoutInflater.from(parent.context).inflate(R.layout.object_item, parent, false) 40 | val textView = rootView.findViewById(R.id.objectName) 41 | 42 | return ObjectItemViewHolder( 43 | rootView, 44 | textView 45 | ) 46 | } 47 | 48 | override fun onBindViewHolder(holder: ObjectViewHolder, position: Int) { 49 | 50 | val item = dataset[position] 51 | 52 | val objectName = item.path 53 | val objectItemView = holder as ObjectItemViewHolder 54 | objectItemView.objectName.text = objectName 55 | 56 | holder.objectName.setOnClickListener{ 57 | fragment.downloadObject(objectName) 58 | } 59 | } 60 | 61 | override fun getItemCount() = dataset.size 62 | 63 | override fun getItemViewType(position: Int): Int { 64 | return 1 65 | } 66 | } -------------------------------------------------------------------------------- /app/src/main/java/io/fullsend/tardigrade/fragment/BucketsFragment.kt: -------------------------------------------------------------------------------- 1 | package io.fullsend.tardigrade.fragment 2 | 3 | import android.os.Bundle 4 | import android.util.Log 5 | import androidx.fragment.app.Fragment 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import androidx.fragment.app.viewModels 10 | import androidx.lifecycle.Observer 11 | import androidx.navigation.fragment.findNavController 12 | import androidx.navigation.fragment.navArgs 13 | import androidx.recyclerview.widget.LinearLayoutManager 14 | import androidx.recyclerview.widget.RecyclerView 15 | import io.fullsend.tardigrade.viewmodel.BucketsViewModel 16 | import io.fullsend.tardigrade.R 17 | import io.fullsend.tardigrade.adapter.BucketsAdapter 18 | import kotlinx.android.synthetic.main.fragment_buckets.* 19 | 20 | class BucketsFragment : Fragment() { 21 | 22 | val args: BucketsFragmentArgs by navArgs() 23 | private lateinit var recyclerView: RecyclerView 24 | private lateinit var viewAdapter: BucketsAdapter 25 | private lateinit var viewManager: RecyclerView.LayoutManager 26 | 27 | override fun onCreate(savedInstanceState: Bundle?) { 28 | super.onCreate(savedInstanceState) 29 | val viewModel: BucketsViewModel by viewModels() 30 | 31 | val satelliteAddress = args.satelliteAddress 32 | val apiKey = args.apiKey 33 | val passphrase = args.passphrase 34 | 35 | viewModel.satelliteAddress = satelliteAddress 36 | viewModel.apiKeyString = apiKey 37 | viewModel.passphrase = passphrase 38 | } 39 | 40 | override fun onCreateView( 41 | inflater: LayoutInflater, container: ViewGroup?, 42 | savedInstanceState: Bundle? 43 | ): View? { 44 | return inflater.inflate(R.layout.fragment_buckets, container, false) 45 | } 46 | 47 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 48 | super.onViewCreated(view, savedInstanceState) 49 | 50 | val viewModel: BucketsViewModel by viewModels() 51 | 52 | progressBar.visibility = View.VISIBLE 53 | viewModel.getBucketList() 54 | viewModel.bucketNames.observe(viewLifecycleOwner, Observer { list -> 55 | list.forEach { 56 | Log.d("Bucket Name: ", it.name) 57 | } 58 | viewAdapter.setDataset(list) 59 | progressBar.visibility = View.INVISIBLE 60 | }) 61 | 62 | viewManager = LinearLayoutManager(context) 63 | viewAdapter = 64 | BucketsAdapter(arrayListOf(), args) 65 | 66 | recyclerView = view.findViewById(R.id.object_list).apply { 67 | setHasFixedSize(true) 68 | layoutManager = viewManager 69 | adapter = viewAdapter 70 | } 71 | 72 | back.setOnClickListener { 73 | findNavController().popBackStack(R.id.main_fragment, false) 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/io/fullsend/tardigrade/fragment/MainFragment.kt: -------------------------------------------------------------------------------- 1 | package io.fullsend.tardigrade.fragment 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import android.os.Bundle 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import androidx.core.widget.doOnTextChanged 10 | import androidx.fragment.app.Fragment 11 | import androidx.fragment.app.viewModels 12 | import androidx.lifecycle.Observer 13 | import androidx.navigation.findNavController 14 | import io.fullsend.tardigrade.viewmodel.MainViewModel 15 | import io.fullsend.tardigrade.R 16 | import kotlinx.android.synthetic.main.fragment_main.* 17 | 18 | 19 | class MainFragment : Fragment() { 20 | 21 | private var documentationUri = "https://documentation.tardigrade.io/getting-started/uploading-your-first-object/prerequisites" 22 | 23 | override fun onCreateView( 24 | inflater: LayoutInflater, 25 | container: ViewGroup?, 26 | savedInstanceState: Bundle? 27 | ): View? { 28 | return inflater.inflate(R.layout.fragment_main, container, false) 29 | } 30 | 31 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 32 | super.onViewCreated(view, savedInstanceState) 33 | val viewModel: MainViewModel by viewModels() 34 | 35 | viewModel.satelliteAddress = editTextSatelliteAddress.text.toString() 36 | viewModel.apiKeyString = editTextApiPhrase.text.toString() 37 | viewModel.passphrase = editTextPassphrase.text.toString() 38 | 39 | 40 | createAccount.setOnClickListener { 41 | val browserIntent = 42 | Intent( 43 | Intent.ACTION_VIEW, 44 | Uri.parse(documentationUri) 45 | ) 46 | startActivity(browserIntent) 47 | } 48 | 49 | editTextSatelliteAddress.doOnTextChanged { text, start, count, after -> 50 | viewModel.satelliteAddress = text.toString() 51 | } 52 | 53 | editTextApiPhrase.doOnTextChanged { text, start, count, after -> 54 | viewModel.apiKeyString = text.toString() 55 | } 56 | 57 | editTextPassphrase.doOnTextChanged { text, start, count, after -> 58 | viewModel.passphrase = text.toString() 59 | } 60 | 61 | showBucketsButton.setOnClickListener { 62 | showBucketsButton.visibility = View.INVISIBLE 63 | mainProgress.visibility = View.VISIBLE 64 | viewModel.getProject() 65 | viewModel.project.observe(viewLifecycleOwner, Observer { project -> 66 | val action = 67 | MainFragmentDirections.actionMainFragmentToBucketsFragment( 68 | viewModel.satelliteAddress, 69 | viewModel.apiKeyString, 70 | viewModel.passphrase 71 | ) 72 | it.findNavController().navigate(action) 73 | mainProgress.visibility = View.INVISIBLE 74 | }) 75 | } 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /app/src/main/java/io/fullsend/tardigrade/fragment/ObjectsFragment.kt: -------------------------------------------------------------------------------- 1 | package io.fullsend.tardigrade.fragment 2 | 3 | import android.app.DownloadManager 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.widget.ProgressBar 10 | import androidx.fragment.app.Fragment 11 | import androidx.fragment.app.viewModels 12 | import androidx.lifecycle.Observer 13 | import androidx.navigation.fragment.findNavController 14 | import androidx.navigation.fragment.navArgs 15 | import androidx.recyclerview.widget.LinearLayoutManager 16 | import androidx.recyclerview.widget.RecyclerView 17 | import com.google.android.material.snackbar.Snackbar 18 | import io.fullsend.tardigrade.R 19 | import io.fullsend.tardigrade.adapter.ObjectsAdapter 20 | import io.fullsend.tardigrade.viewmodel.ObjectsViewModel 21 | import kotlinx.android.synthetic.main.fragment_objects.* 22 | 23 | 24 | class ObjectsFragment : Fragment() { 25 | 26 | private val args: ObjectsFragmentArgs by navArgs() 27 | private lateinit var recyclerView: RecyclerView 28 | private lateinit var viewAdapter: ObjectsAdapter 29 | private lateinit var viewManager: RecyclerView.LayoutManager 30 | private lateinit var parentLayout: View 31 | private lateinit var progress: ProgressBar 32 | 33 | override fun onCreate(savedInstanceState: Bundle?) { 34 | super.onCreate(savedInstanceState) 35 | val viewModel: ObjectsViewModel by viewModels() 36 | 37 | val satelliteAddress = args.satelliteAddress 38 | val apiKey = args.apiKey 39 | val passphrase = args.passphrase 40 | val bucketName = args.bucketName 41 | 42 | viewModel.satelliteAddress = satelliteAddress 43 | viewModel.apiKeyString = apiKey 44 | viewModel.passphrase = passphrase 45 | viewModel.bucketName = bucketName 46 | 47 | } 48 | 49 | override fun onCreateView( 50 | inflater: LayoutInflater, container: ViewGroup?, 51 | savedInstanceState: Bundle? 52 | ): View? { 53 | return inflater.inflate(R.layout.fragment_buckets, container, false) 54 | } 55 | 56 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 57 | super.onViewCreated(view, savedInstanceState) 58 | 59 | val viewModel: ObjectsViewModel by viewModels() 60 | parentLayout = view 61 | progress = progressBar 62 | 63 | progress.visibility = View.VISIBLE 64 | viewModel.getBucketObjectsList() 65 | viewModel.bucketObjects.observe(viewLifecycleOwner, Observer { list -> 66 | viewAdapter.setDataset(list) 67 | progress.visibility = View.INVISIBLE 68 | }) 69 | 70 | viewManager = LinearLayoutManager(context) 71 | viewAdapter = 72 | ObjectsAdapter(arrayListOf(), this) 73 | 74 | recyclerView = view.findViewById(R.id.object_list).apply { 75 | setHasFixedSize(true) 76 | layoutManager = viewManager 77 | adapter = viewAdapter 78 | 79 | } 80 | 81 | bucketName.text = args.bucketName 82 | 83 | back.setOnClickListener { 84 | findNavController().popBackStack(R.id.bucketsFragment, false) 85 | } 86 | } 87 | 88 | fun downloadObject(objectName: String) { 89 | val viewModel: ObjectsViewModel by viewModels() 90 | 91 | context?.let { 92 | progress.visibility = View.VISIBLE 93 | viewModel.downloadObject(objectName) 94 | viewModel.bucketFile.observe(viewLifecycleOwner, Observer { objectName -> 95 | progress.visibility = View.INVISIBLE 96 | showSnackbar(objectName) 97 | }) 98 | } 99 | } 100 | 101 | private fun showSnackbar(fileName: String) { 102 | Snackbar.make(parentLayout, "$fileName downloaded!", Snackbar.LENGTH_LONG) 103 | .setAction("Show downloads") { 104 | startActivity(Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)); 105 | } 106 | .show() 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/io/fullsend/tardigrade/viewmodel/BucketsViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.fullsend.tardigrade.viewmodel 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.liveData 7 | import io.storj.* 8 | import kotlinx.coroutines.Dispatchers 9 | 10 | class BucketsViewModel : ViewModel() { 11 | var satelliteAddress = String() 12 | var apiKeyString = String() 13 | var passphrase = String() 14 | 15 | var bucketNames: LiveData> = MutableLiveData() 16 | 17 | fun getBucketList() { 18 | val apiKey = ApiKey.parse(apiKeyString) 19 | bucketNames = liveData(Dispatchers.IO) { 20 | Uplink().use { uplink -> 21 | uplink.openProject(satelliteAddress, apiKey).use { project -> 22 | val saltedKey: Key = Key.getSaltedKeyFromPassphrase(project, passphrase) 23 | val access = EncryptionAccess(saltedKey) 24 | val scope = Scope(satelliteAddress, apiKey, access) 25 | 26 | val project = uplink.openProject(scope) 27 | val buckets: Iterable = project.listBuckets() 28 | val bucketList = buckets.toList() 29 | emit(bucketList) 30 | } 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/io/fullsend/tardigrade/viewmodel/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.fullsend.tardigrade.viewmodel 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.liveData 7 | import io.storj.* 8 | import kotlinx.coroutines.Dispatchers 9 | 10 | class MainViewModel : ViewModel() { 11 | var satelliteAddress = String() 12 | var apiKeyString = String() 13 | var passphrase = String() 14 | 15 | var project: LiveData = MutableLiveData() 16 | 17 | fun getProject() { 18 | val apiKey = ApiKey.parse(apiKeyString) 19 | project = liveData(Dispatchers.IO) { 20 | Uplink().use { uplink -> 21 | uplink.openProject(satelliteAddress, apiKey).use { project -> 22 | val saltedKey: Key = Key.getSaltedKeyFromPassphrase(project, passphrase) 23 | val access = EncryptionAccess(saltedKey) 24 | val scope = Scope(satelliteAddress, apiKey, access) 25 | val project = uplink.openProject(scope) 26 | emit(project) 27 | } 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/io/fullsend/tardigrade/viewmodel/ObjectsViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.fullsend.tardigrade.viewmodel 2 | 3 | import android.app.Application 4 | import android.content.ContentValues 5 | import android.provider.MediaStore 6 | import android.webkit.MimeTypeMap 7 | import androidx.lifecycle.AndroidViewModel 8 | import androidx.lifecycle.LiveData 9 | import androidx.lifecycle.MutableLiveData 10 | import androidx.lifecycle.liveData 11 | import io.storj.* 12 | import kotlinx.coroutines.Dispatchers 13 | 14 | 15 | class ObjectsViewModel(application: Application) : AndroidViewModel(application) { 16 | var satelliteAddress = String() 17 | var apiKeyString = String() 18 | var passphrase = String() 19 | var bucketName = String() 20 | 21 | var bucketObjects: LiveData> = MutableLiveData() 22 | var bucketFile: LiveData = MutableLiveData() 23 | 24 | fun getBucketObjectsList() { 25 | val apiKey = ApiKey.parse(apiKeyString) 26 | bucketObjects = liveData(Dispatchers.IO) { 27 | Uplink().use { uplink -> 28 | uplink.openProject(satelliteAddress, apiKey).use { project -> 29 | val saltedKey: Key = Key.getSaltedKeyFromPassphrase(project, passphrase) 30 | val access = EncryptionAccess(saltedKey) 31 | val scope = Scope(satelliteAddress, apiKey, access) 32 | 33 | val project = uplink.openProject(scope) 34 | val bucket = project.openBucket(bucketName, scope) 35 | val bucketObjects: Iterable = bucket.listObjects() 36 | val bucketObjectsList = bucketObjects.toList() 37 | emit(bucketObjectsList) 38 | } 39 | } 40 | } 41 | } 42 | 43 | fun downloadObject(objectName: String) { 44 | val apiKey = ApiKey.parse(apiKeyString) 45 | bucketFile = liveData(Dispatchers.IO) { 46 | Uplink().use { uplink -> 47 | uplink.openProject(satelliteAddress, apiKey).use { project -> 48 | val saltedKey: Key = Key.getSaltedKeyFromPassphrase(project, passphrase) 49 | val access = EncryptionAccess(saltedKey) 50 | val scope = Scope(satelliteAddress, apiKey, access) 51 | 52 | val project = uplink.openProject(scope) 53 | val bucket = project.openBucket(bucketName, scope) 54 | 55 | val context = getApplication() 56 | val resolver = context.contentResolver 57 | val contentValues = ContentValues().apply { 58 | put(MediaStore.DownloadColumns.DISPLAY_NAME, objectName) 59 | put(MediaStore.DownloadColumns.MIME_TYPE, getMimeType(objectName)) 60 | } 61 | 62 | val uri = resolver?.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues) 63 | 64 | uri?.let { 65 | resolver.openOutputStream(uri).use { 66 | bucket.downloadObject(objectName, it) 67 | } 68 | } 69 | 70 | emit(objectName) 71 | } 72 | } 73 | } 74 | } 75 | 76 | fun getMimeType(url: String?): String? { 77 | var type: String? = null 78 | val extension = MimeTypeMap.getFileExtensionFromUrl(url) 79 | if (extension != null) { 80 | type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) 81 | } 82 | return type 83 | } 84 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/tardigrade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fullsend-solutions/Tardigrade-Android/8759cfb94a01ca67fb6fc81905be81570fd8114c/app/src/main/res/drawable-v24/tardigrade.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_back_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/bucket_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_buckets.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | 32 | 33 | 42 | 43 | 53 | 54 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 14 | 25 | 26 | 33 | 34 | 35 | 47 | 48 | 55 | 56 | 57 | 69 | 70 | 77 | 78 | 79 | 90 | 91 | 101 | 102 |