├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── code-quality.md │ ├── feature-request.md │ └── ui-update.md └── workflows │ ├── build.yml │ └── qa-release.yml ├── .gitignore ├── .idea ├── .name ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── jarRepositories.xml ├── misc.xml └── vcs.xml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── libs │ └── zkma-3.4.6.aar ├── proguard-rules.pro ├── schemas │ └── io.numbersprotocol.starlingcapture.data.AppDataBase │ │ └── 1.json └── src │ ├── androidTest │ └── java │ │ └── io │ │ └── numbersprotocol │ │ └── starlingcapture │ │ └── ExampleInstrumentedTest.kt │ ├── internal │ ├── AndroidManifest.xml │ ├── google-services.json │ ├── java │ │ └── io │ │ │ └── numbersprotocol │ │ │ └── starlingcapture │ │ │ ├── di │ │ │ └── VariantModule.kt │ │ │ └── publisher │ │ │ ├── PublisherConfigs.kt │ │ │ └── numbers_storage │ │ │ ├── NumbersStorageApi.kt │ │ │ ├── NumbersStoragePublisher.kt │ │ │ ├── NumbersStoragePublisherConfig.kt │ │ │ ├── NumbersStoragePublisherFragment.kt │ │ │ ├── NumbersStoragePublisherViewModel.kt │ │ │ └── sign_up │ │ │ ├── NumbersStoragePublisherSignUpFragment.kt │ │ │ └── NumbersStoragePublisherSignUpViewModel.kt │ └── res │ │ ├── drawable │ │ ├── ic_email.xml │ │ ├── ic_n.xml │ │ ├── ic_person.xml │ │ ├── ic_usc_shoah_foundation.xml │ │ └── splash_background.xml │ │ ├── layout │ │ ├── fragment_numbers_storage_publisher_login.xml │ │ └── fragment_numbers_storage_publisher_signup.xml │ │ ├── navigation │ │ └── publisher.xml │ │ ├── values-b+zh+TW │ │ └── strings.xml │ │ └── values │ │ └── strings.xml │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── io │ │ │ └── numbersprotocol │ │ │ └── starlingcapture │ │ │ ├── BaseActivity.kt │ │ │ ├── BaseApplication.kt │ │ │ ├── collector │ │ │ ├── CollectWorker.kt │ │ │ ├── InformationAndSignatureProvider.kt │ │ │ ├── InformationProvider.kt │ │ │ ├── ProofCollector.kt │ │ │ ├── SignatureProvider.kt │ │ │ ├── android_open_ssl │ │ │ │ └── AndroidOpenSslSignatureProvider.kt │ │ │ ├── infosnapshot │ │ │ │ ├── InfoSnapshotConfig.kt │ │ │ │ └── InfoSnapshotProvider.kt │ │ │ ├── proofmode │ │ │ │ ├── ProofModeConfig.kt │ │ │ │ └── ProofModeProvider.kt │ │ │ └── zion │ │ │ │ ├── SessionSignature.kt │ │ │ │ ├── ZionApi.kt │ │ │ │ └── ZionSessionSignatureProvider.kt │ │ │ ├── data │ │ │ ├── AppDataBase.kt │ │ │ ├── caption │ │ │ │ ├── Caption.kt │ │ │ │ ├── CaptionDao.kt │ │ │ │ └── CaptionRepository.kt │ │ │ ├── information │ │ │ │ ├── Information.kt │ │ │ │ ├── InformationDao.kt │ │ │ │ └── InformationRepository.kt │ │ │ ├── preference │ │ │ │ └── PreferenceRepository.kt │ │ │ ├── proof │ │ │ │ ├── Proof.kt │ │ │ │ ├── ProofDao.kt │ │ │ │ └── ProofRepository.kt │ │ │ ├── publish_history │ │ │ │ ├── PublishHistory.kt │ │ │ │ ├── PublishHistoryDao.kt │ │ │ │ └── PublishHistoryRepository.kt │ │ │ ├── serialization │ │ │ │ ├── SaveProofRelatedDataWorker.kt │ │ │ │ ├── Serialization.kt │ │ │ │ └── SortedProofInformation.kt │ │ │ └── signature │ │ │ │ ├── Signature.kt │ │ │ │ ├── SignatureDao.kt │ │ │ │ └── SignatureRepository.kt │ │ │ ├── di │ │ │ └── MainModule.kt │ │ │ ├── feature │ │ │ ├── audio │ │ │ │ ├── AudioFragment.kt │ │ │ │ └── AudioViewModel.kt │ │ │ ├── camera │ │ │ │ ├── CameraFragment.kt │ │ │ │ └── CameraViewModel.kt │ │ │ ├── ccapi │ │ │ │ ├── CcapiFragment.kt │ │ │ │ └── CcapiViewModel.kt │ │ │ ├── information │ │ │ │ ├── InformationFragment.kt │ │ │ │ ├── InformationViewItem.kt │ │ │ │ └── InformationViewModel.kt │ │ │ ├── information_provider_config │ │ │ │ ├── InformationProviderConfigFragment.kt │ │ │ │ └── InformationProviderConfigViewModel.kt │ │ │ ├── proof │ │ │ │ ├── InformationProviderAdapter.kt │ │ │ │ ├── ProofFragment.kt │ │ │ │ ├── ProofViewModel.kt │ │ │ │ └── SignatureAdapter.kt │ │ │ ├── publisher_config │ │ │ │ ├── PublisherConfigAdapter.kt │ │ │ │ ├── PublisherConfigFragment.kt │ │ │ │ └── PublisherConfigViewModel.kt │ │ │ ├── setting │ │ │ │ ├── PreferenceFragment.kt │ │ │ │ └── SettingFragment.kt │ │ │ ├── storage │ │ │ │ ├── StorageAdapter.kt │ │ │ │ ├── StorageFragment.kt │ │ │ │ └── StorageViewModel.kt │ │ │ └── zion │ │ │ │ ├── ZionFragment.kt │ │ │ │ └── ZionViewModel.kt │ │ │ ├── publisher │ │ │ ├── ProofPublisher.kt │ │ │ ├── PublisherConfig.kt │ │ │ └── PublisherManager.kt │ │ │ ├── source │ │ │ └── canon │ │ │ │ ├── CanonCameraControlApi.kt │ │ │ │ ├── CanonCameraControlProvider.kt │ │ │ │ └── CanonCameraControlService.kt │ │ │ └── util │ │ │ ├── BindingAdapter.kt │ │ │ ├── Bitmap.kt │ │ │ ├── Color.kt │ │ │ ├── Crypto.kt │ │ │ ├── Encoding.kt │ │ │ ├── Event.kt │ │ │ ├── File.kt │ │ │ ├── JsonAdapter.kt │ │ │ ├── LayoutFullScreen.kt │ │ │ ├── MimeType.kt │ │ │ ├── Navigation.kt │ │ │ ├── NotificationUtil.kt │ │ │ ├── RecyclerView.kt │ │ │ ├── SharedPreference.kt │ │ │ ├── Snackbar.kt │ │ │ ├── Validator.kt │ │ │ └── ViewPager.kt │ └── res │ │ ├── drawable-hdpi │ │ └── ic_launcher_foreground.png │ │ ├── drawable-mdpi │ │ └── ic_launcher_foreground.png │ │ ├── drawable-nodpi │ │ ├── logo_capture.png │ │ ├── slate.png │ │ └── sound_wave.png │ │ ├── drawable-xhdpi │ │ └── ic_launcher_foreground.png │ │ ├── drawable-xxhdpi │ │ └── ic_launcher_foreground.png │ │ ├── drawable-xxxhdpi │ │ └── ic_launcher_foreground.png │ │ ├── drawable │ │ ├── empty_divider.xml │ │ ├── ic_add.xml │ │ ├── ic_arrow_back.xml │ │ ├── ic_broken_image.xml │ │ ├── ic_capture.xml │ │ ├── ic_close.xml │ │ ├── ic_delete.xml │ │ ├── ic_device_information.xml │ │ ├── ic_done.xml │ │ ├── ic_edit.xml │ │ ├── ic_error.xml │ │ ├── ic_fiber_manual_record.xml │ │ ├── ic_fingerprint.xml │ │ ├── ic_info.xml │ │ ├── ic_key.xml │ │ ├── ic_language.xml │ │ ├── ic_linked_camera.xml │ │ ├── ic_location.xml │ │ ├── ic_lock_open.xml │ │ ├── ic_microphone.xml │ │ ├── ic_moon.xml │ │ ├── ic_numbers.xml │ │ ├── ic_photo_camera.xml │ │ ├── ic_power_settings.xml │ │ ├── ic_publish.xml │ │ ├── ic_recovery.xml │ │ ├── ic_refresh.xml │ │ ├── ic_save_alt.xml │ │ ├── ic_select_all.xml │ │ ├── ic_settings.xml │ │ ├── ic_settings_input_antenna.xml │ │ ├── ic_stop.xml │ │ ├── ic_timer.xml │ │ ├── ic_toggle_off.xml │ │ ├── ic_toggle_on.xml │ │ ├── ic_videocam.xml │ │ ├── ic_zion.xml │ │ └── splash_background.xml │ │ ├── font │ │ └── dseg7classic.ttf │ │ ├── layout │ │ ├── activity_base.xml │ │ ├── fragment_audio.xml │ │ ├── fragment_camera.xml │ │ ├── fragment_ccapi.xml │ │ ├── fragment_information.xml │ │ ├── fragment_information_provider_config.xml │ │ ├── fragment_proof.xml │ │ ├── fragment_publisher_config.xml │ │ ├── fragment_setting.xml │ │ ├── fragment_storage.xml │ │ ├── fragment_zion.xml │ │ ├── item_information.xml │ │ ├── item_information_provider.xml │ │ ├── item_information_type.xml │ │ ├── item_proof.xml │ │ ├── item_publisher.xml │ │ ├── item_signature.xml │ │ ├── item_simple_information.xml │ │ └── menu_item_switch.xml │ │ ├── menu │ │ ├── ccapi.xml │ │ ├── proof.xml │ │ ├── storage_action_mode.xml │ │ ├── storage_option.xml │ │ └── zion.xml │ │ ├── mipmap-anydpi │ │ └── ic_launcher.xml │ │ ├── navigation │ │ └── main.xml │ │ ├── values-b+zh+TW │ │ └── strings.xml │ │ ├── values-night │ │ └── themes.xml │ │ ├── values │ │ ├── arrays.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── keys.xml │ │ ├── shape.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ ├── themes.xml │ │ └── type.xml │ │ └── xml │ │ ├── file_paths.xml │ │ └── preferences.xml │ ├── master │ ├── java │ │ └── io │ │ │ └── numbersprotocol │ │ │ └── starlingcapture │ │ │ ├── di │ │ │ └── VariantModule.kt │ │ │ └── publisher │ │ │ ├── PublisherConfigs.kt │ │ │ └── sample │ │ │ ├── SamplePublisher.kt │ │ │ ├── SamplePublisherConfig.kt │ │ │ ├── SamplePublisherFragment.kt │ │ │ └── SamplePublisherViewModel.kt │ └── res │ │ ├── layout │ │ └── fragment_sample_publisher.xml │ │ ├── menu │ │ └── sample_publisher.xml │ │ ├── navigation │ │ └── publisher.xml │ │ └── values │ │ └── strings.xml │ ├── starling │ ├── AndroidManifest.xml │ ├── java │ │ └── org │ │ │ └── starlinglab │ │ │ └── starlingcapture │ │ │ ├── di │ │ │ └── VariantModule.kt │ │ │ └── publisher │ │ │ ├── PublisherConfigs.kt │ │ │ └── starling_integrity │ │ │ ├── StarlingIntegrityApi.kt │ │ │ ├── StarlingIntegrityPublisher.kt │ │ │ ├── StarlingIntegrityPublisherConfig.kt │ │ │ ├── StarlingIntegrityPublisherFragment.kt │ │ │ └── StarlingIntegrityPublisherViewModel.kt │ └── res │ │ ├── drawable-nodpi │ │ ├── icon_starling.png │ │ └── logo_starling.png │ │ ├── drawable │ │ ├── ic_starling.xml │ │ └── splash_background.xml │ │ ├── layout │ │ └── fragment_starling_integrity_publisher_login.xml │ │ ├── navigation │ │ └── publisher.xml │ │ └── values │ │ └── string.xml │ ├── test │ └── java │ │ └── io │ │ └── numbersprotocol │ │ └── starlingcapture │ │ ├── ExampleUnitTest.kt │ │ ├── data │ │ └── serialization │ │ │ └── SortedProofInformationTest.kt │ │ ├── source │ │ └── canon │ │ │ └── CanonCameraControlApiTest.kt │ │ └── util │ │ ├── EncodingTest.kt │ │ └── ValidatorTest.kt │ └── testInternal │ └── java │ └── io │ └── numbersprotocol │ └── starlingcapture │ └── publisher │ └── numbers_storage │ └── NumbersStorageApiTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── util └── verification ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── poetry.lock ├── poetry.toml ├── pylintrc ├── pyproject.toml ├── starling_capture_verifier.py ├── tests ├── __init__.py ├── assets │ ├── README.md │ ├── zion-classic │ │ └── ede8c1b820a19e0a438ce28873501fafd33886a21cf4e3c1ffea24f523c857c4 │ │ │ ├── caption.txt │ │ │ ├── ede8c1b820a19e0a438ce28873501fafd33886a21cf4e3c1ffea24f523c857c4.jpg │ │ │ ├── information.json │ │ │ └── signature.json │ ├── zion-session-classic │ │ └── b94c5af28e3aaba7578f6737aeab4d2eae4e08a2e7a52a1d55991d92b9552225 │ │ │ ├── b94c5af28e3aaba7578f6737aeab4d2eae4e08a2e7a52a1d55991d92b9552225.jpg │ │ │ ├── caption.txt │ │ │ ├── information.json │ │ │ └── signature.json │ ├── zion-session │ │ └── 855ba1f6548f710fe3e67f9bfc943c4483bd50a432480ce23061af96d672fd3b │ │ │ ├── 855ba1f6548f710fe3e67f9bfc943c4483bd50a432480ce23061af96d672fd3b.jpg │ │ │ ├── caption.txt │ │ │ ├── information.json │ │ │ └── signature.json │ └── zion │ │ └── 9f9af13673a0ea1e608fdd4c6ce6c746dd03dd58d184705ea7d1e1ef175e5640 │ │ ├── 9f9af13673a0ea1e608fdd4c6ce6c746dd03dd58d184705ea7d1e1ef175e5640.jpg │ │ ├── caption.txt │ │ ├── information.json │ │ └── signature.json └── test_verification.py ├── verification ├── __init__.py └── verification.py └── verify.sh /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Description 11 | 12 | A clear and concise description of what the bug is. 13 | 14 | ![bugs found](https://media.giphy.com/media/UAUtB4Oi9U4EM/giphy.gif) 15 | 16 | ## Steps to Reproduce 17 | 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | ## Expected Behavior 24 | 25 | * Expected: A clear and concise description of what you expected to happen. 26 | * Actual: A clear and concise description of what actually happened. 27 | 28 | ## Logs 29 | 30 | If applicable, add logs or screenshots to help explain your problem. 31 | 32 | ## Environment 33 | 34 | * Version: _e.g. 1.3.0, beta2, etc._ 35 | * OS: _e.g. Ubuntu 20.04, Android 10, iOS 13, etc._ 36 | * Device: _e.g. HTC Exodus 1_ -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/code-quality.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Code Quality 3 | about: Improve code quality or project architecture 4 | title: '' 5 | labels: code 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Suggestion 11 | 12 | The content of the suggestion. 13 | 14 | ![write codes](https://media.giphy.com/media/LmNwrBhejkK9EFP504/giphy.gif) -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Concept 11 | 12 | If we provide \[feature] 13 | 14 | ## Reason/Hypothesis 15 | 16 | We can help \[who] to \[benefit] or solve \[problem] because we know \[who] are \[faced about our target]. 17 | 18 | ## More Description (if any) 19 | 20 | Put more description here to support the concept. 21 | 22 | ## Suggested Implementation (if any) 23 | 24 | Put some suggested implementation here if you already have some ideas in mind. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ui-update.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: UI Update 3 | about: Create a new UI update 4 | title: '' 5 | labels: uiux 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Update Purpose 11 | 12 | Write the purpose of UI update here. 13 | 14 | ![css](https://media.giphy.com/media/yYSSBtDgbbRzq/giphy.gif) 15 | 16 | ## UI Suggestion 17 | 18 | Put images or link to the contents of suggestions. 19 | 20 | ## More Description (if any) 21 | 22 | Put more description here to help developers understand. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Starling Capture -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /src/internal/java/io/numbersprotocol/starlingcapture/publisher/numbers_storage/Secret.kt -------------------------------------------------------------------------------- /app/libs/zkma-3.4.6.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbersprotocol/starling-capture/635115c9d575c957916fa5eddcec4ba3b159a95f/app/libs/zkma-3.4.6.aar -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/src/androidTest/java/io/numbersprotocol/starlingcapture/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("org.starlinglab.starlingcapture", appContext.packageName) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/internal/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/internal/java/io/numbersprotocol/starlingcapture/di/VariantModule.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.di 2 | 3 | import io.numbersprotocol.starlingcapture.publisher.numbers_storage.NumbersStorageApi 4 | import io.numbersprotocol.starlingcapture.publisher.numbers_storage.NumbersStoragePublisherFragment 5 | import io.numbersprotocol.starlingcapture.publisher.numbers_storage.NumbersStoragePublisherViewModel 6 | import io.numbersprotocol.starlingcapture.publisher.numbers_storage.sign_up.NumbersStoragePublisherSignUpFragment 7 | import io.numbersprotocol.starlingcapture.publisher.numbers_storage.sign_up.NumbersStoragePublisherSignUpViewModel 8 | import org.koin.androidx.fragment.dsl.fragment 9 | import org.koin.androidx.viewmodel.dsl.viewModel 10 | import org.koin.dsl.module 11 | 12 | val variantModule = module { 13 | 14 | single { NumbersStorageApi.create() } 15 | 16 | viewModel { NumbersStoragePublisherViewModel(get()) } 17 | fragment { NumbersStoragePublisherFragment() } 18 | 19 | viewModel { NumbersStoragePublisherSignUpViewModel(get()) } 20 | fragment { NumbersStoragePublisherSignUpFragment() } 21 | } -------------------------------------------------------------------------------- /app/src/internal/java/io/numbersprotocol/starlingcapture/publisher/PublisherConfigs.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.publisher 2 | 3 | import io.numbersprotocol.starlingcapture.publisher.numbers_storage.numbersStoragePublisherConfig 4 | 5 | val publisherConfigs = listOf( 6 | numbersStoragePublisherConfig 7 | ) -------------------------------------------------------------------------------- /app/src/internal/java/io/numbersprotocol/starlingcapture/publisher/numbers_storage/NumbersStoragePublisherConfig.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.publisher.numbers_storage 2 | 3 | import androidx.work.OneTimeWorkRequestBuilder 4 | import androidx.work.WorkManager 5 | import androidx.work.workDataOf 6 | import io.numbersprotocol.starlingcapture.R 7 | import io.numbersprotocol.starlingcapture.publisher.ProofPublisher 8 | import io.numbersprotocol.starlingcapture.publisher.PublisherConfig 9 | import io.numbersprotocol.starlingcapture.util.stringLiveData 10 | import io.numbersprotocol.starlingcapture.util.stringPref 11 | 12 | class NumbersStoragePublisherConfig : PublisherConfig( 13 | R.string.numbers_storage, 14 | R.drawable.ic_n, 15 | R.id.toNumbersStoragePublisherLoginFragment, 16 | { context, proofs -> 17 | proofs.forEach { proof -> 18 | val publishRequest = OneTimeWorkRequestBuilder() 19 | .setInputData(workDataOf(ProofPublisher.KEY_PROOF_HASH to proof.hash)) 20 | .build() 21 | WorkManager.getInstance(context).enqueue(publishRequest) 22 | } 23 | }) { 24 | 25 | // FIXME: use encrypted shared preferences to store the token 26 | var authToken by sharedPreference.stringPref(KEY_AUTH_TOKEN) 27 | var userName by sharedPreference.stringPref(KEY_USER_NAME) 28 | val userNameLiveData = sharedPreference.stringLiveData(KEY_USER_NAME) 29 | var email by sharedPreference.stringPref(KEY_EMAIL) 30 | val emailLiveData = sharedPreference.stringLiveData(KEY_EMAIL) 31 | 32 | companion object { 33 | private const val KEY_AUTH_TOKEN = "KEY_AUTH_TOKEN" 34 | private const val KEY_USER_NAME = "KEY_USER_NAME" 35 | private const val KEY_EMAIL = "KEY_EMAIL" 36 | } 37 | } 38 | 39 | val numbersStoragePublisherConfig = NumbersStoragePublisherConfig() -------------------------------------------------------------------------------- /app/src/internal/java/io/numbersprotocol/starlingcapture/publisher/numbers_storage/NumbersStoragePublisherFragment.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.publisher.numbers_storage 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.navigation.fragment.findNavController 9 | import io.numbersprotocol.starlingcapture.R 10 | import io.numbersprotocol.starlingcapture.databinding.FragmentNumbersStoragePublisherLoginBinding 11 | import io.numbersprotocol.starlingcapture.util.navigateSafely 12 | import io.numbersprotocol.starlingcapture.util.observeEvent 13 | import io.numbersprotocol.starlingcapture.util.scopedLayoutFullScreen 14 | import io.numbersprotocol.starlingcapture.util.snack 15 | import kotlinx.android.synthetic.internal.fragment_numbers_storage_publisher_login.* 16 | import org.koin.androidx.viewmodel.ext.android.viewModel 17 | 18 | class NumbersStoragePublisherFragment : Fragment() { 19 | 20 | private val numbersStoragePublisherViewModel: NumbersStoragePublisherViewModel by viewModel() 21 | 22 | init { 23 | scopedLayoutFullScreen = false 24 | } 25 | 26 | override fun onCreateView( 27 | inflater: LayoutInflater, 28 | container: ViewGroup?, 29 | savedInstanceState: Bundle? 30 | ): View? { 31 | FragmentNumbersStoragePublisherLoginBinding.inflate(inflater, container, false) 32 | .also { binding -> 33 | binding.lifecycleOwner = viewLifecycleOwner 34 | binding.viewModel = numbersStoragePublisherViewModel 35 | return binding.root 36 | } 37 | } 38 | 39 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 40 | super.onViewCreated(view, savedInstanceState) 41 | toolbar.setNavigationOnClickListener { findNavController().navigateUp() } 42 | bindViewLifecycle() 43 | } 44 | 45 | private fun bindViewLifecycle() { 46 | numbersStoragePublisherViewModel.signUpEvent.observeEvent(viewLifecycleOwner) { 47 | findNavController().navigateSafely(R.id.toNumbersStoragePublisherSignUpFragment) 48 | } 49 | numbersStoragePublisherViewModel.errorEvent.observeEvent(viewLifecycleOwner) { snack(it) } 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/internal/java/io/numbersprotocol/starlingcapture/publisher/numbers_storage/sign_up/NumbersStoragePublisherSignUpFragment.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.publisher.numbers_storage.sign_up 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.lifecycle.lifecycleScope 9 | import androidx.navigation.fragment.findNavController 10 | import io.numbersprotocol.starlingcapture.R 11 | import io.numbersprotocol.starlingcapture.databinding.FragmentNumbersStoragePublisherSignupBinding 12 | import io.numbersprotocol.starlingcapture.util.observeEvent 13 | import io.numbersprotocol.starlingcapture.util.scopedLayoutFullScreen 14 | import io.numbersprotocol.starlingcapture.util.snack 15 | import kotlinx.android.synthetic.internal.fragment_numbers_storage_publisher_signup.* 16 | import kotlinx.coroutines.delay 17 | import org.koin.androidx.viewmodel.ext.android.viewModel 18 | 19 | class NumbersStoragePublisherSignUpFragment : Fragment() { 20 | 21 | private val numbersStoragePublisherSignUpViewModel: NumbersStoragePublisherSignUpViewModel by viewModel() 22 | 23 | init { 24 | scopedLayoutFullScreen = false 25 | } 26 | 27 | override fun onCreateView( 28 | inflater: LayoutInflater, 29 | container: ViewGroup?, 30 | savedInstanceState: Bundle? 31 | ): View? { 32 | FragmentNumbersStoragePublisherSignupBinding.inflate(inflater, container, false) 33 | .also { binding -> 34 | binding.lifecycleOwner = viewLifecycleOwner 35 | binding.viewModel = numbersStoragePublisherSignUpViewModel 36 | return binding.root 37 | } 38 | } 39 | 40 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 41 | super.onViewCreated(view, savedInstanceState) 42 | toolbar.setNavigationOnClickListener { findNavController().navigateUp() } 43 | bindViewLifecycle() 44 | } 45 | 46 | private fun bindViewLifecycle() { 47 | numbersStoragePublisherSignUpViewModel.errorEvent.observeEvent(viewLifecycleOwner) { 48 | snack(it) 49 | } 50 | numbersStoragePublisherSignUpViewModel.successEvent.observeEvent(viewLifecycleOwner) { 51 | showSuccessfulMessage() 52 | } 53 | } 54 | 55 | private fun showSuccessfulMessage() = lifecycleScope.launchWhenStarted { 56 | snack(R.string.message_account_created) 57 | delay(1500) 58 | findNavController().navigateUp() 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/internal/java/io/numbersprotocol/starlingcapture/publisher/numbers_storage/sign_up/NumbersStoragePublisherSignUpViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.publisher.numbers_storage.sign_up 2 | 3 | import androidx.lifecycle.* 4 | import io.numbersprotocol.starlingcapture.publisher.numbers_storage.NumbersStorageApi 5 | import io.numbersprotocol.starlingcapture.util.Event 6 | import kotlinx.coroutines.flow.combine 7 | import kotlinx.coroutines.launch 8 | import retrofit2.HttpException 9 | 10 | class NumbersStoragePublisherSignUpViewModel( 11 | private val numbersStorageApi: NumbersStorageApi 12 | ) : ViewModel() { 13 | 14 | val userName = MutableLiveData("") 15 | val userNameError = NumbersStorageApi.createUserNameErrorLiveData(userName) 16 | 17 | val email = MutableLiveData("") 18 | val emailError = NumbersStorageApi.createEmailErrorLiveData(email) 19 | 20 | val password = MutableLiveData("") 21 | val passwordError = NumbersStorageApi.createPasswordErrorLiveData(password) 22 | 23 | val isValid = userNameError.asFlow() 24 | .combine(emailError.asFlow()) { userNameError, emailError -> 25 | userNameError == null && emailError == null 26 | }.combine(passwordError.asFlow()) { isValid, passwordError -> 27 | isValid && passwordError == null 28 | }.asLiveData(timeoutInMs = 0) 29 | 30 | val isWaitingForServerResponse = MutableLiveData(false) 31 | val errorEvent = MutableLiveData>() 32 | val successEvent = MutableLiveData>() 33 | 34 | fun createAccount() = viewModelScope.launch { 35 | isWaitingForServerResponse.value = true 36 | try { 37 | numbersStorageApi.createUser(userName.value!!, email.value!!, password.value!!) 38 | successEvent.value = Event(Unit) 39 | } catch (e: HttpException) { 40 | errorEvent.value = Event(NumbersStorageApi.formatErrorResponse(e)) 41 | isWaitingForServerResponse.value = false 42 | } catch (e: Exception) { 43 | errorEvent.value = Event(e.message ?: e.toString()) 44 | isWaitingForServerResponse.value = false 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/internal/res/drawable/ic_email.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/internal/res/drawable/ic_n.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 14 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/internal/res/drawable/ic_person.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/internal/res/drawable/splash_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 15 | 16 | 22 | -------------------------------------------------------------------------------- /app/src/internal/res/navigation/publisher.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 16 | 17 | 18 | 23 | 30 | 31 | 32 | 37 | -------------------------------------------------------------------------------- /app/src/internal/res/values-b+zh+TW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Numbers Storage 4 | 使用者名稱 5 | 電子信箱 6 | 密碼 7 | 登入 8 | 9 | 註冊 10 | 建立帳號 11 | 必填 12 | 登出 13 | 14 | 錯誤的電子信箱格式。 15 | 輸入值太短。 16 | 輸入值為純數字。 17 | 新帳號已經建立完成。 18 | -------------------------------------------------------------------------------- /app/src/internal/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Numbers Storage 4 | User Name 5 | Email 6 | Password 7 | Login 8 | Or 9 | Sign Up 10 | Create Account 11 | Required 12 | Logout 13 | 14 | Invalid email address. 15 | The value is too short. 16 | The value is entirely numeric. 17 | New account has been created successfully. 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 28 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 41 | 42 | 47 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.appcompat.app.AppCompatActivity 6 | import io.numbersprotocol.starlingcapture.databinding.ActivityBaseBinding 7 | import kotlinx.android.synthetic.main.activity_base.* 8 | import org.koin.androidx.fragment.android.setupKoinFragmentFactory 9 | 10 | class BaseActivity : AppCompatActivity() { 11 | 12 | var layoutFullScreen = false 13 | set(value) { 14 | if (value) enableLayoutFullScreen() 15 | else disableLayoutFullScreen() 16 | field = value 17 | } 18 | 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | super.onCreate(savedInstanceState) 21 | setupKoinFragmentFactory() 22 | setTheme(R.style.Theme_MyApp) 23 | ActivityBaseBinding.inflate(layoutInflater).also { binding -> 24 | binding.lifecycleOwner = this 25 | setContentView(binding.root) 26 | } 27 | layoutFullScreen = true 28 | } 29 | 30 | @Suppress("DEPRECATION") 31 | private fun enableLayoutFullScreen() { 32 | rootLayout.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or 33 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or 34 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 35 | } 36 | 37 | @Suppress("DEPRECATION") 38 | private fun disableLayoutFullScreen() { 39 | rootLayout.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/collector/CollectWorker.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.collector 2 | 3 | import android.content.Context 4 | import androidx.annotation.StringRes 5 | import androidx.work.CoroutineWorker 6 | import androidx.work.WorkerParameters 7 | import io.numbersprotocol.starlingcapture.util.MimeType 8 | import io.numbersprotocol.starlingcapture.util.NotificationUtil 9 | import org.koin.core.KoinComponent 10 | import org.koin.core.inject 11 | import kotlin.properties.Delegates 12 | 13 | abstract class CollectWorker( 14 | val context: Context, 15 | params: WorkerParameters 16 | ) : CoroutineWorker(context, params), KoinComponent { 17 | 18 | abstract val name: String 19 | 20 | lateinit var hash: String 21 | lateinit var mimeType: MimeType 22 | private var notificationId by Delegates.notNull() 23 | private val notificationUtil: NotificationUtil by inject() 24 | 25 | private fun initialize() { 26 | hash = inputData.getString(ProofCollector.KEY_HASH)!! 27 | mimeType = MimeType.fromString(inputData.getString(ProofCollector.KEY_MIME_TYPE)!!) 28 | 29 | val illegalNotificationId = NotificationUtil.NOTIFICATION_ID_MIN - 1 30 | notificationId = inputData.getInt(ProofCollector.KEY_NOTIFICATION_ID, illegalNotificationId) 31 | if (notificationId == illegalNotificationId) error("Cannot get notification ID from input data.") 32 | } 33 | 34 | override suspend fun doWork(): Result { 35 | initialize() 36 | return try { 37 | onStarted() 38 | collect() 39 | onCleared() 40 | Result.success() 41 | } catch (e: Exception) { 42 | notificationUtil.notifyException(e, notificationId) 43 | Result.failure() 44 | } finally { 45 | onCleared() 46 | } 47 | } 48 | 49 | open suspend fun onStarted() {} 50 | abstract suspend fun collect() 51 | open suspend fun onCleared() {} 52 | 53 | protected fun notifyStartCollecting(@StringRes stringRes: Int) { 54 | val builder = ProofCollector.getNotificationBuilder(context).apply { 55 | setContentText(context.resources.getString(stringRes)) 56 | setProgress(0, 0, true) 57 | setOngoing(true) 58 | } 59 | notificationUtil.notify(notificationId, builder) 60 | } 61 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/collector/InformationProvider.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.collector 2 | 3 | import android.content.Context 4 | import androidx.work.WorkerParameters 5 | import io.numbersprotocol.starlingcapture.R 6 | import io.numbersprotocol.starlingcapture.data.information.Information 7 | import io.numbersprotocol.starlingcapture.data.information.InformationRepository 8 | import org.koin.core.inject 9 | 10 | abstract class InformationProvider( 11 | context: Context, params: WorkerParameters 12 | ) : CollectWorker(context, params) { 13 | 14 | private val informationRepository: InformationRepository by inject() 15 | 16 | override suspend fun collect() { 17 | notifyStartCollecting(R.string.message_collecting_proof_information) 18 | informationRepository.add(*provide().toTypedArray()) 19 | } 20 | 21 | abstract suspend fun provide(): Collection 22 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/collector/SignatureProvider.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.collector 2 | 3 | import android.content.Context 4 | import androidx.work.WorkerParameters 5 | import io.numbersprotocol.starlingcapture.R 6 | import io.numbersprotocol.starlingcapture.data.information.InformationRepository 7 | import io.numbersprotocol.starlingcapture.data.proof.ProofRepository 8 | import io.numbersprotocol.starlingcapture.data.serialization.SortedProofInformation 9 | import io.numbersprotocol.starlingcapture.data.signature.Signature 10 | import io.numbersprotocol.starlingcapture.data.signature.SignatureRepository 11 | import org.koin.core.inject 12 | 13 | abstract class SignatureProvider( 14 | context: Context, params: WorkerParameters 15 | ) : CollectWorker(context, params) { 16 | 17 | private val proofRepository: ProofRepository by inject() 18 | private val informationRepository: InformationRepository by inject() 19 | private val signatureRepository: SignatureRepository by inject() 20 | 21 | override suspend fun collect() { 22 | notifyStartCollecting(R.string.message_signing_proof_information) 23 | val proof = proofRepository.getByHash(hash)!! 24 | val sortedProofInformation = SortedProofInformation.create(proof, informationRepository) 25 | val json = sortedProofInformation.toJson() 26 | signatureRepository.add(*provide(json).toTypedArray()) 27 | } 28 | 29 | abstract suspend fun provide(serialized: String): Collection 30 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/collector/android_open_ssl/AndroidOpenSslSignatureProvider.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.collector.android_open_ssl 2 | 3 | import android.content.Context 4 | import androidx.work.WorkerParameters 5 | import io.numbersprotocol.starlingcapture.collector.SignatureProvider 6 | import io.numbersprotocol.starlingcapture.data.preference.PreferenceRepository 7 | import io.numbersprotocol.starlingcapture.data.signature.Signature 8 | import io.numbersprotocol.starlingcapture.util.androidOpenSslSignatureProvider 9 | import io.numbersprotocol.starlingcapture.util.signWithSha256AndEcdsa 10 | import org.koin.core.KoinComponent 11 | import org.koin.core.inject 12 | 13 | class AndroidOpenSslSignatureProvider( 14 | context: Context, 15 | params: WorkerParameters 16 | ) : SignatureProvider(context, params), KoinComponent { 17 | 18 | override val name = androidOpenSslSignatureProvider 19 | 20 | private val preferenceRepository: PreferenceRepository by inject() 21 | 22 | override suspend fun provide(serialized: String): Collection { 23 | val privateKey = preferenceRepository.defaultPrivateKey 24 | val publicKey = preferenceRepository.defaultPublicKey 25 | val signature = serialized.signWithSha256AndEcdsa(privateKey) 26 | 27 | return setOf(Signature(hash, name, signature, publicKey)) 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/collector/infosnapshot/InfoSnapshotConfig.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.collector.infosnapshot 2 | 3 | import android.content.Context 4 | import io.numbersprotocol.starlingcapture.util.booleanLiveData 5 | import io.numbersprotocol.starlingcapture.util.booleanPref 6 | import org.koin.core.KoinComponent 7 | import org.koin.core.inject 8 | 9 | object InfoSnapshotConfig : KoinComponent { 10 | 11 | private val context: Context by inject() 12 | const val name = "InfoSnapshot" 13 | private val sharedPreferences = context.getSharedPreferences(name, Context.MODE_PRIVATE) 14 | 15 | private const val KEY_COLLECT_DEVICE_INFO = "KEY_COLLECT_DEVICE_INFO" 16 | var collectDeviceInfo by sharedPreferences.booleanPref(KEY_COLLECT_DEVICE_INFO, true) 17 | val collectDeviceInfoLiveData = 18 | sharedPreferences.booleanLiveData(KEY_COLLECT_DEVICE_INFO, true) 19 | 20 | private const val KEY_COLLECT_LOCALE_INFO = "KEY_COLLECT_LOCALE_INFO" 21 | var collectLocaleInfo by sharedPreferences.booleanPref(KEY_COLLECT_LOCALE_INFO, true) 22 | val collectLocaleInfoLiveData = 23 | sharedPreferences.booleanLiveData(KEY_COLLECT_LOCALE_INFO, true) 24 | 25 | private const val KEY_COLLECT_LOCATION_INFO = "KEY_COLLECT_LOCATION_INFO" 26 | var collectLocationInfo by sharedPreferences.booleanPref(KEY_COLLECT_LOCATION_INFO, true) 27 | val collectLocationInfoLiveData = 28 | sharedPreferences.booleanLiveData(KEY_COLLECT_LOCATION_INFO, true) 29 | 30 | private const val KEY_COLLECT_SENSOR_INFO = "KEY_COLLECT_SENSOR_INFO" 31 | var collectSensorInfo by sharedPreferences.booleanPref(KEY_COLLECT_SENSOR_INFO, true) 32 | val collectSensorInfoLiveData = 33 | sharedPreferences.booleanLiveData(KEY_COLLECT_SENSOR_INFO, true) 34 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/collector/proofmode/ProofModeConfig.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.collector.proofmode 2 | 3 | import android.content.Context 4 | import android.os.Build 5 | import androidx.work.OneTimeWorkRequestBuilder 6 | import io.numbersprotocol.starlingcapture.collector.ProofCollector 7 | import io.numbersprotocol.starlingcapture.util.booleanLiveData 8 | import io.numbersprotocol.starlingcapture.util.booleanPref 9 | import org.koin.core.KoinComponent 10 | import org.koin.core.inject 11 | 12 | object ProofModeConfig : KoinComponent { 13 | 14 | private val context: Context by inject() 15 | private val proofCollector: ProofCollector by inject() 16 | //private val poofMdeProviderBuilder = OneTimeWorkRequestBuilder() 17 | 18 | val isSupported = Build.VERSION.SDK_INT <= Build.VERSION_CODES.P 19 | 20 | const val name = "ProofMode" 21 | private val sharedPreferences = context.getSharedPreferences(name, Context.MODE_PRIVATE) 22 | 23 | private const val KEY_IS_ENABLED = "KEY_IS_ENABLED" 24 | var isEnabled by sharedPreferences.booleanPref(KEY_IS_ENABLED) 25 | private set 26 | val isEnabledLiveData = sharedPreferences.booleanLiveData(KEY_IS_ENABLED) 27 | 28 | fun enable() { 29 | isEnabled = true 30 | //proofCollector.addProvideInformationRequestBuilder(proofModeProviderBuilder) 31 | } 32 | 33 | fun disable() { 34 | //proofCollector.removeProvideInformationRequestBuilder(proofModeProviderBuilder) 35 | isEnabled = false 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/collector/zion/ZionSessionSignatureProvider.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.collector.zion 2 | 3 | import android.content.Context 4 | import androidx.work.WorkerParameters 5 | import io.numbersprotocol.starlingcapture.collector.SignatureProvider 6 | import io.numbersprotocol.starlingcapture.data.signature.Signature 7 | import org.koin.core.KoinComponent 8 | import org.koin.core.inject 9 | 10 | class ZionSessionSignatureProvider( 11 | context: Context, 12 | params: WorkerParameters 13 | ) : SignatureProvider(context, params), KoinComponent { 14 | 15 | override val name = "Zion" 16 | 17 | private val zionApi: ZionApi by inject() 18 | private val sessionSignature: SessionSignature by inject() 19 | 20 | /* param serialized: compact content of metadata file (information.json) 21 | */ 22 | override suspend fun provide(serialized: String): Collection { 23 | val signature = 24 | if (sessionSignature.isEnabled) sessionSignature.signWithSha256AndEcdsa(serialized) 25 | else zionApi.signWithSha256AndEthereum(serialized) 26 | val publicKeys = if (sessionSignature.isEnabled) """ 27 | Session: 28 | ${sessionSignature.publicKey} 29 | 30 | SessionSignature: 31 | ${sessionSignature.publicKeySignature} 32 | 33 | Receive: 34 | ${zionApi.getEthereumReceivePublicKey()} 35 | 36 | Send: 37 | ${zionApi.getEthereumSendPublicKey()} 38 | """.trimIndent() 39 | else """ 40 | Receive: 41 | ${zionApi.getEthereumReceivePublicKey()} 42 | 43 | Send: 44 | ${zionApi.getEthereumSendPublicKey()} 45 | """.trimIndent() 46 | 47 | return setOf(Signature(hash, name, signature, publicKeys)) 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/data/AppDataBase.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.data 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import androidx.room.TypeConverters 6 | import io.numbersprotocol.starlingcapture.data.caption.Caption 7 | import io.numbersprotocol.starlingcapture.data.caption.CaptionDao 8 | import io.numbersprotocol.starlingcapture.data.information.Information 9 | import io.numbersprotocol.starlingcapture.data.information.InformationDao 10 | import io.numbersprotocol.starlingcapture.data.proof.Proof 11 | import io.numbersprotocol.starlingcapture.data.proof.ProofDao 12 | import io.numbersprotocol.starlingcapture.data.publish_history.PublishHistory 13 | import io.numbersprotocol.starlingcapture.data.publish_history.PublishHistoryDao 14 | import io.numbersprotocol.starlingcapture.data.signature.Signature 15 | import io.numbersprotocol.starlingcapture.data.signature.SignatureDao 16 | import io.numbersprotocol.starlingcapture.util.MimeType 17 | 18 | @Database( 19 | entities = [ 20 | Proof::class, 21 | Information::class, 22 | Signature::class, 23 | Caption::class, 24 | PublishHistory::class 25 | ], version = 1 26 | ) 27 | @TypeConverters(MimeType.RoomTypeConverter::class, Information.Importance.RoomTypeConverter::class) 28 | abstract class AppDataBase : RoomDatabase() { 29 | 30 | abstract fun proofDao(): ProofDao 31 | abstract fun informationDao(): InformationDao 32 | abstract fun signatureDao(): SignatureDao 33 | abstract fun captionDao(): CaptionDao 34 | abstract fun publishHistoryDao(): PublishHistoryDao 35 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/data/caption/Caption.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.data.caption 2 | 3 | import androidx.room.Entity 4 | import androidx.room.ForeignKey 5 | import androidx.room.ForeignKey.CASCADE 6 | import androidx.room.PrimaryKey 7 | import io.numbersprotocol.starlingcapture.data.proof.Proof 8 | 9 | @Entity( 10 | foreignKeys = [ForeignKey( 11 | entity = Proof::class, 12 | parentColumns = ["hash"], 13 | childColumns = ["proofHash"], 14 | onDelete = CASCADE 15 | )] 16 | ) 17 | data class Caption( 18 | @PrimaryKey val proofHash: String, 19 | val text: String 20 | ) -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/data/caption/CaptionDao.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.data.caption 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.room.* 5 | 6 | @Dao 7 | abstract class CaptionDao { 8 | 9 | @Query("SELECT * FROM Caption WHERE Caption.proofHash = :proofHash") 10 | abstract suspend fun queryByProofHash(proofHash: String): Caption? 11 | 12 | @Query("SELECT * FROM Caption WHERE Caption.proofHash = :proofHash") 13 | abstract fun queryByProofHashWithLiveData(proofHash: String): LiveData 14 | 15 | @Insert(onConflict = OnConflictStrategy.IGNORE) 16 | abstract suspend fun insert(caption: Caption): Long 17 | 18 | @Update 19 | abstract suspend fun update(caption: Caption): Int 20 | 21 | @Transaction 22 | open suspend fun upsert(caption: Caption) { 23 | val rowId = insert(caption) 24 | if (rowId == -1L) update(caption) 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/data/caption/CaptionRepository.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.data.caption 2 | 3 | import io.numbersprotocol.starlingcapture.data.proof.Proof 4 | 5 | class CaptionRepository(private val captionDao: CaptionDao) { 6 | 7 | suspend fun getByProof(proof: Proof) = captionDao.queryByProofHash(proof.hash) 8 | 9 | fun getByProofWithLiveData(proof: Proof) = captionDao.queryByProofHashWithLiveData(proof.hash) 10 | 11 | suspend fun addOrUpdate(caption: Caption) = captionDao.upsert(caption) 12 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/data/information/Information.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.data.information 2 | 3 | import androidx.annotation.DrawableRes 4 | import androidx.annotation.StringRes 5 | import androidx.room.Embedded 6 | import androidx.room.Entity 7 | import androidx.room.ForeignKey 8 | import androidx.room.ForeignKey.CASCADE 9 | import androidx.room.TypeConverter 10 | import io.numbersprotocol.starlingcapture.R 11 | import io.numbersprotocol.starlingcapture.data.proof.Proof 12 | 13 | @Entity( 14 | primaryKeys = ["proofHash", "provider", "name"], 15 | foreignKeys = [ForeignKey( 16 | entity = Proof::class, 17 | parentColumns = ["hash"], 18 | childColumns = ["proofHash"], 19 | onDelete = CASCADE 20 | )] 21 | ) 22 | data class Information( 23 | val proofHash: String, 24 | val provider: String, 25 | val name: String, 26 | val value: String, 27 | val importance: Importance, 28 | @Embedded(prefix = "type") val type: Type = Type.OTHER_TYPE 29 | ) { 30 | 31 | enum class Importance(val value: Int) { 32 | HIGH(10), 33 | LOW(5); 34 | 35 | companion object { 36 | fun fromValue(value: Int): Importance { 37 | values().forEach { if (it.value == value) return it } 38 | error("Cannot find the Importance with given value: $value") 39 | } 40 | } 41 | 42 | class RoomTypeConverter { 43 | @TypeConverter 44 | fun toImportance(value: Int) = fromValue(value) 45 | 46 | @TypeConverter 47 | fun fromImportance(importance: Importance) = importance.value 48 | } 49 | } 50 | 51 | data class Type( 52 | @StringRes val name: Int, 53 | @DrawableRes val icon: Int 54 | ) { 55 | companion object { 56 | val OTHER_TYPE = Type(R.string.others, R.drawable.ic_info) 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/data/information/InformationDao.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.data.information 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.room.Dao 5 | import androidx.room.Insert 6 | import androidx.room.Query 7 | import kotlinx.coroutines.flow.Flow 8 | 9 | @Dao 10 | interface InformationDao { 11 | 12 | @Query("SELECT * FROM Information WHERE Information.proofHash = :proofHash") 13 | suspend fun queryByProofHash(proofHash: String): List 14 | 15 | @Query("SELECT Information.provider FROM Information WHERE Information.proofHash = :proofHash GROUP BY provider") 16 | fun queryProvidersByProofHashWithLiveData(proofHash: String): LiveData> 17 | 18 | @Query("SELECT * FROM Information WHERE Information.proofHash = :proofHash AND Information.provider = :provider AND Information.importance >= :importance") 19 | fun queryByProofHashAndProviderWithFlow( 20 | proofHash: String, 21 | provider: String, 22 | importance: Information.Importance = Information.Importance.LOW 23 | ): Flow> 24 | 25 | @Insert 26 | suspend fun insert(vararg information: Information): List 27 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/data/information/InformationRepository.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.data.information 2 | 3 | import io.numbersprotocol.starlingcapture.data.proof.Proof 4 | 5 | class InformationRepository(private val informationDao: InformationDao) { 6 | 7 | suspend fun getByProof(proof: Proof) = informationDao.queryByProofHash(proof.hash) 8 | 9 | fun getProvidersByProofWithLiveData(proof: Proof) = 10 | informationDao.queryProvidersByProofHashWithLiveData(proof.hash) 11 | 12 | fun getByProofAndProviderWithFlow( 13 | proof: Proof, 14 | provider: String, 15 | importance: Information.Importance = Information.Importance.LOW 16 | ) = informationDao.queryByProofHashAndProviderWithFlow( 17 | proof.hash, 18 | provider, 19 | importance 20 | ) 21 | 22 | suspend fun add(vararg information: Information) = informationDao.insert(*information) 23 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/data/preference/PreferenceRepository.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.data.preference 2 | 3 | import android.content.Context 4 | import androidx.preference.PreferenceManager 5 | import io.numbersprotocol.starlingcapture.R 6 | import io.numbersprotocol.starlingcapture.util.stringLiveData 7 | import io.numbersprotocol.starlingcapture.util.stringPref 8 | 9 | class PreferenceRepository(context: Context) { 10 | 11 | private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) 12 | 13 | val darkModeLiveData = sharedPreferences.stringLiveData( 14 | context.getString(R.string.key_dark_mode), 15 | "-1" // MODE_NIGHT_FOLLOW_SYSTEM 16 | ) 17 | 18 | var defaultPublicKey by sharedPreferences.stringPref(context.getString(R.string.key_default_public_key)) 19 | 20 | var defaultPrivateKey by sharedPreferences.stringPref(context.getString(R.string.key_default_private_key)) 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/data/proof/Proof.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.data.proof 2 | 3 | import android.os.Parcelable 4 | import androidx.room.Entity 5 | import androidx.room.Ignore 6 | import androidx.room.PrimaryKey 7 | import com.squareup.moshi.JsonClass 8 | import io.numbersprotocol.starlingcapture.util.MimeType 9 | import kotlinx.android.parcel.IgnoredOnParcel 10 | import kotlinx.android.parcel.Parcelize 11 | import java.time.Instant 12 | 13 | @JsonClass(generateAdapter = true) 14 | @Entity 15 | @Parcelize 16 | data class Proof( 17 | @PrimaryKey val hash: String, 18 | val mimeType: MimeType, 19 | val timestamp: Long 20 | ) : Parcelable { 21 | @Ignore 22 | @IgnoredOnParcel 23 | val formattedTimestamp: String = Instant.ofEpochMilli(timestamp).run { toString() } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/data/proof/ProofDao.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.data.proof 2 | 3 | import androidx.paging.DataSource 4 | import androidx.room.* 5 | 6 | @Dao 7 | interface ProofDao { 8 | 9 | @Query("SELECT * FROM Proof") 10 | fun queryAllWithDataSource(): DataSource.Factory 11 | 12 | @Query("SELECT * FROM Proof") 13 | suspend fun queryAll(): List 14 | 15 | @Query("SELECT * FROM Proof WHERE hash=:hash") 16 | suspend fun queryByHash(hash: String): Proof? 17 | 18 | @Insert(onConflict = OnConflictStrategy.IGNORE) 19 | suspend fun insert(vararg proofs: Proof): List 20 | 21 | @Delete 22 | suspend fun delete(vararg proofs: Proof): Int 23 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/data/proof/ProofRepository.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.data.proof 2 | 3 | import android.content.Context 4 | import io.numbersprotocol.starlingcapture.util.sha256 5 | import timber.log.Timber 6 | import java.io.File 7 | 8 | class ProofRepository( 9 | context: Context, 10 | private val proofDao: ProofDao 11 | ) { 12 | 13 | private val rawFilesDir = context.filesDir.resolve("raw") 14 | 15 | fun getAllWithDataSource() = proofDao.queryAllWithDataSource() 16 | 17 | suspend fun getAll() = proofDao.queryAll() 18 | 19 | suspend fun getByHash(hash: String) = proofDao.queryByHash(hash) 20 | 21 | suspend fun add(proof: Proof) = proofDao.insert(proof) 22 | 23 | suspend fun remove(proofs: Collection): Int { 24 | var deleteCount = 0 25 | proofs.forEach { proof -> deleteCount += remove(proof) } 26 | return deleteCount 27 | } 28 | 29 | suspend fun remove(proof: Proof): Int { 30 | val deleteCount = proofDao.delete(proof) 31 | removeRawFile(proof) 32 | return deleteCount 33 | } 34 | 35 | private fun removeRawFile(proof: Proof) { 36 | val rawFile = getRawFile(proof) 37 | if (rawFile.exists()) rawFile.delete() 38 | else Timber.e("Cannot delete raw file as file not exists: $rawFile") 39 | } 40 | 41 | fun getRawFile(proof: Proof) = rawFilesDir.resolve("${proof.hash}.${proof.mimeType.extension}") 42 | 43 | /** 44 | * Copy [file] to add raw file to internal storage. 45 | * @param file: The original source of raw file which will be copied. 46 | * @return: The file added in the internal storage. The name of the returned file will be its hash with original extension. 47 | */ 48 | fun addRawFile(file: File): File { 49 | val fileHash = file.sha256() 50 | return file.copyTo(rawFilesDir.resolve("$fileHash.${file.extension}"), overwrite = true) 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/data/publish_history/PublishHistory.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.data.publish_history 2 | 3 | import androidx.room.Entity 4 | import androidx.room.ForeignKey 5 | import androidx.room.ForeignKey.CASCADE 6 | import io.numbersprotocol.starlingcapture.data.proof.Proof 7 | 8 | @Entity( 9 | primaryKeys = ["proofHash", "publisher"], 10 | foreignKeys = [ForeignKey( 11 | entity = Proof::class, 12 | parentColumns = ["hash"], 13 | childColumns = ["proofHash"], 14 | onDelete = CASCADE 15 | )] 16 | ) 17 | data class PublishHistory( 18 | val proofHash: String, 19 | val publisher: String, 20 | val timestamp: Long 21 | ) -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/data/publish_history/PublishHistoryDao.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.data.publish_history 2 | 3 | import androidx.room.* 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | @Dao 7 | abstract class PublishHistoryDao { 8 | 9 | @Query("SELECT * FROM PublishHistory WHERE PublishHistory.proofHash = :proofHash") 10 | abstract fun queryByProofHashWithFlow(proofHash: String): Flow> 11 | 12 | @Insert(onConflict = OnConflictStrategy.IGNORE) 13 | abstract suspend fun insert(publishHistory: PublishHistory): Long 14 | 15 | @Update 16 | abstract suspend fun update(publishHistory: PublishHistory): Int 17 | 18 | @Transaction 19 | open suspend fun upsert(publishHistory: PublishHistory) { 20 | val rawId = insert(publishHistory) 21 | if (rawId == -1L) update(publishHistory) 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/data/publish_history/PublishHistoryRepository.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.data.publish_history 2 | 3 | import io.numbersprotocol.starlingcapture.data.proof.Proof 4 | 5 | class PublishHistoryRepository(private val publishHistoryDao: PublishHistoryDao) { 6 | 7 | fun getByProofWithFlow(proof: Proof) = publishHistoryDao.queryByProofHashWithFlow(proof.hash) 8 | 9 | suspend fun addOrUpdate(history: PublishHistory) = publishHistoryDao.upsert(history) 10 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/data/serialization/Serialization.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.data.serialization 2 | 3 | import com.squareup.moshi.Moshi 4 | import io.numbersprotocol.starlingcapture.data.information.InformationRepository 5 | import io.numbersprotocol.starlingcapture.data.proof.Proof 6 | import io.numbersprotocol.starlingcapture.data.signature.SignatureRepository 7 | import org.koin.core.KoinComponent 8 | import org.koin.core.inject 9 | 10 | object Serialization : KoinComponent { 11 | 12 | private val moshi: Moshi by inject() 13 | private val informationRepository: InformationRepository by inject() 14 | private val signatureRepository: SignatureRepository by inject() 15 | 16 | suspend fun generateInformationJson(proof: Proof): String { 17 | val sortedProofInformation = SortedProofInformation.create(proof, informationRepository) 18 | return sortedProofInformation.toJson() 19 | } 20 | 21 | suspend fun generateSignaturesJson(proof: Proof): String { 22 | val signatures = signatureRepository.getByProof(proof) 23 | val jsonAdapter = moshi.adapter(List::class.java) 24 | return jsonAdapter.toJson(signatures) 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/data/signature/Signature.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.data.signature 2 | 3 | import androidx.room.Entity 4 | import androidx.room.ForeignKey 5 | import androidx.room.ForeignKey.CASCADE 6 | import com.squareup.moshi.JsonClass 7 | import io.numbersprotocol.starlingcapture.data.proof.Proof 8 | 9 | @JsonClass(generateAdapter = true) 10 | @Entity( 11 | primaryKeys = ["proofHash", "provider", "signature", "publicKey"], 12 | foreignKeys = [ForeignKey( 13 | entity = Proof::class, 14 | parentColumns = ["hash"], 15 | childColumns = ["proofHash"], 16 | onDelete = CASCADE 17 | )] 18 | ) 19 | data class Signature( 20 | val proofHash: String, 21 | val provider: String, 22 | val signature: String, 23 | val publicKey: String 24 | ) -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/data/signature/SignatureDao.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.data.signature 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.room.Dao 5 | import androidx.room.Insert 6 | import androidx.room.Query 7 | 8 | @Dao 9 | interface SignatureDao { 10 | 11 | @Query("SELECT * FROM Signature WHERE Signature.proofHash = :proofHash") 12 | suspend fun queryByProofHash(proofHash: String): List 13 | 14 | @Query("SELECT * FROM Signature WHERE Signature.proofHash = :proofHash") 15 | fun queryByProofHashWithLiveData(proofHash: String): LiveData> 16 | 17 | @Insert 18 | suspend fun insert(vararg signatures: Signature): List 19 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/data/signature/SignatureRepository.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.data.signature 2 | 3 | import io.numbersprotocol.starlingcapture.data.proof.Proof 4 | 5 | class SignatureRepository(private val signatureDao: SignatureDao) { 6 | 7 | suspend fun getByProof(proof: Proof) = signatureDao.queryByProofHash(proof.hash) 8 | 9 | fun getByProofWithLiveData(proof: Proof) = signatureDao.queryByProofHashWithLiveData(proof.hash) 10 | 11 | suspend fun add(vararg signature: Signature) = signatureDao.insert(*signature) 12 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/feature/audio/AudioViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.feature.audio 2 | 3 | import androidx.lifecycle.* 4 | import io.numbersprotocol.starlingcapture.R 5 | import io.numbersprotocol.starlingcapture.collector.ProofCollector 6 | import io.numbersprotocol.starlingcapture.util.MimeType 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.launch 9 | import java.io.File 10 | 11 | class AudioViewModel(private val proofCollector: ProofCollector) : ViewModel() { 12 | val isRecordingAudio = MutableLiveData(false) 13 | 14 | val captureAudioButtonIcon = isRecordingAudio.map { isRecording -> 15 | when { 16 | isRecording -> R.drawable.ic_stop 17 | else -> R.drawable.ic_fiber_manual_record 18 | } 19 | } 20 | 21 | fun storeMedia(cachedMediaFile: File, mimeType: MimeType) = 22 | viewModelScope.launch(Dispatchers.IO) { 23 | proofCollector.storeAndCollect(cachedMediaFile, mimeType) 24 | if (cachedMediaFile.exists()) cachedMediaFile.delete() 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/feature/camera/CameraViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.feature.camera 2 | 3 | import androidx.lifecycle.* 4 | import com.otaliastudios.cameraview.controls.Mode 5 | import io.numbersprotocol.starlingcapture.R 6 | import io.numbersprotocol.starlingcapture.collector.ProofCollector 7 | import io.numbersprotocol.starlingcapture.util.MimeType 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.flow.combine 10 | import kotlinx.coroutines.launch 11 | import java.io.File 12 | 13 | class CameraViewModel(private val proofCollector: ProofCollector) : ViewModel() { 14 | 15 | val cameraMode = MutableLiveData(Mode.PICTURE) 16 | val isTakingVideo = MutableLiveData(false) 17 | val captureButtonIcon = 18 | combine(cameraMode.asFlow(), isTakingVideo.asFlow()) { mode, isTakingVideo -> 19 | when { 20 | mode == Mode.PICTURE -> R.drawable.ic_capture 21 | isTakingVideo -> R.drawable.ic_stop 22 | else -> R.drawable.ic_fiber_manual_record 23 | } 24 | }.asLiveData(timeoutInMs = 0) 25 | 26 | fun storeMedia(cachedMediaFile: File, mimeType: MimeType) = 27 | viewModelScope.launch(Dispatchers.IO) { 28 | proofCollector.storeAndCollect(cachedMediaFile, mimeType) 29 | if (cachedMediaFile.exists()) cachedMediaFile.delete() 30 | } 31 | 32 | fun switchMode() { 33 | cameraMode.value = if (cameraMode.value == Mode.PICTURE) Mode.VIDEO else Mode.PICTURE 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/feature/ccapi/CcapiViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.feature.ccapi 2 | 3 | import android.graphics.Bitmap 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.liveData 7 | import androidx.lifecycle.viewModelScope 8 | import io.numbersprotocol.starlingcapture.source.canon.CanonCameraControlProvider 9 | import io.numbersprotocol.starlingcapture.util.Event 10 | import kotlinx.coroutines.* 11 | import kotlinx.coroutines.channels.ticker 12 | import kotlinx.coroutines.flow.catch 13 | import kotlinx.coroutines.flow.collect 14 | import timber.log.Timber 15 | import java.text.SimpleDateFormat 16 | import java.util.* 17 | 18 | class CcapiViewModel( 19 | private val canonCameraControlProvider: CanonCameraControlProvider 20 | ) : ViewModel() { 21 | 22 | val isEnabled = canonCameraControlProvider.isEnabledLiveData 23 | val address = canonCameraControlProvider.addressLiveData 24 | val editIpAddressAndPortEvent = MutableLiveData>() 25 | 26 | @ObsoleteCoroutinesApi 27 | private val tickerChannel = ticker(delayMillis = 500, initialDelayMillis = 0) 28 | 29 | @ObsoleteCoroutinesApi 30 | val slateTime = liveData { 31 | val dateFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault()) 32 | for (event in tickerChannel) emit(dateFormat.format(Date())) 33 | } 34 | 35 | @ObsoleteCoroutinesApi 36 | val slateDate = liveData { 37 | val dateFormat = SimpleDateFormat.getDateInstance() 38 | for (event in tickerChannel) emit(dateFormat.format(Date())) 39 | } 40 | val slatePhotographer = MutableLiveData("") 41 | val editSlateEvent = MutableLiveData>() 42 | 43 | private var liveViewJob: Job? = null 44 | val liveView = MutableLiveData() 45 | 46 | @ExperimentalCoroutinesApi 47 | fun enable() { 48 | canonCameraControlProvider.enable() 49 | startLiveView() 50 | } 51 | 52 | @ExperimentalCoroutinesApi 53 | private fun startLiveView() { 54 | liveViewJob = viewModelScope.launch(Dispatchers.Main) { 55 | canonCameraControlProvider.liveView 56 | .catch { e -> Timber.e(e) } 57 | .collect { liveView.value = it } 58 | } 59 | } 60 | 61 | @ExperimentalCoroutinesApi 62 | fun disable() { 63 | stopLiveView() 64 | canonCameraControlProvider.disable() 65 | } 66 | 67 | private fun stopLiveView() = liveViewJob?.cancel() 68 | 69 | fun editAddress() { 70 | editIpAddressAndPortEvent.value = Event(Unit) 71 | } 72 | 73 | fun setAddress(address: String) { 74 | canonCameraControlProvider.address = address 75 | } 76 | 77 | fun editSlate() { 78 | editSlateEvent.value = Event(slatePhotographer.value ?: "") 79 | } 80 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/feature/information/InformationViewItem.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.feature.information 2 | 3 | import android.view.View 4 | import com.xwray.groupie.viewbinding.BindableItem 5 | import io.numbersprotocol.starlingcapture.R 6 | import io.numbersprotocol.starlingcapture.data.information.Information 7 | import io.numbersprotocol.starlingcapture.databinding.ItemInformationBinding 8 | import io.numbersprotocol.starlingcapture.databinding.ItemInformationTypeBinding 9 | 10 | class TypeItem(private val type: Information.Type) : BindableItem() { 11 | 12 | override fun getLayout() = R.layout.item_information_type 13 | override fun initializeViewBinding(view: View) = ItemInformationTypeBinding.bind(view) 14 | override fun bind(viewBinding: ItemInformationTypeBinding, position: Int) { 15 | viewBinding.type = type 16 | } 17 | } 18 | 19 | class InformationItem( 20 | private val information: Information 21 | ) : BindableItem() { 22 | 23 | override fun getLayout() = R.layout.item_information 24 | override fun initializeViewBinding(view: View) = ItemInformationBinding.bind(view) 25 | override fun bind(viewBinding: ItemInformationBinding, position: Int) { 26 | viewBinding.information = information 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/feature/information/InformationViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.feature.information 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.asFlow 6 | import androidx.lifecycle.asLiveData 7 | import io.numbersprotocol.starlingcapture.data.information.InformationRepository 8 | import io.numbersprotocol.starlingcapture.data.proof.Proof 9 | import kotlinx.coroutines.ExperimentalCoroutinesApi 10 | import kotlinx.coroutines.flow.combine 11 | import kotlinx.coroutines.flow.filterNotNull 12 | import kotlinx.coroutines.flow.flatMapLatest 13 | import kotlinx.coroutines.flow.map 14 | 15 | class InformationViewModel(informationRepository: InformationRepository) : ViewModel() { 16 | 17 | val proof = MutableLiveData() 18 | val provider = MutableLiveData() 19 | 20 | @ExperimentalCoroutinesApi 21 | val informationGroup = combine( 22 | proof.asFlow().filterNotNull(), 23 | provider.asFlow().filterNotNull() 24 | ) { proof: Proof, provider: String -> proof to provider } 25 | .flatMapLatest { (proof, provider) -> 26 | informationRepository.getByProofAndProviderWithFlow( 27 | proof, 28 | provider 29 | ) 30 | }.map { it.groupBy { information -> information.type } } 31 | .asLiveData(timeoutInMs = 0) 32 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/feature/information_provider_config/InformationProviderConfigFragment.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.feature.information_provider_config 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.navigation.fragment.findNavController 9 | import io.numbersprotocol.starlingcapture.databinding.FragmentInformationProviderConfigBinding 10 | import kotlinx.android.synthetic.main.fragment_information_provider_config.* 11 | import org.koin.androidx.viewmodel.ext.android.viewModel 12 | 13 | class InformationProviderConfigFragment : Fragment() { 14 | 15 | private val informationProviderConfigViewModel: InformationProviderConfigViewModel by viewModel() 16 | 17 | override fun onCreateView( 18 | inflater: LayoutInflater, 19 | container: ViewGroup?, 20 | savedInstanceState: Bundle? 21 | ): View? { 22 | FragmentInformationProviderConfigBinding.inflate(inflater, container, false) 23 | .also { binding -> 24 | binding.viewModel = informationProviderConfigViewModel 25 | binding.lifecycleOwner = viewLifecycleOwner 26 | return binding.root 27 | } 28 | } 29 | 30 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 31 | super.onViewCreated(view, savedInstanceState) 32 | toolbar.setNavigationOnClickListener { findNavController().navigateUp() } 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/feature/information_provider_config/InformationProviderConfigViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.feature.information_provider_config 2 | 3 | import android.view.View 4 | import androidx.appcompat.widget.SwitchCompat 5 | import androidx.lifecycle.ViewModel 6 | import io.numbersprotocol.starlingcapture.collector.infosnapshot.InfoSnapshotConfig 7 | import io.numbersprotocol.starlingcapture.collector.proofmode.ProofModeConfig 8 | 9 | class InformationProviderConfigViewModel : ViewModel() { 10 | 11 | val infoSnapshotCollectDeviceInfo = InfoSnapshotConfig.collectDeviceInfoLiveData 12 | val infoSnapshotCollectLocaleInfo = InfoSnapshotConfig.collectLocaleInfoLiveData 13 | val infoSnapshotCollectLocationInfo = InfoSnapshotConfig.collectLocationInfoLiveData 14 | val infoSnapshotCollectSensorInfo = InfoSnapshotConfig.collectSensorInfoLiveData 15 | val proofModeSupported = ProofModeConfig.isSupported 16 | val proofModeEnabled = ProofModeConfig.isEnabledLiveData 17 | 18 | fun onInfoSnapshotCollectDeviceInfoClick(view: View) { 19 | InfoSnapshotConfig.collectDeviceInfo = (view as SwitchCompat).isChecked 20 | } 21 | 22 | fun onInfoSnapshotCollectLocaleInfoClick(view: View) { 23 | InfoSnapshotConfig.collectLocaleInfo = (view as SwitchCompat).isChecked 24 | } 25 | 26 | fun onInfoSnapshotCollectLocationInfoClick(view: View) { 27 | InfoSnapshotConfig.collectLocationInfo = (view as SwitchCompat).isChecked 28 | } 29 | 30 | fun onInfoSnapshotCollectSensorInfoClick(view: View) { 31 | InfoSnapshotConfig.collectSensorInfo = (view as SwitchCompat).isChecked 32 | } 33 | 34 | fun onProofModeEnabledClick(view: View) { 35 | if ((view as SwitchCompat).isChecked) ProofModeConfig.enable() 36 | else ProofModeConfig.disable() 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/feature/proof/ProofViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.feature.proof 2 | 3 | import androidx.lifecycle.* 4 | import io.numbersprotocol.starlingcapture.data.caption.Caption 5 | import io.numbersprotocol.starlingcapture.data.caption.CaptionRepository 6 | import io.numbersprotocol.starlingcapture.data.information.InformationRepository 7 | import io.numbersprotocol.starlingcapture.data.proof.Proof 8 | import io.numbersprotocol.starlingcapture.data.publish_history.PublishHistoryRepository 9 | import io.numbersprotocol.starlingcapture.data.signature.SignatureRepository 10 | import io.numbersprotocol.starlingcapture.util.Event 11 | import io.numbersprotocol.starlingcapture.util.MimeType 12 | import kotlinx.coroutines.flow.map 13 | import kotlinx.coroutines.launch 14 | 15 | class ProofViewModel( 16 | private val informationRepository: InformationRepository, 17 | private val signatureRepository: SignatureRepository, 18 | private val captionRepository: CaptionRepository, 19 | private val publishHistoryRepository: PublishHistoryRepository 20 | ) : ViewModel() { 21 | 22 | val proof = MutableLiveData() 23 | val informationProviders = proof.switchMap { 24 | informationRepository.getProvidersByProofWithLiveData(it) 25 | } 26 | val signatures = proof.switchMap { 27 | signatureRepository.getByProofWithLiveData(it) 28 | } 29 | val isAudio = proof.map { 30 | it.mimeType == MimeType.MP3 31 | } 32 | val isVideo = proof.map { 33 | it.mimeType == MimeType.MP4 34 | } 35 | val hasPublished = proof.switchMap { proof -> 36 | publishHistoryRepository.getByProofWithFlow(proof).map { it.isNotEmpty() }.asLiveData() 37 | } 38 | val caption = proof.switchMap { 39 | captionRepository.getByProofWithLiveData(it) 40 | } 41 | val viewMediaEvent = MutableLiveData>() 42 | val editCaptionEvent = MutableLiveData>() 43 | 44 | fun viewMedia() { 45 | viewMediaEvent.value = Event(Unit) 46 | } 47 | 48 | fun editCaption() = viewModelScope.launch { 49 | val caption = captionRepository.getByProof(proof.value!!)?.text ?: "" 50 | editCaptionEvent.value = Event(caption) 51 | } 52 | 53 | fun saveCaption(text: String) = viewModelScope.launch { 54 | captionRepository.addOrUpdate(Caption(proof.value!!.hash, text)) 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/feature/proof/SignatureAdapter.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.feature.proof 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.DiffUtil 6 | import androidx.recyclerview.widget.ListAdapter 7 | import androidx.recyclerview.widget.RecyclerView 8 | import io.numbersprotocol.starlingcapture.data.signature.Signature 9 | import io.numbersprotocol.starlingcapture.databinding.ItemSignatureBinding 10 | 11 | class SignatureAdapter : ListAdapter(diffCallback) { 12 | 13 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 14 | val layoutInflater = LayoutInflater.from(parent.context) 15 | val binding = ItemSignatureBinding.inflate(layoutInflater, parent, false) 16 | return ViewHolder(binding) 17 | } 18 | 19 | override fun onBindViewHolder(holder: ViewHolder, position: Int) = 20 | holder.bind(getItem(position)) 21 | 22 | class ViewHolder( 23 | private val binding: ItemSignatureBinding 24 | ) : RecyclerView.ViewHolder(binding.root) { 25 | 26 | fun bind(item: Signature) { 27 | binding.signature = item 28 | binding.executePendingBindings() 29 | } 30 | } 31 | 32 | companion object { 33 | private val diffCallback = object : DiffUtil.ItemCallback() { 34 | override fun areItemsTheSame(oldItem: Signature, newItem: Signature) = 35 | oldItem == newItem 36 | 37 | override fun areContentsTheSame(oldItem: Signature, newItem: Signature) = 38 | oldItem == newItem 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/feature/publisher_config/PublisherConfigAdapter.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.feature.publisher_config 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.DiffUtil 6 | import androidx.recyclerview.widget.ListAdapter 7 | import androidx.recyclerview.widget.RecyclerView 8 | import io.numbersprotocol.starlingcapture.databinding.ItemPublisherBinding 9 | import io.numbersprotocol.starlingcapture.publisher.PublisherConfig 10 | import io.numbersprotocol.starlingcapture.util.RecyclerViewItemListener 11 | 12 | class PublisherConfigAdapter( 13 | private val listener: RecyclerViewItemListener 14 | ) : ListAdapter(diffCallback) { 15 | 16 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 17 | val layoutInflater = LayoutInflater.from(parent.context) 18 | val binding = ItemPublisherBinding.inflate(layoutInflater, parent, false) 19 | return ViewHolder(binding) 20 | } 21 | 22 | override fun onBindViewHolder(holder: ViewHolder, position: Int) = 23 | holder.bind(getItem(position)) 24 | 25 | inner class ViewHolder( 26 | private val binding: ItemPublisherBinding 27 | ) : RecyclerView.ViewHolder(binding.root) { 28 | 29 | fun bind(item: PublisherConfig) { 30 | binding.publisher = item 31 | binding.root.setOnClickListener { listener.onItemClick(item, binding.root) } 32 | binding.executePendingBindings() 33 | } 34 | } 35 | 36 | companion object { 37 | private val diffCallback = object : DiffUtil.ItemCallback() { 38 | override fun areItemsTheSame(oldItem: PublisherConfig, newItem: PublisherConfig) = 39 | oldItem.name == newItem.name 40 | 41 | override fun areContentsTheSame(oldItem: PublisherConfig, newItem: PublisherConfig) = 42 | oldItem.name == newItem.name 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/feature/publisher_config/PublisherConfigFragment.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.feature.publisher_config 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.navigation.fragment.findNavController 9 | import io.numbersprotocol.starlingcapture.databinding.FragmentPublisherConfigBinding 10 | import io.numbersprotocol.starlingcapture.publisher.PublisherConfig 11 | import io.numbersprotocol.starlingcapture.publisher.publisherConfigs 12 | import io.numbersprotocol.starlingcapture.util.RecyclerViewItemListener 13 | import io.numbersprotocol.starlingcapture.util.navigateSafely 14 | import kotlinx.android.synthetic.main.fragment_publisher_config.* 15 | import org.koin.androidx.viewmodel.ext.android.viewModel 16 | 17 | class PublisherConfigFragment : Fragment() { 18 | 19 | private val publisherConfigViewModel: PublisherConfigViewModel by viewModel() 20 | private val recyclerViewItemListener = object : RecyclerViewItemListener() { 21 | override fun onItemClick(item: PublisherConfig, itemView: View) { 22 | super.onItemClick(item, itemView) 23 | findNavController().navigateSafely(item.toFragmentAction) 24 | } 25 | } 26 | 27 | override fun onCreateView( 28 | inflater: LayoutInflater, 29 | container: ViewGroup?, 30 | savedInstanceState: Bundle? 31 | ): View? { 32 | FragmentPublisherConfigBinding.inflate(inflater, container, false).also { binding -> 33 | binding.lifecycleOwner = viewLifecycleOwner 34 | binding.viewModel = publisherConfigViewModel 35 | binding.recyclerView.adapter = PublisherConfigAdapter(recyclerViewItemListener).apply { 36 | submitList(publisherConfigs) 37 | } 38 | return binding.root 39 | } 40 | } 41 | 42 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 43 | super.onViewCreated(view, savedInstanceState) 44 | toolbar.setNavigationOnClickListener { findNavController().navigateUp() } 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/feature/publisher_config/PublisherConfigViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.feature.publisher_config 2 | 3 | import androidx.lifecycle.ViewModel 4 | 5 | class PublisherConfigViewModel : ViewModel() -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/feature/setting/SettingFragment.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.feature.setting 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.navigation.fragment.findNavController 9 | import io.numbersprotocol.starlingcapture.databinding.FragmentSettingBinding 10 | import kotlinx.android.synthetic.main.fragment_setting.* 11 | 12 | class SettingFragment : Fragment() { 13 | 14 | override fun onCreateView( 15 | inflater: LayoutInflater, 16 | container: ViewGroup?, 17 | savedInstanceState: Bundle? 18 | ): View? { 19 | return FragmentSettingBinding.inflate(inflater, container, false).root 20 | } 21 | 22 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 23 | super.onViewCreated(view, savedInstanceState) 24 | toolbar.setNavigationOnClickListener { findNavController().navigateUp() } 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/feature/storage/StorageViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.feature.storage 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.viewModelScope 6 | import androidx.paging.toLiveData 7 | import io.numbersprotocol.starlingcapture.data.proof.Proof 8 | import io.numbersprotocol.starlingcapture.data.proof.ProofRepository 9 | import io.numbersprotocol.starlingcapture.util.Event 10 | import kotlinx.coroutines.launch 11 | 12 | class StorageViewModel(private val proofRepository: ProofRepository) : ViewModel() { 13 | 14 | val proofList = proofRepository.getAllWithDataSource().toLiveData(pageSize = 20) 15 | val newProofEvent = MutableLiveData>() 16 | 17 | fun addProof() { 18 | newProofEvent.value = Event(Unit) 19 | } 20 | 21 | fun deleteProof(items: Iterable) { 22 | // Copy the items to avoid the original iterable has been modified in other place before deletion. 23 | val targets = items.toSet() 24 | viewModelScope.launch { proofRepository.remove(targets) } 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/publisher/PublisherConfig.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.publisher 2 | 3 | import android.content.Context 4 | import android.content.Context.MODE_PRIVATE 5 | import android.content.SharedPreferences 6 | import androidx.annotation.DrawableRes 7 | import androidx.annotation.IdRes 8 | import androidx.annotation.StringRes 9 | import io.numbersprotocol.starlingcapture.data.proof.Proof 10 | import io.numbersprotocol.starlingcapture.util.booleanLiveData 11 | import io.numbersprotocol.starlingcapture.util.booleanPref 12 | import org.koin.core.KoinComponent 13 | import org.koin.core.inject 14 | 15 | open class PublisherConfig( 16 | @StringRes val name: Int, 17 | @DrawableRes val icon: Int, 18 | @IdRes val toFragmentAction: Int, 19 | val onPublish: (Context, Iterable) -> Unit 20 | ) : KoinComponent { 21 | 22 | protected val context: Context by inject() 23 | 24 | @Suppress("MemberVisibilityCanBePrivate") 25 | protected val sharedPreference: SharedPreferences = 26 | context.getSharedPreferences("${PUBLISHER_CONFIG}_${context.getString(name)}", MODE_PRIVATE) 27 | val isEnabledLiveData = 28 | sharedPreference.booleanLiveData(KEY_IS_ENABLED) 29 | var isEnabled by sharedPreference.booleanPref(KEY_IS_ENABLED) 30 | 31 | companion object { 32 | private const val PUBLISHER_CONFIG = "PUBLISHER_CONFIG" 33 | private const val KEY_IS_ENABLED = "KEY_IS_ENABLED" 34 | } 35 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/publisher/PublisherManager.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.publisher 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import androidx.lifecycle.LifecycleOwner 6 | import com.afollestad.materialdialogs.LayoutMode 7 | import com.afollestad.materialdialogs.MaterialDialog 8 | import com.afollestad.materialdialogs.bottomsheets.BottomSheet 9 | import com.afollestad.materialdialogs.lifecycle.lifecycleOwner 10 | import com.afollestad.materialdialogs.list.listItems 11 | import io.numbersprotocol.starlingcapture.R 12 | import io.numbersprotocol.starlingcapture.data.proof.Proof 13 | 14 | class PublisherManager(private val context: Context) { 15 | 16 | fun publishOrShowSelection( 17 | activity: Activity, 18 | proofs: Iterable, 19 | lifecycleOwner: LifecycleOwner, 20 | callback: () -> Unit = {} 21 | ) { 22 | if (publisherConfigs.size == 1 && publisherConfigs.first().isEnabled) { 23 | publisherConfigs.first().onPublish(context, proofs) 24 | callback() 25 | } else showSelection(activity, proofs, lifecycleOwner, callback) 26 | } 27 | 28 | private fun showSelection( 29 | activity: Activity, 30 | proofs: Iterable, 31 | lifecycleOwner: LifecycleOwner, 32 | callback: () -> Unit = {} 33 | ) { 34 | val items = publisherConfigs 35 | .filter { it.isEnabled } 36 | .map { context.getString(it.name) } 37 | MaterialDialog(activity, BottomSheet(LayoutMode.WRAP_CONTENT)).show { 38 | title(R.string.select_a_publisher) 39 | message(R.string.message_enable_publishers_in_settings) 40 | lifecycleOwner(lifecycleOwner) 41 | listItems(items = items) { _, _, text -> 42 | publisherConfigs.find { context.getString(it.name) == text }!! 43 | .onPublish(context, proofs) 44 | callback() 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/util/Bitmap.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.util 2 | 3 | -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/util/Color.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.util 2 | 3 | import android.content.Context 4 | import android.util.TypedValue 5 | import androidx.annotation.AttrRes 6 | 7 | fun Context.themeColor(@AttrRes attrRes: Int): Int { 8 | val typedValue = TypedValue() 9 | theme.resolveAttribute(attrRes, typedValue, true) 10 | return typedValue.data 11 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/util/Crypto.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.util 2 | 3 | import android.security.keystore.KeyProperties 4 | import java.io.File 5 | import java.security.* 6 | import java.security.spec.ECGenParameterSpec 7 | import java.security.spec.PKCS8EncodedKeySpec 8 | import java.security.spec.X509EncodedKeySpec 9 | 10 | private const val SHA_256 = "SHA-256" 11 | 12 | fun File.sha256(): String { 13 | val digest = MessageDigest.getInstance(SHA_256) 14 | DigestInputStream(this.inputStream(), digest).use { digestInputStream -> 15 | val buffer = ByteArray(8192) 16 | // Read all bytes: 17 | @Suppress("ControlFlowWithEmptyBody") 18 | while (digestInputStream.read(buffer, 0, buffer.size) != -1) { 19 | } 20 | } 21 | return digest.digest().asHex() 22 | } 23 | 24 | fun String.sha256() = toByteArray(Charsets.UTF_8).sha256() 25 | 26 | fun ByteArray.sha256(): String { 27 | val messageDigest = MessageDigest.getInstance(SHA_256) 28 | val digested = messageDigest.digest(this) 29 | return digested.asHex() 30 | } 31 | 32 | const val androidOpenSslSignatureProvider = "AndroidOpenSSL" 33 | 34 | fun createEcKeyPair(): KeyPair { 35 | val keyPairGenerator = KeyPairGenerator.getInstance( 36 | KeyProperties.KEY_ALGORITHM_EC, 37 | androidOpenSslSignatureProvider 38 | ) 39 | keyPairGenerator.initialize(ECGenParameterSpec("secp256r1")) 40 | return keyPairGenerator.generateKeyPair() 41 | } 42 | 43 | fun String.signWithSha256AndEcdsa(privateKey: String): String { 44 | val keyFactory = 45 | KeyFactory.getInstance(KeyProperties.KEY_ALGORITHM_EC, androidOpenSslSignatureProvider) 46 | val key = keyFactory.generatePrivate(PKCS8EncodedKeySpec(privateKey.hexAsByteArray())) 47 | val signer = Signature.getInstance("SHA256withECDSA") 48 | .apply { 49 | initSign(key) 50 | update(toByteArray(Charsets.UTF_8)) 51 | } 52 | return signer.sign().asHex() 53 | } 54 | 55 | @Suppress("unused") 56 | fun String.verifyWithSha256AndEcdsa(signature: String, publicKey: String): Boolean { 57 | val keyFactory = 58 | KeyFactory.getInstance(KeyProperties.KEY_ALGORITHM_EC, androidOpenSslSignatureProvider) 59 | val key = keyFactory.generatePublic(X509EncodedKeySpec(publicKey.hexAsByteArray())) 60 | val signer = Signature.getInstance("SHA256withECDSA") 61 | .apply { 62 | initVerify(key) 63 | update(toByteArray(Charsets.UTF_8)) 64 | } 65 | return signer.verify(signature.toByteArray(Charsets.UTF_8)) 66 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/util/Encoding.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.util 2 | 3 | import java.util.* 4 | 5 | fun ByteArray.asHex() = 6 | joinToString(separator = "") { String.format("%02x", (it.toInt() and 0xFF)) } 7 | 8 | fun String.hexAsByteArray() = chunked(2) 9 | .map { it.toUpperCase(Locale.getDefault()).toInt(16).toByte() } 10 | .toByteArray() -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/util/Event.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.util 2 | 3 | import androidx.annotation.MainThread 4 | import androidx.lifecycle.LifecycleOwner 5 | import androidx.lifecycle.LiveData 6 | 7 | class Event(private val content: T) { 8 | @Suppress("MemberVisibilityCanBePrivate") 9 | var hashBeenHandled = false 10 | 11 | fun getContentIfNotHandled(): T? = if (hashBeenHandled) { 12 | null 13 | } else { 14 | hashBeenHandled = true 15 | content 16 | } 17 | } 18 | 19 | @MainThread 20 | inline fun LiveData>.observeEvent( 21 | owner: LifecycleOwner, 22 | crossinline onEventUnhandledContent: (T) -> Unit 23 | ) = observe(owner) { 24 | it.getContentIfNotHandled()?.let(onEventUnhandledContent) 25 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/util/File.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.util 2 | 3 | import java.io.File 4 | import java.io.InputStream 5 | 6 | fun File.copyFromInputStream(inputStream: InputStream) = outputStream().use { 7 | inputStream.copyTo(it) 8 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/util/JsonAdapter.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.util 2 | 3 | import com.squareup.moshi.JsonAdapter 4 | import com.squareup.moshi.JsonReader 5 | import com.squareup.moshi.JsonWriter 6 | import com.squareup.moshi.Moshi 7 | import java.lang.reflect.ParameterizedType 8 | import java.lang.reflect.Type 9 | import java.util.* 10 | 11 | class SortedSetAdapter( 12 | private val elementAdapter: JsonAdapter 13 | ) : JsonAdapter>() { 14 | 15 | override fun fromJson(reader: JsonReader): SortedSet? { 16 | val result = sortedSetOf() 17 | reader.beginArray() 18 | while (reader.hasNext()) result.add(elementAdapter.fromJson(reader)!!) 19 | reader.endArray() 20 | return result 21 | } 22 | 23 | override fun toJson(writer: JsonWriter, value: SortedSet?) { 24 | writer.beginArray() 25 | value?.forEach { elementAdapter.toJson(writer, it) } 26 | writer.endArray() 27 | } 28 | } 29 | 30 | class SortedSetAdapterFactory : JsonAdapter.Factory { 31 | 32 | override fun create( 33 | type: Type, 34 | annotations: MutableSet, 35 | moshi: Moshi 36 | ): JsonAdapter<*>? { 37 | if (annotations.isNotEmpty()) { 38 | return null 39 | } 40 | 41 | if (type !is ParameterizedType) { 42 | return null 43 | } 44 | 45 | if (type.rawType != SortedSet::class.java) { 46 | return null 47 | } 48 | 49 | val elementType = type.actualTypeArguments[0] 50 | val elementAdapter = moshi.adapter(elementType) 51 | return SortedSetAdapter(elementAdapter).nullSafe() 52 | } 53 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/util/LayoutFullScreen.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.util 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.lifecycle.DefaultLifecycleObserver 5 | import androidx.lifecycle.LifecycleOwner 6 | import io.numbersprotocol.starlingcapture.BaseActivity 7 | 8 | var Fragment.scopedLayoutFullScreen: Boolean 9 | get() = (requireContext() as BaseActivity).layoutFullScreen 10 | set(value) { 11 | if (!value) { 12 | lifecycle.addObserver(object : DefaultLifecycleObserver { 13 | override fun onStart(owner: LifecycleOwner) { 14 | super.onStart(owner) 15 | (requireContext() as BaseActivity).layoutFullScreen = false 16 | } 17 | 18 | override fun onStop(owner: LifecycleOwner) { 19 | super.onStop(owner) 20 | (requireContext() as BaseActivity).layoutFullScreen = true 21 | } 22 | }) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/util/MimeType.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.util 2 | 3 | import androidx.room.TypeConverter 4 | import com.squareup.moshi.FromJson 5 | import com.squareup.moshi.ToJson 6 | import java.io.File 7 | import java.util.* 8 | 9 | enum class MimeType(private val string: String, val extension: String) { 10 | JPEG("image/jpeg", "jpg"), 11 | MP4("video/mp4", "mp4"), 12 | M4A("audio/mp4", "m4a"), 13 | MP3("audio/mpeg", "mp3"); 14 | 15 | override fun toString() = string 16 | 17 | class RoomTypeConverter { 18 | 19 | @TypeConverter 20 | fun toMimeType(value: String) = fromString(value) 21 | 22 | @TypeConverter 23 | fun fromMimeType(value: MimeType) = value.toString() 24 | } 25 | 26 | class JsonAdapter { 27 | 28 | @FromJson 29 | fun fromJson(value: String) = fromString(value) 30 | 31 | @ToJson 32 | fun toJson(value: MimeType) = value.toString() 33 | } 34 | 35 | companion object { 36 | fun fromString(string: String): MimeType { 37 | values().forEach { if (it.string == string) return it } 38 | error("Cannot find the MIME type with given string: $string") 39 | } 40 | 41 | fun fromUrl(url: String): MimeType { 42 | val extension = File(url).extension 43 | values().forEach { 44 | if (it.extension.toLowerCase(Locale.ROOT) == extension.toLowerCase(Locale.ROOT)) return it 45 | } 46 | error("Cannot find the MIME type with given extension: $extension") 47 | } 48 | 49 | fun isSupported(url: String) = values() 50 | .map { it.extension.toLowerCase(Locale.ROOT) } 51 | .contains(File(url).extension.toLowerCase(Locale.ROOT)) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/util/Navigation.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.util 2 | 3 | import androidx.annotation.IdRes 4 | import androidx.navigation.NavController 5 | import androidx.navigation.NavDirections 6 | import androidx.navigation.Navigator 7 | 8 | fun NavController.navigateSafely(@IdRes resId: Int) { 9 | (currentDestination ?: graph).getAction(resId) ?: return 10 | if (currentDestination?.id != resId) navigate(resId) 11 | } 12 | 13 | fun NavController.navigateSafely(directions: NavDirections, navigatorExtras: Navigator.Extras) { 14 | val action = (currentDestination ?: graph).getAction(directions.actionId) ?: return 15 | if (currentDestination?.id != action.destinationId) { 16 | navigate(directions, navigatorExtras) 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/util/NotificationUtil.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.util 2 | 3 | import android.app.NotificationChannel 4 | import android.app.NotificationManager 5 | import android.app.PendingIntent 6 | import android.content.Context 7 | import android.content.Intent 8 | import androidx.annotation.StringRes 9 | import androidx.core.app.NotificationCompat 10 | import androidx.core.app.NotificationManagerCompat 11 | import io.numbersprotocol.starlingcapture.BaseActivity 12 | import io.numbersprotocol.starlingcapture.R 13 | import io.numbersprotocol.starlingcapture.collector.ProofCollector 14 | import timber.log.Timber 15 | import java.util.concurrent.atomic.AtomicInteger 16 | 17 | class NotificationUtil(private val context: Context) { 18 | 19 | private val notificationIdMin = AtomicInteger(NOTIFICATION_ID_MIN) 20 | 21 | val baseActivityIntent: PendingIntent = Intent(context, BaseActivity::class.java).let { 22 | PendingIntent.getActivity(context, 0, it, 0) 23 | } 24 | 25 | fun initialize() { 26 | createChannel(CHANNEL_DEFAULT, R.string.starling_capture) 27 | } 28 | 29 | fun createChannel( 30 | id: String, 31 | @StringRes name: Int, 32 | importance: Int = NotificationManager.IMPORTANCE_HIGH 33 | ) { 34 | val channel = NotificationChannel(id, context.getString(name), importance) 35 | val notificationManager = 36 | context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 37 | notificationManager.createNotificationChannel(channel) 38 | } 39 | 40 | fun createNotificationId() = notificationIdMin.getAndIncrement() 41 | 42 | fun notifyException(e: Throwable, notificationId: Int) { 43 | Timber.e(e) 44 | val builder = ProofCollector.getNotificationBuilder(context).apply { 45 | setSmallIcon(R.drawable.ic_error) 46 | setContentText(e.message) 47 | setProgress(0, 0, false) 48 | setOngoing(false) 49 | } 50 | notify(notificationId, builder) 51 | } 52 | 53 | fun notify(notificationId: Int, builder: NotificationCompat.Builder) { 54 | NotificationManagerCompat.from(context).notify(notificationId, builder.build()) 55 | } 56 | 57 | fun cancel(notificationId: Int) { 58 | NotificationManagerCompat.from(context).cancel(notificationId) 59 | } 60 | 61 | companion object { 62 | const val CHANNEL_DEFAULT = "CHANNEL_DEFAULT" 63 | const val NOTIFICATION_SAVE_PROOF_RELATED_DATA = 1 64 | const val NOTIFICATION_ID_MIN = 1000 65 | } 66 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/util/RecyclerView.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.util 2 | 3 | import android.view.View 4 | 5 | abstract class RecyclerViewItemListener { 6 | open fun onItemClick(item: T, itemView: View) {} 7 | open fun onItemLongClick(item: T) {} 8 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/util/Snackbar.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.util 2 | 3 | import androidx.annotation.StringRes 4 | import androidx.fragment.app.Fragment 5 | import com.google.android.material.snackbar.Snackbar 6 | import timber.log.Timber 7 | 8 | fun Fragment.snack(e: Throwable, length: Int = Snackbar.LENGTH_LONG) { 9 | Timber.e(e) 10 | Snackbar.make(requireView(), e.message ?: e.toString(), length).show() 11 | } 12 | 13 | fun Fragment.snack(message: String, length: Int = Snackbar.LENGTH_LONG) { 14 | Timber.i(message) 15 | Snackbar.make(requireView(), message, length).show() 16 | } 17 | 18 | fun Fragment.snack( 19 | @StringRes message: Int, 20 | length: Int = Snackbar.LENGTH_LONG 21 | ) { 22 | snack(requireContext().getString(message), length) 23 | } -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/util/Validator.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.util 2 | 3 | import android.text.TextUtils 4 | import android.util.Patterns 5 | import java.util.regex.Pattern 6 | 7 | fun CharSequence.isInt() = toString().toIntOrNull() != null 8 | 9 | fun CharSequence.isPositiveInteger(): Boolean { 10 | val integer = toString().toIntOrNull() ?: return false 11 | return integer > 0 12 | } 13 | 14 | private const val IP_ADDRESS_WITH_OPTIONAL_PORT_PATTERN_STRING = 15 | "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\." + 16 | "(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\." + 17 | "(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\." + 18 | "(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9]))" + 19 | "(:([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?" 20 | 21 | private val IP_ADDRESS_WITH_OPTIONAL_PORT_PATTERN: Pattern = 22 | Pattern.compile(IP_ADDRESS_WITH_OPTIONAL_PORT_PATTERN_STRING) 23 | 24 | fun CharSequence.isIpAddress() = IP_ADDRESS_WITH_OPTIONAL_PORT_PATTERN.matcher(this).matches() 25 | 26 | fun CharSequence.isEmail() = 27 | !TextUtils.isEmpty(this) && Patterns.EMAIL_ADDRESS.matcher(this).matches() -------------------------------------------------------------------------------- /app/src/main/java/io/numbersprotocol/starlingcapture/util/ViewPager.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.util 2 | 3 | import android.content.Context 4 | import android.graphics.Rect 5 | import android.view.View 6 | import androidx.annotation.DimenRes 7 | import androidx.recyclerview.widget.RecyclerView 8 | import androidx.viewpager2.widget.ViewPager2 9 | import io.numbersprotocol.starlingcapture.R 10 | import io.numbersprotocol.starlingcapture.util.CardPreviewTransformer.Companion.currentItemHorizontalSpacing 11 | 12 | fun ViewPager2.enableCardPreview() { 13 | offscreenPageLimit = 1 14 | setPageTransformer(CardPreviewTransformer(context)) 15 | addItemDecoration(CardPreviewDecoration(context)) 16 | } 17 | 18 | class CardPreviewTransformer(private val context: Context) : ViewPager2.PageTransformer { 19 | 20 | override fun transformPage(page: View, position: Float) { 21 | val nextItemVisibleSizeValue = context.resources.getDimension(nextItemVisibleSize) 22 | 23 | page.translationX = 24 | -(nextItemVisibleSizeValue * 2 + currentItemHorizontalSpacing) * position 25 | } 26 | 27 | companion object { 28 | @DimenRes 29 | const val nextItemVisibleSize = R.dimen.keyline_3 30 | const val currentItemHorizontalSpacing = 0 31 | } 32 | } 33 | 34 | class CardPreviewDecoration(private val context: Context) : RecyclerView.ItemDecoration() { 35 | 36 | override fun getItemOffsets( 37 | outRect: Rect, 38 | view: View, 39 | parent: RecyclerView, 40 | state: RecyclerView.State 41 | ) { 42 | val nextItemVisibleSizeValue = 43 | context.resources.getDimension(CardPreviewTransformer.nextItemVisibleSize) 44 | 45 | outRect.right = (nextItemVisibleSizeValue + currentItemHorizontalSpacing).toInt() 46 | outRect.left = (nextItemVisibleSizeValue + currentItemHorizontalSpacing).toInt() 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbersprotocol/starling-capture/635115c9d575c957916fa5eddcec4ba3b159a95f/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbersprotocol/starling-capture/635115c9d575c957916fa5eddcec4ba3b159a95f/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/logo_capture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbersprotocol/starling-capture/635115c9d575c957916fa5eddcec4ba3b159a95f/app/src/main/res/drawable-nodpi/logo_capture.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/slate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbersprotocol/starling-capture/635115c9d575c957916fa5eddcec4ba3b159a95f/app/src/main/res/drawable-nodpi/slate.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/sound_wave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbersprotocol/starling-capture/635115c9d575c957916fa5eddcec4ba3b159a95f/app/src/main/res/drawable-nodpi/sound_wave.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbersprotocol/starling-capture/635115c9d575c957916fa5eddcec4ba3b159a95f/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbersprotocol/starling-capture/635115c9d575c957916fa5eddcec4ba3b159a95f/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbersprotocol/starling-capture/635115c9d575c957916fa5eddcec4ba3b159a95f/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/empty_divider.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_back.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_broken_image.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_capture.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 16 | 19 | 22 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_device_information.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_done.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_edit.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_error.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fiber_manual_record.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fingerprint.xml: -------------------------------------------------------------------------------- 1 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_key.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_language.xml: -------------------------------------------------------------------------------- 1 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_linked_camera.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_location.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_lock_open.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_microphone.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_moon.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_photo_camera.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_power_settings.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_publish.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_recovery.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_refresh.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_save_alt.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_select_all.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings_input_antenna.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_stop.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_timer.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_toggle_off.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_toggle_on.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_videocam.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_zion.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/splash_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/font/dseg7classic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbersprotocol/starling-capture/635115c9d575c957916fa5eddcec4ba3b159a95f/app/src/main/res/font/dseg7classic.ttf -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_base.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_audio.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 11 | 12 | 13 | 16 | 17 | 29 | 30 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_information.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 23 | 24 | 27 | 28 | 35 | 36 | 37 | 49 | 50 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_publisher_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 20 | 21 | 24 | 25 | 31 | 32 | 33 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_setting.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 17 | 18 | 24 | 25 | 26 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_information.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 19 | 20 | 28 | 29 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_information_type.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 17 | 18 | 25 | 26 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_publisher.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 17 | 18 | 24 | 25 | 33 | 34 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_simple_information.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 18 | 19 | 28 | 29 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/layout/menu_item_switch.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/menu/ccapi.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/menu/proof.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | 16 | 17 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/menu/storage_action_mode.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 14 | 19 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/menu/storage_option.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/menu/zion.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @string/light 5 | @string/dark 6 | @string/set_by_battery_saver 7 | @string/system_default 8 | 9 | 10 | 1 11 | 2 12 | 3 13 | -1 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #80DEEA 4 | #4DD0E1 5 | #26C6DA 6 | #00BCD4 7 | #00ACC1 8 | #0097A7 9 | #00838F 10 | #CF6679 11 | #B00020 12 | #FFFFFF 13 | #000000 14 | #263238 15 | #33000000 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2dp 4 | 4dp 5 | 8dp 6 | 16dp 7 | 24dp 8 | 48dp 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/keys.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | key_dark_mode 4 | key_information_provider 5 | key_default_public_key 6 | key_default_private_key 7 | key_sign_with_zion 8 | key_publisher 9 | Key_version 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/shape.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 14 | 15 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 16 | 17 | 18 | 21 | 22 | 23 | 27 | 28 | 29 | 32 | 33 | 34 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/values/type.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 12 | 13 | 16 | 17 | 20 | 21 | 24 | 25 | 28 | 29 | 32 | 33 | 36 | 37 | 40 | 41 | 44 | 45 | 48 | 49 | 52 | 53 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /app/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 27 | 31 | 35 | 36 | 37 | 38 | 42 | 43 | 44 | 45 | 50 | 51 | -------------------------------------------------------------------------------- /app/src/master/java/io/numbersprotocol/starlingcapture/di/VariantModule.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.di 2 | 3 | import io.numbersprotocol.starlingcapture.publisher.sample.SamplePublisherFragment 4 | import io.numbersprotocol.starlingcapture.publisher.sample.SamplePublisherViewModel 5 | import org.koin.androidx.fragment.dsl.fragment 6 | import org.koin.androidx.viewmodel.dsl.viewModel 7 | import org.koin.dsl.module 8 | 9 | val variantModule = module { 10 | 11 | viewModel { SamplePublisherViewModel() } 12 | fragment { SamplePublisherFragment() } 13 | } -------------------------------------------------------------------------------- /app/src/master/java/io/numbersprotocol/starlingcapture/publisher/PublisherConfigs.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.publisher 2 | 3 | import io.numbersprotocol.starlingcapture.publisher.sample.samplePublisherConfig 4 | 5 | val publisherConfigs = listOf( 6 | samplePublisherConfig 7 | ) -------------------------------------------------------------------------------- /app/src/master/java/io/numbersprotocol/starlingcapture/publisher/sample/SamplePublisher.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.publisher.sample 2 | 3 | import android.content.Context 4 | import androidx.work.WorkerParameters 5 | import io.numbersprotocol.starlingcapture.R 6 | import io.numbersprotocol.starlingcapture.data.proof.Proof 7 | import io.numbersprotocol.starlingcapture.publisher.ProofPublisher 8 | import kotlinx.coroutines.delay 9 | 10 | class SamplePublisher( 11 | context: Context, 12 | params: WorkerParameters 13 | ) : ProofPublisher(context, params) { 14 | 15 | override val name = context.getString(R.string.sample_publisher) 16 | 17 | override suspend fun publish(proof: Proof): Result { 18 | delay(3000) // Pretend we are publishing the proof. 19 | return Result.success() 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/master/java/io/numbersprotocol/starlingcapture/publisher/sample/SamplePublisherConfig.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.publisher.sample 2 | 3 | import androidx.work.OneTimeWorkRequestBuilder 4 | import androidx.work.WorkManager 5 | import androidx.work.workDataOf 6 | import io.numbersprotocol.starlingcapture.R 7 | import io.numbersprotocol.starlingcapture.publisher.ProofPublisher 8 | import io.numbersprotocol.starlingcapture.publisher.PublisherConfig 9 | 10 | val samplePublisherConfig = PublisherConfig( 11 | R.string.sample_publisher, 12 | R.drawable.ic_publish, 13 | R.id.toSamplePublisherFragment 14 | ) { context, proofs -> 15 | proofs.forEach { proof -> 16 | val publishRequest = OneTimeWorkRequestBuilder() 17 | .setInputData(workDataOf(ProofPublisher.KEY_PROOF_HASH to proof.hash)) 18 | .build() 19 | WorkManager.getInstance(context).enqueue(publishRequest) 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/master/java/io/numbersprotocol/starlingcapture/publisher/sample/SamplePublisherFragment.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.publisher.sample 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.navigation.fragment.findNavController 9 | import io.numbersprotocol.starlingcapture.databinding.FragmentSamplePublisherBinding 10 | import kotlinx.android.synthetic.main.menu_item_switch.* 11 | import kotlinx.android.synthetic.master.fragment_sample_publisher.* 12 | import org.koin.androidx.viewmodel.ext.android.viewModel 13 | 14 | class SamplePublisherFragment : Fragment() { 15 | 16 | private val samplePublisherViewModel: SamplePublisherViewModel by viewModel() 17 | 18 | override fun onCreateView( 19 | inflater: LayoutInflater, 20 | container: ViewGroup?, 21 | savedInstanceState: Bundle? 22 | ): View? { 23 | FragmentSamplePublisherBinding.inflate(inflater, container, false).also { binding -> 24 | binding.lifecycleOwner = viewLifecycleOwner 25 | binding.viewModel = samplePublisherViewModel 26 | return binding.root 27 | } 28 | } 29 | 30 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 31 | super.onViewCreated(view, savedInstanceState) 32 | toolbar.setNavigationOnClickListener { findNavController().navigateUp() } 33 | initializeToolbarSwitch() 34 | } 35 | 36 | private fun initializeToolbarSwitch() { 37 | samplePublisherConfig.isEnabledLiveData.observe(viewLifecycleOwner) { 38 | switchItem.isChecked = it 39 | } 40 | switchItem.setOnCheckedChangeListener { _, isChecked -> 41 | samplePublisherConfig.isEnabled = isChecked 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/master/java/io/numbersprotocol/starlingcapture/publisher/sample/SamplePublisherViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.publisher.sample 2 | 3 | import androidx.lifecycle.ViewModel 4 | 5 | class SamplePublisherViewModel : ViewModel() -------------------------------------------------------------------------------- /app/src/master/res/layout/fragment_sample_publisher.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 20 | 21 | 24 | 25 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/master/res/menu/sample_publisher.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /app/src/master/res/navigation/publisher.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 20 | 21 | 22 | 27 | -------------------------------------------------------------------------------- /app/src/master/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Sample Publisher 4 | -------------------------------------------------------------------------------- /app/src/starling/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/starling/java/org/starlinglab/starlingcapture/di/VariantModule.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.di 2 | 3 | import org.starlinglab.starlingcapture.publisher.starling_integrity.StarlingIntegrityApi 4 | import org.starlinglab.starlingcapture.publisher.starling_integrity.StarlingIntegrityPublisherFragment 5 | import org.starlinglab.starlingcapture.publisher.starling_integrity.StarlingIntegrityPublisherViewModel 6 | //import io.numbersprotocol.starlingcapture.publisher.numbers_storage.sign_up.NumbersStoragePublisherSignUpFragment 7 | //import io.numbersprotocol.starlingcapture.publisher.numbers_storage.sign_up.NumbersStoragePublisherSignUpViewModel 8 | import org.koin.androidx.fragment.dsl.fragment 9 | import org.koin.androidx.viewmodel.dsl.viewModel 10 | import org.koin.dsl.module 11 | 12 | val variantModule = module { 13 | 14 | single { StarlingIntegrityApi.create() } 15 | 16 | viewModel { StarlingIntegrityPublisherViewModel() } 17 | fragment { StarlingIntegrityPublisherFragment() } 18 | } -------------------------------------------------------------------------------- /app/src/starling/java/org/starlinglab/starlingcapture/publisher/PublisherConfigs.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.publisher 2 | 3 | import org.starlinglab.starlingcapture.publisher.starling_integrity.starlingIntegrityPublisherConfig 4 | 5 | val publisherConfigs = listOf( 6 | starlingIntegrityPublisherConfig 7 | ) -------------------------------------------------------------------------------- /app/src/starling/java/org/starlinglab/starlingcapture/publisher/starling_integrity/StarlingIntegrityApi.kt: -------------------------------------------------------------------------------- 1 | package org.starlinglab.starlingcapture.publisher.starling_integrity 2 | 3 | import io.numbersprotocol.starlingcapture.BuildConfig 4 | import okhttp3.MultipartBody 5 | import okhttp3.OkHttpClient 6 | import retrofit2.Retrofit 7 | import retrofit2.converter.moshi.MoshiConverterFactory 8 | import retrofit2.converter.scalars.ScalarsConverterFactory 9 | import retrofit2.http.Header 10 | import retrofit2.http.Multipart 11 | import retrofit2.http.POST 12 | import retrofit2.http.Part 13 | import java.util.concurrent.TimeUnit 14 | 15 | interface StarlingIntegrityApi { 16 | 17 | @Multipart 18 | @POST("v1/assets/create") 19 | suspend fun createMedia( 20 | @Header("Authorization") authToken: String, 21 | @Part rawFile: MultipartBody.Part, 22 | @Part("meta") information: String, 23 | @Part("target_provider") targetProvider: String, 24 | @Part("caption") caption: String, 25 | @Part("signature") signatures: String, 26 | @Part("tag") tag: String 27 | ): String 28 | 29 | companion object { 30 | fun create(): StarlingIntegrityApi = Retrofit.Builder() 31 | .baseUrl(BuildConfig.STARLING_INTEGRITY_BASE_URL) 32 | .addConverterFactory(ScalarsConverterFactory.create()) 33 | .addConverterFactory(MoshiConverterFactory.create()) 34 | .client( 35 | OkHttpClient.Builder() 36 | .connectTimeout(240, TimeUnit.SECONDS) 37 | .writeTimeout(240, TimeUnit.SECONDS) 38 | .readTimeout(240, TimeUnit.SECONDS) 39 | .build() 40 | ) 41 | .build() 42 | .create(StarlingIntegrityApi::class.java) 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/starling/java/org/starlinglab/starlingcapture/publisher/starling_integrity/StarlingIntegrityPublisherConfig.kt: -------------------------------------------------------------------------------- 1 | package org.starlinglab.starlingcapture.publisher.starling_integrity 2 | 3 | import androidx.work.OneTimeWorkRequestBuilder 4 | import androidx.work.WorkManager 5 | import androidx.work.workDataOf 6 | import io.numbersprotocol.starlingcapture.R 7 | import io.numbersprotocol.starlingcapture.publisher.ProofPublisher 8 | import io.numbersprotocol.starlingcapture.publisher.PublisherConfig 9 | import io.numbersprotocol.starlingcapture.util.stringPref 10 | 11 | class StarlingIntegrityPublisherConfig : PublisherConfig( 12 | R.string.starling_integrity, 13 | R.drawable.ic_starling, 14 | R.id.toStarlingIntegrityPublisherLoginFragment, 15 | { context, proofs -> 16 | proofs.forEach { proof -> 17 | val publishRequest = OneTimeWorkRequestBuilder() 18 | .setInputData(workDataOf(ProofPublisher.KEY_PROOF_HASH to proof.hash)) 19 | .build() 20 | WorkManager.getInstance(context).enqueue(publishRequest) 21 | } 22 | }) { 23 | 24 | // FIXME: use encrypted shared preferences to store the token 25 | var authToken by sharedPreference.stringPref(KEY_AUTH_TOKEN) 26 | 27 | companion object { 28 | private const val KEY_AUTH_TOKEN = "KEY_AUTH_TOKEN" 29 | } 30 | } 31 | 32 | val starlingIntegrityPublisherConfig = StarlingIntegrityPublisherConfig() -------------------------------------------------------------------------------- /app/src/starling/java/org/starlinglab/starlingcapture/publisher/starling_integrity/StarlingIntegrityPublisherFragment.kt: -------------------------------------------------------------------------------- 1 | package org.starlinglab.starlingcapture.publisher.starling_integrity 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.navigation.fragment.findNavController 9 | import io.numbersprotocol.starlingcapture.databinding.FragmentStarlingIntegrityPublisherLoginBinding 10 | import io.numbersprotocol.starlingcapture.util.observeEvent 11 | import io.numbersprotocol.starlingcapture.util.scopedLayoutFullScreen 12 | import io.numbersprotocol.starlingcapture.util.snack 13 | import kotlinx.android.synthetic.starling.fragment_starling_integrity_publisher_login.* 14 | import org.koin.androidx.viewmodel.ext.android.viewModel 15 | 16 | class StarlingIntegrityPublisherFragment : Fragment() { 17 | 18 | private val starlingIntegrityPublisherViewModel: StarlingIntegrityPublisherViewModel by viewModel() 19 | 20 | init { 21 | scopedLayoutFullScreen = false 22 | } 23 | 24 | override fun onCreateView( 25 | inflater: LayoutInflater, 26 | container: ViewGroup?, 27 | savedInstanceState: Bundle? 28 | ): View? { 29 | FragmentStarlingIntegrityPublisherLoginBinding.inflate(inflater, container, false) 30 | .also { binding -> 31 | binding.lifecycleOwner = viewLifecycleOwner 32 | binding.viewModel = starlingIntegrityPublisherViewModel 33 | return binding.root 34 | } 35 | } 36 | 37 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 38 | super.onViewCreated(view, savedInstanceState) 39 | toolbar.setNavigationOnClickListener { findNavController().navigateUp() } 40 | bindViewLifecycle() 41 | } 42 | 43 | private fun bindViewLifecycle() { 44 | starlingIntegrityPublisherViewModel.errorEvent.observeEvent(viewLifecycleOwner) { snack(it) } 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/starling/java/org/starlinglab/starlingcapture/publisher/starling_integrity/StarlingIntegrityPublisherViewModel.kt: -------------------------------------------------------------------------------- 1 | package org.starlinglab.starlingcapture.publisher.starling_integrity 2 | 3 | import androidx.lifecycle.* 4 | import io.numbersprotocol.starlingcapture.util.Event 5 | import kotlinx.coroutines.launch 6 | 7 | class StarlingIntegrityPublisherViewModel : ViewModel() { 8 | 9 | val hasLoggedIn = starlingIntegrityPublisherConfig.isEnabledLiveData 10 | val loginToken = MutableLiveData("") 11 | val errorEvent = MutableLiveData>() 12 | 13 | fun login() = viewModelScope.launch { 14 | starlingIntegrityPublisherConfig.apply { 15 | authToken = "Bearer ${loginToken.value?.trim()}" 16 | isEnabled = true 17 | } 18 | } 19 | 20 | fun logout() = viewModelScope.launch { 21 | starlingIntegrityPublisherConfig.isEnabled = false 22 | starlingIntegrityPublisherConfig.authToken = "" 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/starling/res/drawable-nodpi/icon_starling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbersprotocol/starling-capture/635115c9d575c957916fa5eddcec4ba3b159a95f/app/src/starling/res/drawable-nodpi/icon_starling.png -------------------------------------------------------------------------------- /app/src/starling/res/drawable-nodpi/logo_starling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbersprotocol/starling-capture/635115c9d575c957916fa5eddcec4ba3b159a95f/app/src/starling/res/drawable-nodpi/logo_starling.png -------------------------------------------------------------------------------- /app/src/starling/res/drawable/ic_starling.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/starling/res/drawable/splash_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/starling/res/navigation/publisher.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 16 | 17 | 18 | 23 | -------------------------------------------------------------------------------- /app/src/starling/res/values/string.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Starling Integrity 4 | Login Token 5 | Login 6 | Logout 7 | -------------------------------------------------------------------------------- /app/src/test/java/io/numbersprotocol/starlingcapture/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture 2 | 3 | import org.junit.Test 4 | 5 | /** 6 | * Example local unit test, which will execute on the development machine (host). 7 | * 8 | * See [testing documentation](http://d.android.com/tools/testing). 9 | */ 10 | class ExampleUnitTest { 11 | @Test 12 | fun test() { 13 | val a: String? = null 14 | 15 | a?.also { 16 | println(it) 17 | } ?: also { 18 | println("null") 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/test/java/io/numbersprotocol/starlingcapture/source/canon/CanonCameraControlApiTest.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.source.canon 2 | 3 | import kotlinx.coroutines.runBlocking 4 | import org.junit.Before 5 | import org.junit.Ignore 6 | import org.junit.Test 7 | 8 | class CanonCameraControlApiTest { 9 | 10 | private lateinit var canonCameraControlApi: CanonCameraControlApi 11 | 12 | @Before 13 | fun setUp() { 14 | canonCameraControlApi = CanonCameraControlApi.create("192.168.1.60:8080") 15 | } 16 | 17 | @Ignore("require Canon camera with CCAPI") 18 | @Test 19 | fun testListSupportedApi() = runBlocking { 20 | println(canonCameraControlApi.listSupportedApis()) 21 | } 22 | 23 | @Ignore("require Canon camera with CCAPI") 24 | @Test 25 | fun testPolling() = runBlocking { 26 | println(canonCameraControlApi.poll()) 27 | } 28 | 29 | @Ignore("require Canon camera with CCAPI") 30 | @Test 31 | fun testGetContent() = runBlocking { 32 | val url = "http://192.168.1.60:8080/ccapi/ver100/contents/sd/100CANON/IMG_0362.JPG" 33 | println(canonCameraControlApi.getContent(url).byteStream()) 34 | } 35 | 36 | @Ignore("require Canon camera with CCAPI") 37 | @Test 38 | fun testStartLiveView() = runBlocking { 39 | canonCameraControlApi.startLiveView() 40 | } 41 | 42 | @Ignore("require Canon camera with CCAPI") 43 | @Test 44 | fun testGetLiveView() = runBlocking { 45 | println(canonCameraControlApi.getLiveView().byteStream()) 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/test/java/io/numbersprotocol/starlingcapture/util/EncodingTest.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.util 2 | 3 | import org.junit.Assert.assertArrayEquals 4 | import org.junit.Test 5 | 6 | class EncodingTest { 7 | 8 | @Test 9 | fun testByteArrayToHexConversion() { 10 | val bytes = "hello".toByteArray(Charsets.UTF_8) 11 | assertArrayEquals(bytes, bytes.asHex().hexAsByteArray()) 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/test/java/io/numbersprotocol/starlingcapture/util/ValidatorTest.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.util 2 | 3 | import org.junit.Assert.assertFalse 4 | import org.junit.Assert.assertTrue 5 | import org.junit.Test 6 | 7 | class ValidatorTest { 8 | 9 | @Test 10 | fun testIpAddressValidator() { 11 | val legal1 = "192.168.1.2:8080" 12 | assertTrue(legal1.isIpAddress()) 13 | val legal2 = "192.168.1.2" 14 | assertTrue(legal2.isIpAddress()) 15 | val illegal1 = "256.1.1.1:65535" 16 | assertFalse(illegal1.isIpAddress()) 17 | val illegal2 = "1.1.1.1:70000" 18 | assertFalse(illegal2.isIpAddress()) 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/testInternal/java/io/numbersprotocol/starlingcapture/publisher/numbers_storage/NumbersStorageApiTest.kt: -------------------------------------------------------------------------------- 1 | package io.numbersprotocol.starlingcapture.publisher.numbers_storage 2 | 3 | import io.numbersprotocol.starlingcapture.BuildConfig 4 | import kotlinx.coroutines.runBlocking 5 | import org.junit.Assert.assertNotNull 6 | import org.junit.Assert.assertTrue 7 | import org.junit.Ignore 8 | import org.junit.Test 9 | import retrofit2.HttpException 10 | 11 | class NumbersStorageApiTest { 12 | 13 | private val numbersStorageApi = NumbersStorageApi.create() 14 | private val userName = "tester" 15 | private val email = "tester@test.com" 16 | private val password = "testpassword" 17 | 18 | @Test 19 | fun testBaseUrlGenerated() { 20 | assertTrue(BuildConfig.NUMBERS_STORAGE_BASE_URL.isNotEmpty() && BuildConfig.NUMBERS_STORAGE_BASE_URL != "null") 21 | } 22 | 23 | @Ignore("Numbers Storage Backend") 24 | @Test 25 | fun testCreateUser() = runBlocking { 26 | try { 27 | val result = numbersStorageApi.createUser(userName, email, password) 28 | assertTrue(result.userName == userName && result.email == email) 29 | println(result) 30 | } catch (e: Exception) { 31 | handleHttpException(e as HttpException) 32 | } 33 | } 34 | 35 | @Ignore("Numbers Storage Backend") 36 | @Test 37 | fun testLogin() = runBlocking { 38 | try { 39 | val result = numbersStorageApi.login(email, password) 40 | assertTrue(result.authToken.isNotBlank()) 41 | println(result) 42 | } catch (e: Exception) { 43 | handleHttpException(e as HttpException) 44 | } 45 | } 46 | 47 | @Ignore("Numbers Storage Backend") 48 | @Test 49 | fun testLogout() = runBlocking { 50 | val token = "token 6f532ad2a412db68c7fe3474714859572486f3b0" 51 | val result = numbersStorageApi.logout(token) 52 | println(result.message()) 53 | assertTrue(result.message() == "No Content" || result.message() == "Unauthorized") 54 | } 55 | 56 | private fun handleHttpException(e: HttpException) { 57 | println(e) 58 | val errorString = e.response()?.errorBody()?.string() 59 | assertNotNull(errorString) 60 | println(errorString) 61 | } 62 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | ext.kotlin_version = "1.4.10" 4 | repositories { 5 | google() 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.1" 12 | classpath "com.google.gms:google-services:4.3.4" 13 | classpath "com.google.firebase:firebase-crashlytics-gradle:2.3.0" 14 | // NOTE: Do not place your application dependencies here; they belong 15 | // in the individual module build.gradle files 16 | } 17 | } 18 | 19 | allprojects { 20 | repositories { 21 | google() 22 | jcenter() 23 | maven { url "https://jitpack.io" } 24 | maven { url "https://raw.githubusercontent.com/guardianproject/gpmaven/master" } 25 | } 26 | } 27 | 28 | task clean(type: Delete) { 29 | delete rootProject.buildDir 30 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | # Enable incremental annotation processor for data binding 23 | android.dataBinding.incremental=true 24 | # Run kapt tasks in parallel 25 | kapt.use.worker.api=true 26 | # Enable compile avoidance for kapt 27 | kapt.include.compile.classpath=false 28 | # Disable testOnly flag for builds 29 | android.injected.testOnly=false -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbersprotocol/starling-capture/635115c9d575c957916fa5eddcec4ba3b159a95f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Oct 13 11:08:58 CST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ":app" 2 | rootProject.name = "Starling Capture" -------------------------------------------------------------------------------- /util/verification/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.linting.enabled": true, 3 | "python.linting.pylintEnabled": true, 4 | "python.linting.mypyEnabled": true, 5 | "python.linting.pylintUseMinimalCheckers": false, 6 | "python.pythonPath": ".venv/bin/python" 7 | } -------------------------------------------------------------------------------- /util/verification/README.md: -------------------------------------------------------------------------------- 1 | # Verification Tool 2 | 3 | ## Getting Started 4 | 5 | ### Environment 6 | 7 | * Visual Studio Code 8 | * Poetry 1.0.10 or later 9 | 10 | ### Installation 11 | 12 | Install the dependencies with poetry. 13 | 14 | ``` bash 15 | poetry install --no-root 16 | ``` 17 | 18 | Poetry will create the virtual environment in `.venv/` folder. Visual Studio Code will automatically select the corresponding Python interpreter with the `.vscode/settings.json` file. No manual configuration required. 19 | 20 | ``` json 21 | { 22 | "python.pythonPath": ".venv/bin/python" 23 | } 24 | ``` 25 | 26 | If you are using the built-in terminal in Visual Studio Code, remember to restart it by killing the terminal first in order to enter the virtual shell session automatically. 27 | 28 | ## Verify! 29 | 30 | Currently, this tool only verify the digital signatures generated from Android OpenSSL provider. 31 | 32 | To verify the json files generated from the Starling Capture app, 33 | 34 | ``` python 35 | try: 36 | result = verify('./tests/assets/information.json', './tests/assets/signature.json') 37 | except Error as e: 38 | print(e) 39 | result = False 40 | ``` 41 | 42 | See the [tests](./tests/test_verification.py) for details. 43 | 44 | ## Test 45 | 46 | To run tests, 47 | 48 | ``` bash 49 | poetry run pytest 50 | ``` 51 | -------------------------------------------------------------------------------- /util/verification/poetry.toml: -------------------------------------------------------------------------------- 1 | [virtualenvs] 2 | in-project = true 3 | -------------------------------------------------------------------------------- /util/verification/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "verification" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Sean Wu "] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.8" 9 | ecdsa = "^0.16" 10 | web3 = "^5.30.0" 11 | pysha3 = "^1.0.2" 12 | 13 | [tool.poetry.dev-dependencies] 14 | pytest = "^6.1" 15 | pylint = "^2.6.0" 16 | mypy = "^0.790" 17 | autopep8 = "^1.5.4" 18 | 19 | [build-system] 20 | requires = ["poetry>=0.12"] 21 | build-backend = "poetry.masonry.api" 22 | -------------------------------------------------------------------------------- /util/verification/starling_capture_verifier.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | from verification import verify 4 | 5 | 6 | def parse_args(): 7 | ap = argparse.ArgumentParser() 8 | ap.add_argument( 9 | '--information-filepath', 10 | required=True, 11 | help='The metadata of Starling Capture.' 12 | ) 13 | ap.add_argument( 14 | '--signature-filepath', 15 | required=True, 16 | help='The digital signature of Starling Capture.' 17 | ) 18 | return ap.parse_args() 19 | 20 | 21 | if __name__ == '__main__': 22 | args = parse_args() 23 | verify(args.information_filepath, args.signature_filepath) 24 | -------------------------------------------------------------------------------- /util/verification/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbersprotocol/starling-capture/635115c9d575c957916fa5eddcec4ba3b159a95f/util/verification/tests/__init__.py -------------------------------------------------------------------------------- /util/verification/tests/assets/README.md: -------------------------------------------------------------------------------- 1 | # Quick Start 2 | 3 | 4 | 5 | # Example Key Pairs 6 | 7 | SW key pair 8 | 9 | 1. default public key 10 | 11 | ``` 12 | 3059301306072a8648ce3d020106082a8648ce3d03010703420004813afd7f6ba95fdebeac7812c7ab5af9ca59547b2a73f5aa75accca4e4ad2eb6849d4948a9fcfb8c2b890ba0dfbe4463bbfe4ac163981a93517bfb34598fc850 13 | ``` 14 | 15 | 2. default private key 16 | 17 | ``` 18 | 308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b02010104204ebe7c7bff6b0e8954c061cb360fa777821b65aade8cc4b64e4b8c95c0aafb44a14403420004813afd7f6ba95fdebeac7812c7ab5af9ca59547b2a73f5aa75accca4e4ad2eb6849d4948a9fcfb8c2b890ba0dfbe4463bbfe4ac163981a93517bfb34598fc850 19 | ``` 20 | 21 | Zion 22 | 1. mnemonics 23 | 24 | ``` 25 | pet effort swear system breeze arrow proof green disorder retreat fox bargain 26 | ``` 27 | 28 | 2. private key 29 | 30 | ``` 31 | 7b465693570629061ab4b1ae1b1da3ebeec9ee675518131be591e94db187d235 32 | ``` 33 | 34 | 3. wallet address 35 | 36 | ``` 37 | 0x5f5AD77F4f924232a6E486216Ddefba8a732b96B 38 | ``` 39 | 40 | Session key pair 41 | 42 | 1. Public key 43 | 44 | ``` 45 | 3059301306072a8648ce3d020106082a8648ce3d030107034200045f4876d8ae65477b5985484ee9d6fd2a5a78b2bcebdaf090f7c9bb85c89f4de55f909e5663caac8cd6e958d8eff50a029ec4c7245aa1ea1716787c62229a9a34 46 | ``` 47 | 48 | 2. Private key 49 | 50 | ``` 51 | 308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b020101042022619451d73a16a0c41c3507435948af61f1a8dbbb901d5b18a1cf2ff648cafea144034200045f4876d8ae65477b5985484ee9d6fd2a5a78b2bcebdaf090f7c9bb85c89f4de55f909e5663caac8cd6e958d8eff50a029ec4c7245aa1ea1716787c62229a9a34 52 | ``` 53 | 54 | Public key signature 55 | 56 | ``` 57 | d779896950561be2f2532758ba977d1a04280e98c86d7cde4b1b58f570ec4a6530c2c163b997ae3dcaa843e922f0c888e591adde46b84f1b20001ad68d47686b1b 58 | ``` 59 | 60 | # Example Assets 61 | 62 | 1. HW: Exodus 1s 63 | 1. Starling Capture: 1.8.0-master-debug 64 | 1. Enable Zion: true 65 | 1. Enable Session Signature: false 66 | 1. Asset hash: `ede8c1b820a19e0a438ce28873501fafd33886a21cf4e3c1ffea24f523c857c4` 67 | 1. AndroidOpenSSL verification: pass 68 | 1. [Download link](https://app.asana.com/app/asana/-/get_asset?asset_id=1202279149900570&force_download) 69 | -------------------------------------------------------------------------------- /util/verification/tests/assets/zion-classic/ede8c1b820a19e0a438ce28873501fafd33886a21cf4e3c1ffea24f523c857c4/caption.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbersprotocol/starling-capture/635115c9d575c957916fa5eddcec4ba3b159a95f/util/verification/tests/assets/zion-classic/ede8c1b820a19e0a438ce28873501fafd33886a21cf4e3c1ffea24f523c857c4/caption.txt -------------------------------------------------------------------------------- /util/verification/tests/assets/zion-classic/ede8c1b820a19e0a438ce28873501fafd33886a21cf4e3c1ffea24f523c857c4/ede8c1b820a19e0a438ce28873501fafd33886a21cf4e3c1ffea24f523c857c4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbersprotocol/starling-capture/635115c9d575c957916fa5eddcec4ba3b159a95f/util/verification/tests/assets/zion-classic/ede8c1b820a19e0a438ce28873501fafd33886a21cf4e3c1ffea24f523c857c4/ede8c1b820a19e0a438ce28873501fafd33886a21cf4e3c1ffea24f523c857c4.jpg -------------------------------------------------------------------------------- /util/verification/tests/assets/zion-classic/ede8c1b820a19e0a438ce28873501fafd33886a21cf4e3c1ffea24f523c857c4/signature.json: -------------------------------------------------------------------------------- 1 | [{"proofHash":"ede8c1b820a19e0a438ce28873501fafd33886a21cf4e3c1ffea24f523c857c4","provider":"AndroidOpenSSL","signature":"3044022055d9d72e9495b6136bd69fb15a6736a393df4f4ad6217aa28eea290e95260ff602204ab940007c98a213887fd9956b9aa7564ea9062e23158fcf84cce0993d57c0db","publicKey":"3059301306072a8648ce3d020106082a8648ce3d03010703420004813afd7f6ba95fdebeac7812c7ab5af9ca59547b2a73f5aa75accca4e4ad2eb6849d4948a9fcfb8c2b890ba0dfbe4463bbfe4ac163981a93517bfb34598fc850"},{"proofHash":"ede8c1b820a19e0a438ce28873501fafd33886a21cf4e3c1ffea24f523c857c4","provider":"Zion","signature":"d0d40e2dd333df8ef565f26dd4206c8b750334b245b7eaed56dab0e42af111e0130723a0f5c48411d920d23c727eb923f62189d255ce2ca1fe8ec7151f1c19ea1c","publicKey":"Receive:\n03aced43f9dddc120291f5cdf73580fbb592b5b21054ce61eb73cbaf98efcbe82e\n\nSend:\n03aced43f9dddc120291f5cdf73580fbb592b5b21054ce61eb73cbaf98efcbe82e"}] -------------------------------------------------------------------------------- /util/verification/tests/assets/zion-session-classic/b94c5af28e3aaba7578f6737aeab4d2eae4e08a2e7a52a1d55991d92b9552225/b94c5af28e3aaba7578f6737aeab4d2eae4e08a2e7a52a1d55991d92b9552225.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbersprotocol/starling-capture/635115c9d575c957916fa5eddcec4ba3b159a95f/util/verification/tests/assets/zion-session-classic/b94c5af28e3aaba7578f6737aeab4d2eae4e08a2e7a52a1d55991d92b9552225/b94c5af28e3aaba7578f6737aeab4d2eae4e08a2e7a52a1d55991d92b9552225.jpg -------------------------------------------------------------------------------- /util/verification/tests/assets/zion-session-classic/b94c5af28e3aaba7578f6737aeab4d2eae4e08a2e7a52a1d55991d92b9552225/caption.txt: -------------------------------------------------------------------------------- 1 | Zion, session, 1.8.0 -------------------------------------------------------------------------------- /util/verification/tests/assets/zion-session-classic/b94c5af28e3aaba7578f6737aeab4d2eae4e08a2e7a52a1d55991d92b9552225/signature.json: -------------------------------------------------------------------------------- 1 | [{"proofHash":"b94c5af28e3aaba7578f6737aeab4d2eae4e08a2e7a52a1d55991d92b9552225","provider":"AndroidOpenSSL","signature":"30460221008f2d1190bcaca048bb7169d2ce3497435a046112f1f967669f1f9a8ca508be07022100987e0e0643fc2c098ac65985d6ac7b1e2216fd08b7ba6787da6eaa410115b627","publicKey":"3059301306072a8648ce3d020106082a8648ce3d030107034200048ef4f308290e1574e3af497e302daa7186bff2c3b81ed46c2d4472ab5fc7c089a8c995c08ca5eaad8ff677560bbc5111ca7c76816ed155f6cdeba46ab905ba8b"},{"proofHash":"b94c5af28e3aaba7578f6737aeab4d2eae4e08a2e7a52a1d55991d92b9552225","provider":"Zion","signature":"30460221008e58c8fe07371c60e0dad2b33f0ddd6fbd012ed09a7db26ac032f5297b2df5000221008e065bf5a793e6d13a83fbbfeee5710799ca42ebdcf6301b5b4136509706ca37","publicKey":"Session:\n3059301306072a8648ce3d020106082a8648ce3d03010703420004f749217e283ee7d09b1fddd2c7b23cb9a54dabca82af243c5587810aab5967dd74678581d845715e3f309e681a254da21492a2b09e0b0478157b7302408e875b\n\nReceive:\n03aced43f9dddc120291f5cdf73580fbb592b5b21054ce61eb73cbaf98efcbe82e\n\nSend:\n03aced43f9dddc120291f5cdf73580fbb592b5b21054ce61eb73cbaf98efcbe82e"}] -------------------------------------------------------------------------------- /util/verification/tests/assets/zion-session/855ba1f6548f710fe3e67f9bfc943c4483bd50a432480ce23061af96d672fd3b/855ba1f6548f710fe3e67f9bfc943c4483bd50a432480ce23061af96d672fd3b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbersprotocol/starling-capture/635115c9d575c957916fa5eddcec4ba3b159a95f/util/verification/tests/assets/zion-session/855ba1f6548f710fe3e67f9bfc943c4483bd50a432480ce23061af96d672fd3b/855ba1f6548f710fe3e67f9bfc943c4483bd50a432480ce23061af96d672fd3b.jpg -------------------------------------------------------------------------------- /util/verification/tests/assets/zion-session/855ba1f6548f710fe3e67f9bfc943c4483bd50a432480ce23061af96d672fd3b/caption.txt: -------------------------------------------------------------------------------- 1 | Zion, session, 1.8.1 -------------------------------------------------------------------------------- /util/verification/tests/assets/zion-session/855ba1f6548f710fe3e67f9bfc943c4483bd50a432480ce23061af96d672fd3b/signature.json: -------------------------------------------------------------------------------- 1 | [{"proofHash":"855ba1f6548f710fe3e67f9bfc943c4483bd50a432480ce23061af96d672fd3b","provider":"AndroidOpenSSL","signature":"30450220042caf010e49c5be0c4e549165466270e090262ce23be1e792661850b2a933820221008ae17fccc00a8b6aa3353cf86498156a94ff6f3c164ae86a996918c9c37053a7","publicKey":"3059301306072a8648ce3d020106082a8648ce3d03010703420004f6e017934e0f44e3b6a37569a43c6d3714115fde712bf0c6d8f907298ad3da629316ce921fc5a466eeead675bb00256143dd6693e3c6a86749b4e50ed1ad1299"},{"proofHash":"855ba1f6548f710fe3e67f9bfc943c4483bd50a432480ce23061af96d672fd3b","provider":"Zion","signature":"30450220758445d2fdc8929027349def4795a5fde097b61c131923843e5c4fa84c4bcae5022100bf911a1c8b50faea041ae94397ed722fed7120af6ed5a4d32c8c095608db02c2","publicKey":"Session:\n3059301306072a8648ce3d020106082a8648ce3d03010703420004b0edf158c50aff005b933abdaf3d667116a17ca3dd7f41ed95b93ce977ef537c07b05c85750f7d791bd1f4394f85b0f8b73500f9998018b26e152ba44ace50e8\n\nSessionSignature:\nd6c4f8882def4ab0febf7d2b9a7c46a67c48a2ead5ef5285cb8776fffea995057b3ae03eb017b3721a1820be91ee21b59eb208e86faaddbaa389c5bad421e9201b\n\nReceive:\n03aced43f9dddc120291f5cdf73580fbb592b5b21054ce61eb73cbaf98efcbe82e\n\nSend:\n03aced43f9dddc120291f5cdf73580fbb592b5b21054ce61eb73cbaf98efcbe82e"}] -------------------------------------------------------------------------------- /util/verification/tests/assets/zion/9f9af13673a0ea1e608fdd4c6ce6c746dd03dd58d184705ea7d1e1ef175e5640/9f9af13673a0ea1e608fdd4c6ce6c746dd03dd58d184705ea7d1e1ef175e5640.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbersprotocol/starling-capture/635115c9d575c957916fa5eddcec4ba3b159a95f/util/verification/tests/assets/zion/9f9af13673a0ea1e608fdd4c6ce6c746dd03dd58d184705ea7d1e1ef175e5640/9f9af13673a0ea1e608fdd4c6ce6c746dd03dd58d184705ea7d1e1ef175e5640.jpg -------------------------------------------------------------------------------- /util/verification/tests/assets/zion/9f9af13673a0ea1e608fdd4c6ce6c746dd03dd58d184705ea7d1e1ef175e5640/caption.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/numbersprotocol/starling-capture/635115c9d575c957916fa5eddcec4ba3b159a95f/util/verification/tests/assets/zion/9f9af13673a0ea1e608fdd4c6ce6c746dd03dd58d184705ea7d1e1ef175e5640/caption.txt -------------------------------------------------------------------------------- /util/verification/tests/assets/zion/9f9af13673a0ea1e608fdd4c6ce6c746dd03dd58d184705ea7d1e1ef175e5640/signature.json: -------------------------------------------------------------------------------- 1 | [{"proofHash":"9f9af13673a0ea1e608fdd4c6ce6c746dd03dd58d184705ea7d1e1ef175e5640","provider":"AndroidOpenSSL","signature":"3046022100c56ed2788302a30fff448e863e9db3b41b8da1c9a81dbba1e1823e5b184d553f022100c36b2e2ade92164945ceb28a569adfac39325e2b934a5568a41b6f4ec41dcf72","publicKey":"3059301306072a8648ce3d020106082a8648ce3d03010703420004f6e017934e0f44e3b6a37569a43c6d3714115fde712bf0c6d8f907298ad3da629316ce921fc5a466eeead675bb00256143dd6693e3c6a86749b4e50ed1ad1299"},{"proofHash":"9f9af13673a0ea1e608fdd4c6ce6c746dd03dd58d184705ea7d1e1ef175e5640","provider":"Zion","signature":"c018ff36d30b6482ac3de02111493b49d85027acda94fd5585429d3c8d13d8777666d3e334c9922477627642279b5d8e245c20ba877816bc2917c2eac363c9251c","publicKey":"Receive:\n03aced43f9dddc120291f5cdf73580fbb592b5b21054ce61eb73cbaf98efcbe82e\n\nSend:\n03aced43f9dddc120291f5cdf73580fbb592b5b21054ce61eb73cbaf98efcbe82e"}] -------------------------------------------------------------------------------- /util/verification/verification/__init__.py: -------------------------------------------------------------------------------- 1 | from .verification import verify 2 | from .verification import verify_ecdsa_with_sha256 3 | from .verification import verify_ethereum_signature 4 | from .verification import generate_sha256_from_filepath 5 | from .verification import get_signature_from_information_file 6 | from .verification import hex_string_to_bytes 7 | from .verification import read_json_file 8 | from .verification import read_text_file 9 | 10 | __version__ = '0.1.0' 11 | -------------------------------------------------------------------------------- /util/verification/verify.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CAPTURE_DIR="$1" 4 | 5 | INFORMATION_FILEPATH="${CAPTURE_DIR}/information.json" 6 | SIGNATURE_FILEPATH="${CAPTURE_DIR}/signature.json" 7 | 8 | poetry run python3 starling_capture_verifier.py \ 9 | --information-filepath ${INFORMATION_FILEPATH} \ 10 | --signature-filepath ${SIGNATURE_FILEPATH} 11 | --------------------------------------------------------------------------------