├── .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 | 
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 | 
--------------------------------------------------------------------------------
/.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 | 
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 |
4 |
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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
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 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/proof.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/storage_action_mode.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/storage_option.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/zion.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------