| @@ -0,0 +1,116 @@ | |||
| <component name="ProjectCodeStyleConfiguration"> | |||
| <code_scheme name="Project" version="173"> | |||
| <codeStyleSettings language="XML"> | |||
| <indentOptions> | |||
| <option name="CONTINUATION_INDENT_SIZE" value="4" /> | |||
| </indentOptions> | |||
| <arrangement> | |||
| <rules> | |||
| <section> | |||
| <rule> | |||
| <match> | |||
| <AND> | |||
| <NAME>xmlns:android</NAME> | |||
| <XML_ATTRIBUTE /> | |||
| <XML_NAMESPACE>^$</XML_NAMESPACE> | |||
| </AND> | |||
| </match> | |||
| </rule> | |||
| </section> | |||
| <section> | |||
| <rule> | |||
| <match> | |||
| <AND> | |||
| <NAME>xmlns:.*</NAME> | |||
| <XML_ATTRIBUTE /> | |||
| <XML_NAMESPACE>^$</XML_NAMESPACE> | |||
| </AND> | |||
| </match> | |||
| <order>BY_NAME</order> | |||
| </rule> | |||
| </section> | |||
| <section> | |||
| <rule> | |||
| <match> | |||
| <AND> | |||
| <NAME>.*:id</NAME> | |||
| <XML_ATTRIBUTE /> | |||
| <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> | |||
| </AND> | |||
| </match> | |||
| </rule> | |||
| </section> | |||
| <section> | |||
| <rule> | |||
| <match> | |||
| <AND> | |||
| <NAME>.*:name</NAME> | |||
| <XML_ATTRIBUTE /> | |||
| <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> | |||
| </AND> | |||
| </match> | |||
| </rule> | |||
| </section> | |||
| <section> | |||
| <rule> | |||
| <match> | |||
| <AND> | |||
| <NAME>name</NAME> | |||
| <XML_ATTRIBUTE /> | |||
| <XML_NAMESPACE>^$</XML_NAMESPACE> | |||
| </AND> | |||
| </match> | |||
| </rule> | |||
| </section> | |||
| <section> | |||
| <rule> | |||
| <match> | |||
| <AND> | |||
| <NAME>style</NAME> | |||
| <XML_ATTRIBUTE /> | |||
| <XML_NAMESPACE>^$</XML_NAMESPACE> | |||
| </AND> | |||
| </match> | |||
| </rule> | |||
| </section> | |||
| <section> | |||
| <rule> | |||
| <match> | |||
| <AND> | |||
| <NAME>.*</NAME> | |||
| <XML_ATTRIBUTE /> | |||
| <XML_NAMESPACE>^$</XML_NAMESPACE> | |||
| </AND> | |||
| </match> | |||
| <order>BY_NAME</order> | |||
| </rule> | |||
| </section> | |||
| <section> | |||
| <rule> | |||
| <match> | |||
| <AND> | |||
| <NAME>.*</NAME> | |||
| <XML_ATTRIBUTE /> | |||
| <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> | |||
| </AND> | |||
| </match> | |||
| <order>ANDROID_ATTRIBUTE_ORDER</order> | |||
| </rule> | |||
| </section> | |||
| <section> | |||
| <rule> | |||
| <match> | |||
| <AND> | |||
| <NAME>.*</NAME> | |||
| <XML_ATTRIBUTE /> | |||
| <XML_NAMESPACE>.*</XML_NAMESPACE> | |||
| </AND> | |||
| </match> | |||
| <order>BY_NAME</order> | |||
| </rule> | |||
| </section> | |||
| </rules> | |||
| </arrangement> | |||
| </codeStyleSettings> | |||
| </code_scheme> | |||
| </component> | |||
| @@ -3,11 +3,11 @@ apply plugin: 'kotlin-android-extensions' | |||
| apply plugin: 'kotlin-android' | |||
| android { | |||
| compileSdkVersion 28 | |||
| compileSdkVersion 29 | |||
| defaultConfig { | |||
| applicationId "lu.circl.mispbump" | |||
| minSdkVersion 23 | |||
| targetSdkVersion 28 | |||
| targetSdkVersion 29 | |||
| versionCode 1 | |||
| versionName "1.0" | |||
| testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | |||
| @@ -26,6 +26,10 @@ android { | |||
| buildToolsVersion = '29.0.1' | |||
| } | |||
| repositories { | |||
| mavenCentral() | |||
| } | |||
| dependencies { | |||
| // android | |||
| implementation 'com.google.android.material:material:1.0.0' | |||
| @@ -37,9 +41,9 @@ dependencies { | |||
| implementation 'androidx.preference:preference:1.1.0-rc01' | |||
| // retrofit | |||
| implementation 'com.squareup.retrofit2:retrofit:2.6.0' | |||
| implementation 'com.squareup.retrofit2:converter-gson:2.5.0' | |||
| implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0' | |||
| implementation 'com.squareup.retrofit2:retrofit:2.6.1' | |||
| implementation 'com.squareup.retrofit2:converter-gson:2.6.1' | |||
| implementation 'com.squareup.okhttp3:logging-interceptor:4.1.0' | |||
| // barcode reading | |||
| implementation 'com.google.android.gms:play-services-vision:18.0.0' | |||
| @@ -53,7 +57,5 @@ dependencies { | |||
| androidTestImplementation 'androidx.test:runner:1.2.0' | |||
| androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' | |||
| implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" | |||
| } | |||
| repositories { | |||
| mavenCentral() | |||
| implementation project(path: ':expandablecardview') | |||
| } | |||
| @@ -1,12 +1,8 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <manifest | |||
| xmlns:android="http://schemas.android.com/apk/res/android" | |||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:tools="http://schemas.android.com/tools" | |||
| package="lu.circl.mispbump"> | |||
| <uses-permission android:name="android.permission.CAMERA"/> | |||
| <uses-permission android:name="android.permission.INTERNET"/> | |||
| <application | |||
| android:allowBackup="false" | |||
| android:hardwareAccelerated="true" | |||
| @@ -16,43 +12,44 @@ | |||
| android:supportsRtl="true" | |||
| android:theme="@style/AppTheme" | |||
| tools:ignore="GoogleAppIndexingWarning"> | |||
| <activity android:name=".activities.StartUpActivity"> | |||
| <activity android:name=".activities.NetworkTestActivity"></activity> | |||
| <activity android:name=".activities.UploadActivity" /> | |||
| <activity android:name=".activities.LauncherActivity"> | |||
| <intent-filter> | |||
| <action android:name="android.intent.action.MAIN"/> | |||
| <action android:name="android.intent.action.MAIN" /> | |||
| <category android:name="android.intent.category.LAUNCHER"/> | |||
| <category android:name="android.intent.category.LAUNCHER" /> | |||
| </intent-filter> | |||
| </activity> | |||
| <activity | |||
| android:name=".activities.LoginActivity" | |||
| android:label="@string/login"/> | |||
| android:label="@string/login" /> | |||
| <activity | |||
| android:name=".activities.HomeActivity" | |||
| android:label="@string/app_name"/> | |||
| android:label="@string/app_name" /> | |||
| <activity | |||
| android:name=".activities.ExchangeActivity" | |||
| android:configChanges="orientation|screenSize" | |||
| android:parentActivityName=".activities.HomeActivity" | |||
| android:screenOrientation="portrait" | |||
| android:theme="@style/AppTheme.Translucent"/> | |||
| <activity | |||
| android:name=".activities.UploadInfoActivity" | |||
| android:configChanges="orientation|screenSize" | |||
| android:parentActivityName=".activities.HomeActivity"/> | |||
| android:theme="@style/AppTheme.Translucent" /> | |||
| <activity | |||
| android:name=".activities.UploadActivity" | |||
| android:name=".activities.SyncInfoDetailActivity" | |||
| android:configChanges="orientation|screenSize" | |||
| android:label="Upload" | |||
| android:parentActivityName=".activities.HomeActivity"/> | |||
| android:label="@string/sync_details_activity_label" | |||
| android:parentActivityName=".activities.HomeActivity" /> | |||
| <activity | |||
| android:name=".activities.PreferenceActivity" | |||
| android:label="@string/settings" | |||
| android:parentActivityName=".activities.HomeActivity"/> | |||
| android:parentActivityName=".activities.HomeActivity" /> | |||
| <activity | |||
| android:name=".activities.ProfileActivity" | |||
| android:label="Profile" | |||
| android:parentActivityName=".activities.HomeActivity" | |||
| android:theme="@style/AppTheme.Translucent"/> | |||
| android:theme="@style/AppTheme.Translucent" /> | |||
| </application> | |||
| <uses-permission android:name="android.permission.CAMERA" /> | |||
| <uses-permission android:name="android.permission.INTERNET" /> | |||
| </manifest> | |||
| @@ -22,16 +22,14 @@ import com.google.gson.JsonSyntaxException; | |||
| import java.security.NoSuchAlgorithmException; | |||
| import java.security.spec.InvalidKeySpecException; | |||
| import java.util.List; | |||
| import lu.circl.mispbump.R; | |||
| import lu.circl.mispbump.auxiliary.DialogManager; | |||
| import lu.circl.mispbump.auxiliary.PreferenceManager; | |||
| import lu.circl.mispbump.auxiliary.QrCodeGenerator; | |||
| import lu.circl.mispbump.auxiliary.RandomString; | |||
| import lu.circl.mispbump.fragments.CameraFragment; | |||
| import lu.circl.mispbump.models.ExchangeInformation; | |||
| import lu.circl.mispbump.models.SyncInformation; | |||
| import lu.circl.mispbump.models.UploadInformation; | |||
| import lu.circl.mispbump.models.restModels.Server; | |||
| import lu.circl.mispbump.security.DiffieHellman; | |||
| @@ -40,7 +38,6 @@ public class ExchangeActivity extends AppCompatActivity { | |||
| private PreferenceManager preferenceManager; | |||
| private QrCodeGenerator qrCodeGenerator; | |||
| private DiffieHellman diffieHellman; | |||
| private UploadInformation uploadInformation; | |||
| private CameraFragment cameraFragment; | |||
| @@ -50,6 +47,8 @@ public class ExchangeActivity extends AppCompatActivity { | |||
| private ImageView qrCode; | |||
| private ImageButton prevButton, nextButton; | |||
| private SyncInformation syncInformation; | |||
| private Bitmap publicKeyQr, dataQr; | |||
| private SyncState currentSyncState; | |||
| @@ -67,9 +66,10 @@ public class ExchangeActivity extends AppCompatActivity { | |||
| initViews(); | |||
| initCamera(); | |||
| uploadInformation = new UploadInformation(); | |||
| publicKeyQr = generatePublicKeyBitmap(); | |||
| syncInformation = new SyncInformation(); | |||
| setSyncState(SyncState.KEY_EXCHANGE); | |||
| } | |||
| @@ -105,24 +105,23 @@ public class ExchangeActivity extends AppCompatActivity { | |||
| fragmentTransaction.commit(); | |||
| } | |||
| private ExchangeInformation generateSyncExchangeInformation() { | |||
| ExchangeInformation exchangeInformation = new ExchangeInformation(); | |||
| exchangeInformation.setOrganisation(preferenceManager.getUserOrganisation().toSyncOrganisation()); | |||
| exchangeInformation.setSyncUser(preferenceManager.getUserInfo().toSyncUser()); | |||
| exchangeInformation.setServer(new Server(preferenceManager.getUserCredentials().first)); | |||
| return exchangeInformation; | |||
| } | |||
| private Bitmap generatePublicKeyBitmap() { | |||
| return qrCodeGenerator.generateQrCode(DiffieHellman.publicKeyToString(diffieHellman.getPublicKey())); | |||
| } | |||
| private Bitmap generateLocalSyncInfoBitmap() { | |||
| uploadInformation.setLocal(generateLocalSyncInfo()); | |||
| return qrCodeGenerator.generateQrCode(diffieHellman.encrypt(new Gson().toJson(uploadInformation.getLocal()))); | |||
| } | |||
| private SyncInformation generateLocalSyncInfo() { | |||
| SyncInformation syncInformation = new SyncInformation(); | |||
| syncInformation.organisation = preferenceManager.getUserOrganisation().toSyncOrganisation(); | |||
| syncInformation.syncUserAuthkey = new RandomString(40).nextString(); | |||
| syncInformation.baseUrl = preferenceManager.getUserCredentials().first; | |||
| syncInformation.syncUserPassword = new RandomString(16).nextString(); | |||
| syncInformation.syncUserEmail = preferenceManager.getUserInfo().getEmail(); | |||
| return syncInformation; | |||
| ExchangeInformation exchangeInformation = generateSyncExchangeInformation(); | |||
| syncInformation.setLocal(exchangeInformation); | |||
| return qrCodeGenerator.generateQrCode(diffieHellman.encrypt(new Gson().toJson(exchangeInformation))); | |||
| } | |||
| @@ -275,34 +274,9 @@ public class ExchangeActivity extends AppCompatActivity { | |||
| break; | |||
| case DATA_EXCHANGE: | |||
| try { | |||
| final SyncInformation remoteSyncInfo = new Gson().fromJson(diffieHellman.decrypt(qrData), SyncInformation.class); | |||
| final List<UploadInformation> uploadInformationList = preferenceManager.getUploadInformationList(); | |||
| for (final UploadInformation ui : uploadInformationList) { | |||
| if (ui.getRemote().organisation.getUuid().equals(remoteSyncInfo.organisation.getUuid())) { | |||
| DialogManager.syncAlreadyExistsDialog(ui.getRemote(), remoteSyncInfo, ExchangeActivity.this, new DialogManager.IDialogFeedback() { | |||
| @Override | |||
| public void positive() { | |||
| // update remote info only | |||
| uploadInformation.setUuid(ui.getUuid()); | |||
| uploadInformation.setDate(); | |||
| } | |||
| @Override | |||
| public void negative() { | |||
| // replace credentials too | |||
| uploadInformationList.remove(ui); | |||
| preferenceManager.setUploadInformationList(uploadInformationList); | |||
| } | |||
| }); | |||
| break; | |||
| } | |||
| } | |||
| uploadInformation.setRemote(remoteSyncInfo); | |||
| preferenceManager.addUploadInformation(uploadInformation); | |||
| ExchangeInformation remoteSyncInfo = new Gson().fromJson(diffieHellman.decrypt(qrData), ExchangeInformation.class); | |||
| syncInformation.populateRemoteExchangeInformation(remoteSyncInfo); | |||
| preferenceManager.addSyncInformation(syncInformation); | |||
| setSyncState(SyncState.DATA_EXCHANGE_DONE); | |||
| } catch (JsonSyntaxException e) { | |||
| if (currentReadQrStatus == ReadQrStatus.PENDING) { | |||
| @@ -340,10 +314,8 @@ public class ExchangeActivity extends AppCompatActivity { | |||
| setSyncState(SyncState.DATA_EXCHANGE); | |||
| break; | |||
| case DATA_EXCHANGE_DONE: | |||
| uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.PENDING); | |||
| preferenceManager.addUploadInformation(uploadInformation); | |||
| Intent i = new Intent(ExchangeActivity.this, UploadInfoActivity.class); | |||
| i.putExtra(UploadInfoActivity.EXTRA_UPLOAD_INFO_UUID, uploadInformation.getUuid()); | |||
| Intent i = new Intent(ExchangeActivity.this, SyncInfoDetailActivity.class); | |||
| i.putExtra(SyncInfoDetailActivity.EXTRA_SYNC_INFO_UUID, syncInformation.getUuid()); | |||
| startActivity(i); | |||
| finish(); | |||
| break; | |||
| @@ -12,7 +12,6 @@ import android.widget.TextView; | |||
| import androidx.appcompat.app.AppCompatActivity; | |||
| import androidx.appcompat.widget.Toolbar; | |||
| import androidx.core.app.ActivityOptionsCompat; | |||
| import androidx.core.util.Pair; | |||
| import androidx.recyclerview.widget.LinearLayoutManager; | |||
| import androidx.recyclerview.widget.RecyclerView; | |||
| @@ -21,21 +20,18 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton; | |||
| import java.util.List; | |||
| import lu.circl.mispbump.R; | |||
| import lu.circl.mispbump.adapters.UploadInfoAdapter; | |||
| import lu.circl.mispbump.auxiliary.MispRestClient; | |||
| import lu.circl.mispbump.adapters.SyncInfoAdapter; | |||
| import lu.circl.mispbump.auxiliary.PreferenceManager; | |||
| import lu.circl.mispbump.interfaces.OnRecyclerItemClickListener; | |||
| import lu.circl.mispbump.models.SyncModel; | |||
| import lu.circl.mispbump.models.UploadInformation; | |||
| import lu.circl.mispbump.models.restModels.Server; | |||
| import lu.circl.mispbump.models.SyncInformation; | |||
| public class HomeActivity extends AppCompatActivity { | |||
| private List<UploadInformation> uploadInformationList; | |||
| private List<SyncInformation> syncInformationList; | |||
| private PreferenceManager preferenceManager; | |||
| private RecyclerView recyclerView; | |||
| private UploadInfoAdapter uploadInfoAdapter; | |||
| private SyncInfoAdapter syncInfoAdapter; | |||
| private TextView emptyRecyclerView; | |||
| @Override | |||
| @@ -90,55 +86,33 @@ public class HomeActivity extends AppCompatActivity { | |||
| private void initRecyclerView() { | |||
| recyclerView = findViewById(R.id.recyclerView); | |||
| recyclerView.setLayoutManager(new LinearLayoutManager(HomeActivity.this)); | |||
| uploadInfoAdapter = new UploadInfoAdapter(HomeActivity.this); | |||
| uploadInfoAdapter.setOnRecyclerPositionClickListener(onRecyclerItemClickListener()); | |||
| recyclerView.setAdapter(uploadInfoAdapter); | |||
| syncInfoAdapter = new SyncInfoAdapter(); | |||
| syncInfoAdapter.setOnRecyclerPositionClickListener(onRecyclerItemClickListener()); | |||
| recyclerView.setAdapter(syncInfoAdapter); | |||
| } | |||
| private void refreshRecyclerView() { | |||
| syncInformationList = preferenceManager.getSyncInformationList(); | |||
| Pair<String, String> credentials = preferenceManager.getUserCredentials(); | |||
| MispRestClient mispRestClient = MispRestClient.getInstance(credentials.first, credentials.second); | |||
| mispRestClient.getAllServers(new MispRestClient.AllServersCallback() { | |||
| @Override | |||
| public void success(Server[] servers) { | |||
| SyncModel.createFromServer(mispRestClient, servers[0], new SyncModel.InitializeWithServerObject() { | |||
| @Override | |||
| public void success(SyncModel syncModel) { | |||
| Log.d("DEBUG", syncModel.toString()); | |||
| } | |||
| @Override | |||
| public void failure(String error) { | |||
| } | |||
| }); | |||
| } | |||
| @Override | |||
| public void failure(String error) { | |||
| } | |||
| }); | |||
| uploadInformationList = preferenceManager.getUploadInformationList(); | |||
| if (uploadInformationList.isEmpty()) { | |||
| if (syncInformationList.isEmpty()) { | |||
| emptyRecyclerView.setVisibility(View.VISIBLE); | |||
| recyclerView.setVisibility(View.GONE); | |||
| } else { | |||
| emptyRecyclerView.setVisibility(View.GONE); | |||
| recyclerView.setVisibility(View.VISIBLE); | |||
| uploadInfoAdapter.setItems(uploadInformationList); | |||
| syncInfoAdapter.setItems(syncInformationList); | |||
| for (SyncInformation si : syncInformationList) { | |||
| Log.d("DEBUG", si.toString()); | |||
| } | |||
| } | |||
| } | |||
| private OnRecyclerItemClickListener<Integer> onRecyclerItemClickListener() { | |||
| return (v, index) -> { | |||
| Intent i = new Intent(HomeActivity.this, UploadInfoActivity.class); | |||
| i.putExtra(UploadInfoActivity.EXTRA_UPLOAD_INFO_UUID, uploadInformationList.get(index).getUuid()); | |||
| Intent i = new Intent(HomeActivity.this, SyncInfoDetailActivity.class); | |||
| i.putExtra(SyncInfoDetailActivity.EXTRA_SYNC_INFO_UUID, syncInformationList.get(index).getUuid()); | |||
| ActivityOptionsCompat options = ActivityOptionsCompat.makeClipRevealAnimation(v.findViewById(R.id.rootLayout), (int) v.getX(), (int) v.getY(), v.getWidth(), v.getHeight()); | |||
| startActivity(i, options.toBundle()); | |||
| @@ -12,14 +12,14 @@ import lu.circl.mispbump.auxiliary.PreferenceManager; | |||
| /** | |||
| * Starts either the login or home activity. | |||
| */ | |||
| public class StartUpActivity extends AppCompatActivity { | |||
| public class LauncherActivity extends AppCompatActivity { | |||
| @Override | |||
| protected void onCreate(Bundle savedInstanceState) { | |||
| super.onCreate(savedInstanceState); | |||
| if (isUserLoggedIn()) { | |||
| Intent home = new Intent(this, HomeActivity.class); | |||
| Intent home = new Intent(this, NetworkTestActivity.class); | |||
| startActivity(home); | |||
| } else { | |||
| Intent login = new Intent(this, LoginActivity.class); | |||
| @@ -0,0 +1,99 @@ | |||
| package lu.circl.mispbump.activities; | |||
| import android.os.Bundle; | |||
| import android.util.Log; | |||
| import androidx.appcompat.app.AppCompatActivity; | |||
| import androidx.core.util.Pair; | |||
| import java.util.List; | |||
| import lu.circl.mispbump.R; | |||
| import lu.circl.mispbump.auxiliary.MispRestClient; | |||
| import lu.circl.mispbump.auxiliary.PreferenceManager; | |||
| import lu.circl.mispbump.interfaces.MispService; | |||
| import lu.circl.mispbump.models.SyncInformation; | |||
| import lu.circl.mispbump.models.restModels.MispOrganisation; | |||
| import lu.circl.mispbump.models.restModels.MispServer; | |||
| import lu.circl.mispbump.models.restModels.Organisation; | |||
| import retrofit2.Call; | |||
| import retrofit2.Callback; | |||
| import retrofit2.Response; | |||
| public class NetworkTestActivity extends AppCompatActivity { | |||
| private PreferenceManager preferenceManager; | |||
| private MispService service; | |||
| @Override | |||
| protected void onCreate(Bundle savedInstanceState) { | |||
| super.onCreate(savedInstanceState); | |||
| setContentView(R.layout.activity_network_test); | |||
| preferenceManager = PreferenceManager.getInstance(NetworkTestActivity.this); | |||
| Pair<String, String> credentials = preferenceManager.getUserCredentials(); | |||
| MispRestClient restClient = MispRestClient.getInstance(credentials.first, credentials.second); | |||
| service = restClient.getService(); | |||
| loadAllSyncs(); | |||
| } | |||
| private void boundSyncInfoToServer() { | |||
| List<SyncInformation> syncInformationList = preferenceManager.getSyncInformationList(); | |||
| for (SyncInformation syncInfo : syncInformationList) { | |||
| String authkey = syncInfo.getSyncServer().getAuthkey(); | |||
| String localUUID = syncInfo.getLocal().getOrganisation().getUuid(); | |||
| String foreignUUID = syncInfo.getRemoteOrganisation().getUuid(); | |||
| } | |||
| } | |||
| private void loadAllSyncs() { | |||
| Call<List<MispServer>> allServersCall = service.getAllServers(); | |||
| allServersCall.enqueue(new Callback<List<MispServer>>() { | |||
| @Override | |||
| public void onResponse(Call<List<MispServer>> call, Response<List<MispServer>> response) { | |||
| if (!response.isSuccessful()) { | |||
| return; | |||
| } | |||
| List<MispServer> allServers = response.body(); | |||
| assert allServers != null; | |||
| for (MispServer mispServer : allServers) { | |||
| loadOrganisation(mispServer.getRemoteOrganisation().getId()); | |||
| } | |||
| } | |||
| @Override | |||
| public void onFailure(Call<List<MispServer>> call, Throwable t) { | |||
| } | |||
| }); | |||
| } | |||
| private void loadOrganisation(int id) { | |||
| Call<MispOrganisation> organisationCall = service.getOrganisation(id); | |||
| organisationCall.enqueue(new Callback<MispOrganisation>() { | |||
| @Override | |||
| public void onResponse(Call<MispOrganisation> call, Response<MispOrganisation> response) { | |||
| if (!response.isSuccessful()) { | |||
| return; | |||
| } | |||
| Organisation org = response.body().organisation; | |||
| Log.d("DEBUG", org.toString()); | |||
| } | |||
| @Override | |||
| public void onFailure(Call<MispOrganisation> call, Throwable t) { | |||
| } | |||
| }); | |||
| } | |||
| } | |||
| @@ -1,7 +1,6 @@ | |||
| package lu.circl.mispbump.activities; | |||
| import android.content.DialogInterface; | |||
| import android.content.Intent; | |||
| import android.graphics.Shader; | |||
| import android.graphics.drawable.AnimatedVectorDrawable; | |||
| @@ -120,13 +119,10 @@ public class ProfileActivity extends AppCompatActivity { | |||
| } | |||
| private View.OnClickListener onFabClicked() { | |||
| return new View.OnClickListener() { | |||
| @Override | |||
| public void onClick(View v) { | |||
| fab.setImageDrawable(fabLoadingDrawable); | |||
| fabLoadingDrawable.start(); | |||
| updateProfile(); | |||
| } | |||
| return v -> { | |||
| fab.setImageDrawable(fabLoadingDrawable); | |||
| fabLoadingDrawable.start(); | |||
| updateProfile(); | |||
| }; | |||
| } | |||
| @@ -181,23 +177,15 @@ public class ProfileActivity extends AppCompatActivity { | |||
| builder.setTitle("Clear all saved data and logout"); | |||
| builder.setMessage("Do you really want to delete all data and logout?"); | |||
| builder.setNegativeButton("Discard", new DialogInterface.OnClickListener() { | |||
| @Override | |||
| public void onClick(DialogInterface dialog, int which) { | |||
| dialog.cancel(); | |||
| } | |||
| }); | |||
| builder.setNegativeButton("Discard", (dialog, which) -> dialog.cancel()); | |||
| builder.setPositiveButton("Delete & Logout", new DialogInterface.OnClickListener() { | |||
| @Override | |||
| public void onClick(DialogInterface dialog, int which) { | |||
| preferenceManager.clearAllData(); | |||
| KeyStoreWrapper.deleteAllStoredKeys(); | |||
| builder.setPositiveButton("Delete & Logout", (dialog, which) -> { | |||
| preferenceManager.clearAllData(); | |||
| KeyStoreWrapper.deleteAllStoredKeys(); | |||
| Intent login = new Intent(getApplicationContext(), LoginActivity.class); | |||
| startActivity(login); | |||
| finish(); | |||
| } | |||
| Intent login = new Intent(getApplicationContext(), LoginActivity.class); | |||
| startActivity(login); | |||
| finish(); | |||
| }); | |||
| builder.create().show(); | |||
| @@ -0,0 +1,190 @@ | |||
| package lu.circl.mispbump.activities; | |||
| import android.animation.ValueAnimator; | |||
| import android.content.Intent; | |||
| import android.graphics.Color; | |||
| import android.graphics.drawable.ColorDrawable; | |||
| import android.graphics.drawable.Drawable; | |||
| import android.os.Bundle; | |||
| import android.view.View; | |||
| import android.view.ViewGroup; | |||
| import android.view.ViewGroupOverlay; | |||
| import android.widget.CheckBox; | |||
| import android.widget.LinearLayout; | |||
| import androidx.annotation.NonNull; | |||
| import androidx.appcompat.app.ActionBar; | |||
| import androidx.appcompat.app.AppCompatActivity; | |||
| import androidx.appcompat.widget.Toolbar; | |||
| import com.google.android.material.floatingactionbutton.FloatingActionButton; | |||
| import java.util.UUID; | |||
| import lu.circl.mispbump.R; | |||
| import lu.circl.mispbump.auxiliary.PreferenceManager; | |||
| import lu.circl.mispbump.customViews.MaterialPasswordView; | |||
| import lu.circl.mispbump.customViews.MaterialPreferenceText; | |||
| import lu.circl.mispbump.models.SyncInformation; | |||
| public class SyncInfoDetailActivity extends AppCompatActivity { | |||
| public static String EXTRA_SYNC_INFO_UUID = "EXTRA_SYNC_INFO_UUID"; | |||
| private PreferenceManager preferenceManager; | |||
| private SyncInformation syncInformation; | |||
| private boolean fabMenuExpanded; | |||
| @Override | |||
| protected void onCreate(Bundle savedInstanceState) { | |||
| super.onCreate(savedInstanceState); | |||
| setContentView(R.layout.activity_sync_info_detail); | |||
| preferenceManager = PreferenceManager.getInstance(SyncInfoDetailActivity.this); | |||
| syncInformation = preferenceManager.getSyncInformation(getExtraUuid()); | |||
| if (syncInformation == null) { | |||
| throw new RuntimeException("Could not find UploadInformation with UUID {" + getExtraUuid().toString() + "}"); | |||
| } | |||
| initToolbar(); | |||
| initFabMenu(); | |||
| populateContent(); | |||
| } | |||
| @Override | |||
| protected void onPause() { | |||
| super.onPause(); | |||
| preferenceManager.addSyncInformation(syncInformation); | |||
| } | |||
| private UUID getExtraUuid() { | |||
| return (UUID) getIntent().getSerializableExtra(EXTRA_SYNC_INFO_UUID); | |||
| } | |||
| private void initToolbar() { | |||
| Toolbar toolbar = findViewById(R.id.toolbar); | |||
| setSupportActionBar(toolbar); | |||
| ActionBar ab = getSupportActionBar(); | |||
| assert ab != null; | |||
| ab.setDisplayHomeAsUpEnabled(true); | |||
| } | |||
| private void initFabMenu() { | |||
| FloatingActionButton fab = findViewById(R.id.fab_main); | |||
| FloatingActionButton fabUpload = findViewById(R.id.fab_upload); | |||
| FloatingActionButton fabDownload = findViewById(R.id.fab_download); | |||
| LinearLayout uploadLayout = findViewById(R.id.layout_upload); | |||
| LinearLayout downloadLayout = findViewById(R.id.layout_download); | |||
| uploadLayout.setVisibility(View.GONE); | |||
| downloadLayout.setVisibility(View.GONE); | |||
| fab.setOnClickListener(view -> { | |||
| if (fabMenuExpanded) { | |||
| uploadLayout.setVisibility(View.GONE); | |||
| downloadLayout.setVisibility(View.GONE); | |||
| fabMenuExpanded = false; | |||
| } else { | |||
| uploadLayout.setVisibility(View.VISIBLE); | |||
| downloadLayout.setVisibility(View.VISIBLE); | |||
| fabMenuExpanded = true; | |||
| } | |||
| }); | |||
| fabUpload.setOnClickListener(view -> { | |||
| preferenceManager.addSyncInformation(syncInformation); | |||
| Intent upload = new Intent(SyncInfoDetailActivity.this, UploadActivity.class); | |||
| upload.putExtra(UploadActivity.EXTRA_SYNC_INFO_UUID, syncInformation.getUuid().toString()); | |||
| startActivity(upload); | |||
| }); | |||
| fabDownload.setOnClickListener(view -> { | |||
| }); | |||
| } | |||
| private void populateContent() { | |||
| // information | |||
| MaterialPreferenceText name = findViewById(R.id.name); | |||
| name.setSubtitle(syncInformation.getRemoteOrganisation().getName()); | |||
| MaterialPreferenceText uuid = findViewById(R.id.uuid); | |||
| uuid.setSubtitle(syncInformation.getRemoteOrganisation().getUuid()); | |||
| MaterialPreferenceText sector = findViewById(R.id.sector); | |||
| sector.setSubtitle(syncInformation.getRemoteOrganisation().getSector()); | |||
| MaterialPreferenceText description = findViewById(R.id.description); | |||
| description.setSubtitle(syncInformation.getRemoteOrganisation().getDescription()); | |||
| // settings | |||
| CheckBox allowSelfSigned = findViewById(R.id.checkbox_self_signed); | |||
| allowSelfSigned.setChecked(syncInformation.getSyncServer().getSelf_signed()); | |||
| allowSelfSigned.setOnCheckedChangeListener((compoundButton, b) -> { | |||
| syncInformation.getSyncServer().setSelf_signed(b); | |||
| }); | |||
| CheckBox push = findViewById(R.id.checkbox_push); | |||
| push.setChecked(syncInformation.getSyncServer().getPush()); | |||
| push.setOnCheckedChangeListener((compoundButton, b) -> syncInformation.getSyncServer().setPush(b)); | |||
| CheckBox pull = findViewById(R.id.checkbox_pull); | |||
| pull.setChecked(syncInformation.getSyncServer().getPull()); | |||
| pull.setOnCheckedChangeListener((compundButton, b) -> syncInformation.getSyncServer().setPull(b)); | |||
| CheckBox cache = findViewById(R.id.checkbox_cache); | |||
| cache.setChecked(syncInformation.getSyncServer().getCaching_enabled()); | |||
| cache.setOnCheckedChangeListener((compoundButton, b) -> syncInformation.getSyncServer().setCaching_enabled(b)); | |||
| // credentials | |||
| MaterialPreferenceText email = findViewById(R.id.email); | |||
| email.setSubtitle(syncInformation.getLocal().getSyncUser().getEmail()); | |||
| MaterialPasswordView password = findViewById(R.id.password); | |||
| password.setPassword(syncInformation.getLocal().getSyncUser().getPassword()); | |||
| MaterialPasswordView authkey = findViewById(R.id.authkey); | |||
| authkey.setPassword(syncInformation.getLocal().getSyncUser().getAuthkey()); | |||
| } | |||
| public static void applyDim(@NonNull ViewGroup parent, float dimAmount) { | |||
| // ViewGroup root = (ViewGroup) getWindow().getDecorView().getRootView(); | |||
| Drawable dim = new ColorDrawable(Color.BLACK); | |||
| dim.setBounds(0, 0, parent.getWidth(), parent.getHeight()); | |||
| ValueAnimator valueAnimator = ValueAnimator.ofFloat(dimAmount); | |||
| valueAnimator.addUpdateListener(valueAnim -> { | |||
| float value = (float) valueAnim.getAnimatedValue(); | |||
| dim.setAlpha((int) (255 * value)); | |||
| ViewGroupOverlay overlay = parent.getOverlay(); | |||
| overlay.add(dim); | |||
| }); | |||
| valueAnimator.start(); | |||
| } | |||
| public static void clearDim(@NonNull ViewGroup parent) { | |||
| ViewGroupOverlay overlay = parent.getOverlay(); | |||
| overlay.clear(); | |||
| } | |||
| } | |||
| @@ -6,21 +6,19 @@ import android.os.Bundle; | |||
| import android.view.MenuItem; | |||
| import android.view.View; | |||
| import androidx.annotation.Nullable; | |||
| import androidx.annotation.NonNull; | |||
| import androidx.appcompat.app.ActionBar; | |||
| import androidx.appcompat.app.AppCompatActivity; | |||
| import androidx.appcompat.widget.Toolbar; | |||
| import androidx.core.util.Pair; | |||
| import com.google.android.material.floatingactionbutton.FloatingActionButton; | |||
| import java.util.UUID; | |||
| import lu.circl.mispbump.R; | |||
| import lu.circl.mispbump.auxiliary.MispRestClient; | |||
| import lu.circl.mispbump.auxiliary.PreferenceManager; | |||
| import lu.circl.mispbump.customViews.UploadAction; | |||
| import lu.circl.mispbump.models.UploadInformation; | |||
| import lu.circl.mispbump.customViews.ProgressActionView; | |||
| import lu.circl.mispbump.models.SyncInformation; | |||
| import lu.circl.mispbump.models.restModels.Organisation; | |||
| import lu.circl.mispbump.models.restModels.Server; | |||
| import lu.circl.mispbump.models.restModels.User; | |||
| @@ -28,13 +26,74 @@ import lu.circl.mispbump.models.restModels.User; | |||
| public class UploadActivity extends AppCompatActivity { | |||
| public static String EXTRA_UPLOAD_INFO = "uploadInformation"; | |||
| public static final String EXTRA_SYNC_INFO_UUID = "EXTRA_SYNC_INFO_UUID"; | |||
| private View rootLayout; | |||
| private PreferenceManager preferenceManager; | |||
| private UploadInformation uploadInformation; | |||
| private MispRestClient mispRest; | |||
| private SyncInformation syncInformation; | |||
| private ProgressActionView availableAction, organisationAction, userAction, serverAction; | |||
| @Override | |||
| protected void onCreate(Bundle savedInstanceState) { | |||
| super.onCreate(savedInstanceState); | |||
| setContentView(R.layout.activity_upload); | |||
| rootLayout = findViewById(R.id.rootLayout); | |||
| preferenceManager = PreferenceManager.getInstance(UploadActivity.this); | |||
| Pair<String, String> credentials = preferenceManager.getUserCredentials(); | |||
| mispRest = MispRestClient.getInstance(credentials.first, credentials.second); | |||
| parseExtra(); | |||
| initToolbar(); | |||
| initProgressActionViews(); | |||
| startUpload(); | |||
| } | |||
| @Override | |||
| public boolean onOptionsItemSelected(@NonNull MenuItem item) { | |||
| if (item.getItemId() == android.R.id.home) { | |||
| finish(); | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| private void parseExtra() { | |||
| Intent i = getIntent(); | |||
| String syncInfoUuid = i.getStringExtra(EXTRA_SYNC_INFO_UUID); | |||
| syncInformation = preferenceManager.getSyncInformation(UUID.fromString(syncInfoUuid)); | |||
| } | |||
| private void initToolbar() { | |||
| Toolbar myToolbar = findViewById(R.id.toolbar); | |||
| setSupportActionBar(myToolbar); | |||
| ActionBar ab = getSupportActionBar(); | |||
| if (ab != null) { | |||
| ab.setDisplayHomeAsUpEnabled(true); | |||
| ab.setDisplayShowTitleEnabled(true); | |||
| } | |||
| } | |||
| private void initProgressActionViews() { | |||
| availableAction = findViewById(R.id.availableProgressAction); | |||
| organisationAction = findViewById(R.id.organisationProgressAction); | |||
| userAction = findViewById(R.id.userProgressAction); | |||
| serverAction = findViewById(R.id.serverProgressAction); | |||
| availableAction.pending(); | |||
| organisationAction.pending(); | |||
| userAction.pending(); | |||
| serverAction.pending(); | |||
| } | |||
| private MispRestClient restClient; | |||
| private UploadAction availableAction, orgAction, userAction, serverAction; | |||
| private MispRestClient.AvailableCallback availableCallback = new MispRestClient.AvailableCallback() { | |||
| @Override | |||
| @@ -92,187 +151,64 @@ public class UploadActivity extends AppCompatActivity { | |||
| } | |||
| }; | |||
| private FloatingActionButton fab; | |||
| private boolean errorWhileUpload; | |||
| @Override | |||
| protected void onCreate(Bundle savedInstanceState) { | |||
| super.onCreate(savedInstanceState); | |||
| setContentView(R.layout.activity_upload); | |||
| preferenceManager = PreferenceManager.getInstance(UploadActivity.this); | |||
| Pair<String, String> credentials = preferenceManager.getUserCredentials(); | |||
| restClient = MispRestClient.getInstance(credentials.first, credentials.second); | |||
| parseExtra(); | |||
| initViews(); | |||
| startUpload(); | |||
| } | |||
| @Override | |||
| public boolean onOptionsItemSelected(MenuItem item) { | |||
| if (item.getItemId() == android.R.id.home) { | |||
| saveCurrentState(); | |||
| finish(); | |||
| return true; | |||
| } | |||
| return super.onOptionsItemSelected(item); | |||
| } | |||
| @Override | |||
| public void onBackPressed() { | |||
| super.onBackPressed(); | |||
| saveCurrentState(); | |||
| } | |||
| private void parseExtra() { | |||
| Intent i = getIntent(); | |||
| UUID currentUUID = (UUID) i.getSerializableExtra(EXTRA_UPLOAD_INFO); | |||
| for (UploadInformation ui : preferenceManager.getUploadInformationList()) { | |||
| if (ui.getUuid().compareTo(currentUUID) == 0) { | |||
| uploadInformation = ui; | |||
| return; | |||
| } | |||
| } | |||
| if (uploadInformation == null) { | |||
| throw new RuntimeException("Could not find UploadInfo with UUID {" + currentUUID.toString() + "}"); | |||
| } | |||
| } | |||
| private void initViews() { | |||
| getWindow().setStatusBarColor(getColor(R.color.colorPrimary)); | |||
| fab = findViewById(R.id.fab); | |||
| fab.hide(); | |||
| // toolbar | |||
| Toolbar toolbar = findViewById(R.id.toolbar); | |||
| setSupportActionBar(toolbar); | |||
| ActionBar ab = getSupportActionBar(); | |||
| assert ab != null; | |||
| ab.setDisplayShowTitleEnabled(false); | |||
| ab.setDisplayHomeAsUpEnabled(true); | |||
| ab.setHomeAsUpIndicator(R.drawable.ic_close); | |||
| availableAction = findViewById(R.id.availableAction); | |||
| orgAction = findViewById(R.id.orgAction); | |||
| userAction = findViewById(R.id.userAction); | |||
| serverAction = findViewById(R.id.serverAction); | |||
| } | |||
| private void saveCurrentState() { | |||
| if (errorWhileUpload) { | |||
| uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.FAILURE); | |||
| } | |||
| preferenceManager.addUploadInformation(uploadInformation); | |||
| } | |||
| private void setUploadActionState(UploadAction uploadAction, UploadAction.UploadState state, @Nullable String error) { | |||
| uploadAction.setCurrentUploadState(state); | |||
| uploadAction.setError(error); | |||
| switch (state) { | |||
| case PENDING: | |||
| if (fab.isShown()) { | |||
| fab.hide(); | |||
| } | |||
| break; | |||
| case LOADING: | |||
| errorWhileUpload = false; | |||
| if (fab.isShown()) { | |||
| fab.hide(); | |||
| } | |||
| break; | |||
| case DONE: | |||
| errorWhileUpload = false; | |||
| break; | |||
| case ERROR: | |||
| uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.FAILURE); | |||
| fab.setImageResource(R.drawable.ic_autorenew); | |||
| fab.setOnClickListener(new View.OnClickListener() { | |||
| @Override | |||
| public void onClick(View v) { | |||
| setUploadActionState(availableAction, UploadAction.UploadState.LOADING, null); | |||
| startUpload(); | |||
| } | |||
| }); | |||
| if (!fab.isShown()) { | |||
| fab.show(); | |||
| } | |||
| errorWhileUpload = true; | |||
| break; | |||
| } | |||
| } | |||
| private User generateSyncUser(Organisation organisation) { | |||
| User syncUser = new User(); | |||
| User syncUser = syncInformation.getSyncUser(); | |||
| syncUser.setOrg_id(organisation.getId()); | |||
| // syncUser.role_id = User.ROLE_SYNC_USER; | |||
| syncUser.setEmail(uploadInformation.getRemote().syncUserEmail); | |||
| syncUser.setPassword(uploadInformation.getRemote().syncUserPassword); | |||
| syncUser.setAuthkey(uploadInformation.getRemote().syncUserAuthkey); | |||
| syncUser.setRole_id(6); | |||
| syncUser.setTermsaccepted(true); | |||
| return syncUser; | |||
| } | |||
| private Server generateSyncServer() { | |||
| Server server = new Server(); | |||
| server.setName(uploadInformation.getRemote().organisation.getName() + "'s Sync Server"); | |||
| server.setUrl(uploadInformation.getRemote().baseUrl); | |||
| server.setRemote_org_id(uploadInformation.getRemote().organisation.getId()); | |||
| server.setAuthkey(uploadInformation.getLocal().syncUserAuthkey); | |||
| server.setPull(uploadInformation.isPull()); | |||
| server.setPush(uploadInformation.isPush()); | |||
| server.setCaching_enabled(uploadInformation.isCached()); | |||
| server.setSelf_signed(uploadInformation.isAllowSelfSigned()); | |||
| Server server = syncInformation.getSyncServer(); | |||
| server.setName(syncInformation.getRemoteOrganisation().getName() + "'s Sync Server"); | |||
| server.setRemote_org_id(syncInformation.getRemoteOrganisation().getId()); | |||
| server.setAuthkey(syncInformation.getLocal().getSyncUser().getAuthkey()); | |||
| server.setPull(syncInformation.getSyncServer().getPull()); | |||
| server.setPush(syncInformation.getSyncServer().getPush()); | |||
| server.setCaching_enabled(syncInformation.getSyncServer().getCaching_enabled()); | |||
| server.setSelf_signed(syncInformation.getSyncServer().getCaching_enabled()); | |||
| return server; | |||
| } | |||
| /** | |||
| * Start upload to misp instance. | |||
| */ | |||
| private void startUpload() { | |||
| availableAction.setCurrentUploadState(UploadAction.UploadState.LOADING); | |||
| restClient.isAvailable(availableCallback); | |||
| availableAction.start(); | |||
| mispRest.isAvailable(availableCallback); | |||
| } | |||
| private void mispAvailable(boolean available, String error) { | |||
| if (available) { | |||
| setUploadActionState(availableAction, UploadAction.UploadState.DONE, null); | |||
| restClient.addOrganisation(uploadInformation.getRemote().organisation, organisationCallback); | |||
| availableAction.done(); | |||
| organisationAction.start(); | |||
| mispRest.addOrganisation(syncInformation.getRemoteOrganisation(), organisationCallback); | |||
| } else { | |||
| setUploadActionState(availableAction, UploadAction.UploadState.ERROR, error); | |||
| availableAction.error(error); | |||
| } | |||
| } | |||
| private void organisationAdded(Organisation organisation) { | |||
| if (organisation != null) { | |||
| setUploadActionState(orgAction, UploadAction.UploadState.DONE, null); | |||
| uploadInformation.getRemote().organisation.setId(organisation.getId()); | |||
| restClient.addUser(generateSyncUser(organisation), userCallback); | |||
| organisationAction.done(); | |||
| userAction.start(); | |||
| syncInformation.getRemoteOrganisation().setId(organisation.getId()); | |||
| mispRest.addUser(generateSyncUser(organisation), userCallback); | |||
| } else { | |||
| // search by UUID because the error does not give the actual ID | |||
| restClient.getOrganisation(uploadInformation.getRemote().organisation.getUuid(), new MispRestClient.OrganisationCallback() { | |||
| mispRest.getOrganisation(syncInformation.getRemoteOrganisation().getUuid(), new MispRestClient.OrganisationCallback() { | |||
| @Override | |||
| public void success(Organisation organisation) { | |||
| organisationAdded(organisation); | |||
| organisationAction.done("Organisation already on MISP instance"); | |||
| } | |||
| @Override | |||
| public void failure(String error) { | |||
| setUploadActionState(orgAction, UploadAction.UploadState.ERROR, error); | |||
| organisationAction.error(error); | |||
| } | |||
| }); | |||
| } | |||
| @@ -280,18 +216,21 @@ public class UploadActivity extends AppCompatActivity { | |||
| private void userAdded(User user) { | |||
| if (user != null) { | |||
| setUploadActionState(userAction, UploadAction.UploadState.DONE, null); | |||
| restClient.getAllServers(allServersCallback); | |||
| userAction.done(); | |||
| serverAction.start(); | |||
| mispRest.getAllServers(allServersCallback); | |||
| } else { | |||
| restClient.getUser(uploadInformation.getRemote().syncUserEmail, new MispRestClient.UserCallback() { | |||
| mispRest.getUser(syncInformation.getLocal().getSyncUser().getEmail(), new MispRestClient.UserCallback() { | |||
| @Override | |||
| public void success(User user) { | |||
| userAction.done("User already on MISP instance"); | |||
| userAdded(user); | |||
| } | |||
| @Override | |||
| public void failure(String error) { | |||
| setUploadActionState(userAction, UploadAction.UploadState.ERROR, error); | |||
| userAction.error(error); | |||
| } | |||
| }); | |||
| } | |||
| @@ -309,28 +248,16 @@ public class UploadActivity extends AppCompatActivity { | |||
| } | |||
| } | |||
| restClient.addServer(serverToUpload, serverCallback); | |||
| mispRest.addServer(serverToUpload, serverCallback); | |||
| } else { | |||
| setUploadActionState(serverAction, UploadAction.UploadState.ERROR, "Could not retrieve server information"); | |||
| serverAction.error("Unknown error while creating the Sync Server"); | |||
| } | |||
| } | |||
| private void serverAdded(Server server) { | |||
| if (server != null) { | |||
| setUploadActionState(serverAction, UploadAction.UploadState.DONE, null); | |||
| uploadInformation.setCurrentSyncStatus(UploadInformation.SyncStatus.COMPLETE); | |||
| saveCurrentState(); | |||
| fab.setImageResource(R.drawable.ic_check); | |||
| fab.setOnClickListener(new View.OnClickListener() { | |||
| @Override | |||
| public void onClick(View v) { | |||
| finish(); | |||
| } | |||
| }); | |||
| fab.show(); | |||
| } else { | |||
| setUploadActionState(serverAction, UploadAction.UploadState.ERROR, "Could not add server"); | |||
| serverAction.done(); | |||
| preferenceManager.addSyncInformation(syncInformation); | |||
| } | |||
| } | |||
| } | |||
| @@ -1,269 +0,0 @@ | |||
| package lu.circl.mispbump.activities; | |||
| import android.content.Intent; | |||
| import android.os.Bundle; | |||
| import android.view.Menu; | |||
| import android.view.MenuItem; | |||
| import android.view.View; | |||
| import android.widget.TextView; | |||
| import androidx.annotation.NonNull; | |||
| import androidx.annotation.Nullable; | |||
| import androidx.appcompat.app.ActionBar; | |||
| import androidx.appcompat.app.AppCompatActivity; | |||
| import androidx.appcompat.widget.Toolbar; | |||
| import androidx.fragment.app.Fragment; | |||
| import androidx.fragment.app.FragmentManager; | |||
| import androidx.fragment.app.FragmentPagerAdapter; | |||
| import androidx.viewpager.widget.ViewPager; | |||
| import com.google.android.material.floatingactionbutton.FloatingActionButton; | |||
| import com.google.android.material.tabs.TabLayout; | |||
| import java.util.UUID; | |||
| import lu.circl.mispbump.R; | |||
| import lu.circl.mispbump.auxiliary.DialogManager; | |||
| import lu.circl.mispbump.auxiliary.PreferenceManager; | |||
| import lu.circl.mispbump.fragments.UploadCredentialsFragment; | |||
| import lu.circl.mispbump.fragments.UploadSettingsFragment; | |||
| import lu.circl.mispbump.models.UploadInformation; | |||
| public class UploadInfoActivity extends AppCompatActivity { | |||
| public static String EXTRA_UPLOAD_INFO_UUID = "uploadInformationUuid"; | |||
| private PreferenceManager preferenceManager; | |||
| private UploadInformation uploadInformation; | |||
| private ViewPagerAdapter viewPagerAdapter; | |||
| private FloatingActionButton fab; | |||
| @Override | |||
| protected void onCreate(Bundle savedInstanceState) { | |||
| super.onCreate(savedInstanceState); | |||
| setContentView(R.layout.activity_upload_information); | |||
| preferenceManager = PreferenceManager.getInstance(UploadInfoActivity.this); | |||
| // tint statusBar | |||
| getWindow().setStatusBarColor(getColor(R.color.colorPrimary)); | |||
| // getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); | |||
| parseExtra(); | |||
| initToolbar(); | |||
| initViewPager(); | |||
| initViews(); | |||
| } | |||
| @Override | |||
| protected void onResume() { | |||
| super.onResume(); | |||
| // refresh current uploadInformation | |||
| if (uploadInformation != null) { | |||
| uploadInformation = preferenceManager.getUploadInformation(uploadInformation.getUuid()); | |||
| initContent(); | |||
| } | |||
| } | |||
| @Override | |||
| public void onBackPressed() { | |||
| super.onBackPressed(); | |||
| saveCurrentSettings(); | |||
| } | |||
| @Override | |||
| public boolean onCreateOptionsMenu(Menu menu) { | |||
| getMenuInflater().inflate(R.menu.menu_upload_info, menu); | |||
| return true; | |||
| } | |||
| @Override | |||
| public boolean onOptionsItemSelected(MenuItem item) { | |||
| switch (item.getItemId()) { | |||
| case R.id.delete: | |||
| DialogManager.deleteSyncInformationDialog(UploadInfoActivity.this, new DialogManager.IDialogFeedback() { | |||
| @Override | |||
| public void positive() { | |||
| preferenceManager.removeUploadInformation(uploadInformation.getUuid()); | |||
| finish(); | |||
| } | |||
| @Override | |||
| public void negative() { | |||
| } | |||
| }); | |||
| return true; | |||
| case android.R.id.home: | |||
| saveCurrentSettings(); | |||
| finish(); | |||
| return true; | |||
| default: | |||
| return super.onOptionsItemSelected(item); | |||
| } | |||
| } | |||
| private void parseExtra() { | |||
| Intent i = getIntent(); | |||
| UUID currentUUID = (UUID) i.getSerializableExtra(EXTRA_UPLOAD_INFO_UUID); | |||
| uploadInformation = preferenceManager.getUploadInformation(currentUUID); | |||
| if (uploadInformation == null) { | |||
| throw new RuntimeException("Could not find UploadInformation with UUID {" + currentUUID.toString() + "}"); | |||
| } | |||
| } | |||
| private void initToolbar() { | |||
| Toolbar toolbar = findViewById(R.id.toolbar); | |||
| setSupportActionBar(toolbar); | |||
| ActionBar ab = getSupportActionBar(); | |||
| assert ab != null; | |||
| TextView toolbarTitle = findViewById(R.id.toolbarTitle); | |||
| toolbarTitle.setText(uploadInformation.getRemote().organisation.getName()); | |||
| ab.setHomeAsUpIndicator(R.drawable.ic_close); | |||
| ab.setDisplayShowTitleEnabled(false); | |||
| ab.setDisplayHomeAsUpEnabled(true); | |||
| } | |||
| private void initViewPager() { | |||
| ViewPager viewPager = findViewById(R.id.viewPager); | |||
| TabLayout tabLayout = findViewById(R.id.tabLayout); | |||
| viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager(), uploadInformation); | |||
| viewPager.setAdapter(viewPagerAdapter); | |||
| viewPager.addOnPageChangeListener(onPageChangeListener()); | |||
| tabLayout.setupWithViewPager(viewPager); | |||
| } | |||
| private void initViews() { | |||
| fab = findViewById(R.id.fab); | |||
| fab.setOnClickListener(new View.OnClickListener() { | |||
| @Override | |||
| public void onClick(View v) { | |||
| // for the UploadActivity to have the latest settings of this UploadInfoObject | |||
| saveCurrentSettings(); | |||
| Intent i = new Intent(UploadInfoActivity.this, UploadActivity.class); | |||
| i.putExtra(UploadActivity.EXTRA_UPLOAD_INFO, uploadInformation.getUuid()); | |||
| startActivity(i); | |||
| } | |||
| }); | |||
| } | |||
| private void initContent() { | |||
| switch (uploadInformation.getCurrentSyncStatus()) { | |||
| case COMPLETE: | |||
| break; | |||
| case FAILURE: | |||
| break; | |||
| case PENDING: | |||
| break; | |||
| default: | |||
| break; | |||
| } | |||
| } | |||
| private void saveCurrentSettings() { | |||
| uploadInformation.setAllowSelfSigned(viewPagerAdapter.uploadSettingsFragment.getAllowSelfSigned()); | |||
| uploadInformation.setPull(viewPagerAdapter.uploadSettingsFragment.getPull()); | |||
| uploadInformation.setPush(viewPagerAdapter.uploadSettingsFragment.getPush()); | |||
| uploadInformation.setCached(viewPagerAdapter.uploadSettingsFragment.getCache()); | |||
| preferenceManager.addUploadInformation(uploadInformation); | |||
| } | |||
| private ViewPager.OnPageChangeListener onPageChangeListener() { | |||
| return new ViewPager.OnPageChangeListener() { | |||
| @Override | |||
| public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { | |||
| if (position == 0) { | |||
| float scale = (1 - positionOffset); | |||
| fab.setScaleX(scale); | |||
| fab.setScaleY(scale); | |||
| } | |||
| } | |||
| @Override | |||
| public void onPageSelected(int position) { | |||
| } | |||
| @Override | |||
| public void onPageScrollStateChanged(int state) { | |||
| } | |||
| }; | |||
| } | |||
| class ViewPagerAdapter extends FragmentPagerAdapter { | |||
| private UploadSettingsFragment uploadSettingsFragment; | |||
| private UploadCredentialsFragment uploadCredentialsFragment; | |||
| ViewPagerAdapter(@NonNull FragmentManager fm, UploadInformation uploadInformation) { | |||
| super(fm, ViewPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); | |||
| uploadSettingsFragment = new UploadSettingsFragment(uploadInformation); | |||
| uploadCredentialsFragment = new UploadCredentialsFragment(uploadInformation); | |||
| } | |||
| @NonNull | |||
| @Override | |||
| public Fragment getItem(int position) { | |||
| switch (position) { | |||
| case 0: | |||
| return uploadSettingsFragment; | |||
| case 1: | |||
| return uploadCredentialsFragment; | |||
| default: | |||
| uploadSettingsFragment = new UploadSettingsFragment(); | |||
| return uploadSettingsFragment; | |||
| } | |||
| } | |||
| @Nullable | |||
| @Override | |||
| public CharSequence getPageTitle(int position) { | |||
| switch (position) { | |||
| case 0: | |||
| return getString(R.string.settings); | |||
| case 1: | |||
| return getString(R.string.credentials); | |||
| default: | |||
| return "N/A"; | |||
| } | |||
| } | |||
| @Override | |||
| public int getCount() { | |||
| return 2; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,99 @@ | |||
| package lu.circl.mispbump.adapters; | |||
| import android.view.LayoutInflater; | |||
| import android.view.View; | |||
| import android.view.ViewGroup; | |||
| import android.widget.ImageView; | |||
| import android.widget.TextView; | |||
| import androidx.annotation.NonNull; | |||
| import androidx.recyclerview.widget.RecyclerView; | |||
| import java.text.SimpleDateFormat; | |||
| import java.util.List; | |||
| import java.util.Locale; | |||
| import lu.circl.mispbump.R; | |||
| import lu.circl.mispbump.interfaces.OnRecyclerItemClickListener; | |||
| import lu.circl.mispbump.models.SyncInformation; | |||
| public class SyncInfoAdapter extends RecyclerView.Adapter<SyncInfoAdapter.ViewHolder> { | |||
| private List<SyncInformation> items; | |||
| private OnRecyclerItemClickListener<Integer> onRecyclerPositionClickListener; | |||
| @NonNull | |||
| @Override | |||
| public SyncInfoAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { | |||
| View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.row_upload_information, viewGroup, false); | |||
| return new SyncInfoAdapter.ViewHolder(v); | |||
| } | |||
| @Override | |||
| public void onBindViewHolder(@NonNull final SyncInfoAdapter.ViewHolder holder, final int position) { | |||
| final SyncInformation item = items.get(position); | |||
| SimpleDateFormat monthFormatter = new SimpleDateFormat("MMM", Locale.getDefault()); | |||
| SimpleDateFormat dayFormatter = new SimpleDateFormat("dd", Locale.getDefault()); | |||
| holder.dateMonth.setText(monthFormatter.format(item.getSyncDate())); | |||
| holder.dateDay.setText(dayFormatter.format(item.getSyncDate())); | |||
| holder.orgName.setText(item.getRemoteOrganisation().getName()); | |||
| // switch (item.getCurrentSyncStatus()) { | |||
| // case COMPLETE: | |||
| // ImageViewCompat.setImageTintList(holder.syncStatus, ColorStateList.valueOf(context.getColor(R.color.status_green))); | |||
| // holder.syncStatus.setImageResource(R.drawable.ic_check_outline); | |||
| // break; | |||
| // case FAILURE: | |||
| // ImageViewCompat.setImageTintList(holder.syncStatus, ColorStateList.valueOf(context.getColor(R.color.status_red))); | |||
| // holder.syncStatus.setImageResource(R.drawable.ic_error_outline); | |||
| // break; | |||
| // case PENDING: | |||
| // ImageViewCompat.setImageTintList(holder.syncStatus, ColorStateList.valueOf(context.getColor(R.color.status_amber))); | |||
| // holder.syncStatus.setImageResource(R.drawable.ic_pending); | |||
| // break; | |||
| // } | |||
| holder.rootView.setOnClickListener(view -> onRecyclerPositionClickListener.onClick(view, position)); | |||
| } | |||
| @Override | |||
| public int getItemCount() { | |||
| return items.size(); | |||
| } | |||
| public void setItems(List<SyncInformation> items) { | |||
| this.items = items; | |||
| notifyDataSetChanged(); | |||
| } | |||
| public void setOnRecyclerPositionClickListener(OnRecyclerItemClickListener<Integer> onRecyclerPositionClickListener) { | |||
| this.onRecyclerPositionClickListener = onRecyclerPositionClickListener; | |||
| } | |||
| static class ViewHolder extends RecyclerView.ViewHolder { | |||
| View rootView; | |||
| ImageView syncStatus; | |||
| TextView orgName, dateMonth, dateDay; | |||
| ViewHolder(@NonNull View itemView) { | |||
| super(itemView); | |||
| rootView = itemView; | |||
| orgName = itemView.findViewById(R.id.orgName); | |||
| dateMonth = itemView.findViewById(R.id.date_month); | |||
| dateDay = itemView.findViewById(R.id.date_day); | |||
| syncStatus = itemView.findViewById(R.id.syncStatus); | |||
| } | |||
| } | |||
| } | |||
| @@ -1,98 +0,0 @@ | |||
| package lu.circl.mispbump.adapters; | |||
| import android.content.Context; | |||
| import android.content.res.ColorStateList; | |||
| import android.view.LayoutInflater; | |||
| import android.view.View; | |||
| import android.view.ViewGroup; | |||
| import android.widget.ImageView; | |||
| import android.widget.TextView; | |||
| import androidx.annotation.NonNull; | |||
| import androidx.core.widget.ImageViewCompat; | |||
| import androidx.recyclerview.widget.RecyclerView; | |||
| import java.util.List; | |||
| import lu.circl.mispbump.R; | |||
| import lu.circl.mispbump.interfaces.OnRecyclerItemClickListener; | |||
| import lu.circl.mispbump.models.UploadInformation; | |||
| public class UploadInfoAdapter extends RecyclerView.Adapter<UploadInfoAdapter.ViewHolder> { | |||
| private Context context; | |||
| private List<UploadInformation> items; | |||
| private OnRecyclerItemClickListener<Integer> onRecyclerPositionClickListener; | |||
| public UploadInfoAdapter(Context context) { | |||
| this.context = context; | |||
| } | |||
| @NonNull | |||
| @Override | |||
| public UploadInfoAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { | |||
| View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.row_upload_information, viewGroup, false); | |||
| return new UploadInfoAdapter.ViewHolder(v); | |||
| } | |||
| @Override | |||
| public void onBindViewHolder(@NonNull final UploadInfoAdapter.ViewHolder holder, final int position) { | |||
| final UploadInformation item = items.get(position); | |||
| holder.date.setText(item.getDateString()); | |||
| holder.orgName.setText(item.getRemote().organisation.getName()); | |||
| switch (item.getCurrentSyncStatus()) { | |||
| case COMPLETE: | |||
| ImageViewCompat.setImageTintList(holder.syncStatus, ColorStateList.valueOf(context.getColor(R.color.status_green))); | |||
| holder.syncStatus.setImageResource(R.drawable.ic_check_outline); | |||
| break; | |||
| case FAILURE: | |||
| ImageViewCompat.setImageTintList(holder.syncStatus, ColorStateList.valueOf(context.getColor(R.color.status_red))); | |||
| holder.syncStatus.setImageResource(R.drawable.ic_error_outline); | |||
| break; | |||
| case PENDING: | |||
| ImageViewCompat.setImageTintList(holder.syncStatus, ColorStateList.valueOf(context.getColor(R.color.status_amber))); | |||
| holder.syncStatus.setImageResource(R.drawable.ic_pending); | |||
| break; | |||
| } | |||
| holder.rootView.setOnClickListener(view -> onRecyclerPositionClickListener.onClick(view, position)); | |||
| } | |||
| @Override | |||
| public int getItemCount() { | |||
| return items.size(); | |||
| } | |||
| public void setItems(List<UploadInformation> items) { | |||
| this.items = items; | |||
| notifyDataSetChanged(); | |||
| } | |||
| public void setOnRecyclerPositionClickListener(OnRecyclerItemClickListener<Integer> onRecyclerPositionClickListener) { | |||
| this.onRecyclerPositionClickListener = onRecyclerPositionClickListener; | |||
| } | |||
| static class ViewHolder extends RecyclerView.ViewHolder { | |||
| View rootView; | |||
| ImageView syncStatus; | |||
| TextView orgName, date; | |||
| ViewHolder(@NonNull View itemView) { | |||
| super(itemView); | |||
| rootView = itemView; | |||
| orgName = itemView.findViewById(R.id.orgName); | |||
| date = itemView.findViewById(R.id.date); | |||
| syncStatus = itemView.findViewById(R.id.syncStatus); | |||
| } | |||
| } | |||
| } | |||
| @@ -27,11 +27,11 @@ public class DialogManager { | |||
| // this dialog needs definite user feedback | |||
| adb.setCancelable(false); | |||
| if (oldSync.organisation.getName().equals(newSync.organisation.getName())) { | |||
| adb.setTitle("Already Synced with " + oldSync.organisation.getName()); | |||
| } else { | |||
| adb.setTitle("Already Synced with " + oldSync.organisation.getName() + "(Now:" + newSync.organisation.getName() + ")"); | |||
| } | |||
| // if (oldSync.organisation.getName().equals(newSync.organisation.getName())) { | |||
| // adb.setTitle("Already Synced with " + oldSync.organisation.getName()); | |||
| // } else { | |||
| // adb.setTitle("Already Synced with " + oldSync.organisation.getName() + "(Now:" + newSync.organisation.getName() + ")"); | |||
| // } | |||
| adb.setMessage(""); | |||
| @@ -143,7 +143,7 @@ public class DialogManager { | |||
| final AlertDialog.Builder adb = new AlertDialog.Builder(context); | |||
| adb.setTitle("Sync information received"); | |||
| adb.setMessage(syncInformation.organisation.getName()); | |||
| adb.setMessage(syncInformation.getRemoteOrganisation().getName()); | |||
| adb.setPositiveButton("Accept", new DialogInterface.OnClickListener() { | |||
| @Override | |||
| public void onClick(DialogInterface dialog, int which) { | |||
| @@ -18,7 +18,7 @@ import javax.net.ssl.SSLSocketFactory; | |||
| import javax.net.ssl.TrustManager; | |||
| import javax.net.ssl.X509TrustManager; | |||
| import lu.circl.mispbump.interfaces.MispRestInterface; | |||
| import lu.circl.mispbump.interfaces.MispService; | |||
| import lu.circl.mispbump.models.restModels.MispOrganisation; | |||
| import lu.circl.mispbump.models.restModels.MispRole; | |||
| import lu.circl.mispbump.models.restModels.MispServer; | |||
| @@ -47,7 +47,7 @@ public class MispRestClient { | |||
| private static MispRestClient instance; | |||
| private MispRestInterface mispRestInterface; | |||
| private MispService mispService; | |||
| public static MispRestClient getInstance(String url, String authkey) { | |||
| if (instance == null) { | |||
| @@ -67,13 +67,18 @@ public class MispRestClient { | |||
| .client(getCustomClient(true, true, authkey)) | |||
| .build(); | |||
| mispRestInterface = retrofit.create(MispRestInterface.class); | |||
| mispService = retrofit.create(MispService.class); | |||
| } catch (IllegalArgumentException e) { | |||
| throw new RuntimeException(e); | |||
| } | |||
| } | |||
| public MispService getService() { | |||
| return mispService; | |||
| } | |||
| /** | |||
| * @param unsafe whether to accept all certificates or only trusted ones | |||
| * @param logging whether to log Retrofit calls (for debugging) | |||
| @@ -153,7 +158,7 @@ public class MispRestClient { | |||
| * @param callback {@link AvailableCallback} | |||
| */ | |||
| public void isAvailable(final AvailableCallback callback) { | |||
| Call<Version> call = mispRestInterface.pyMispVersion(); | |||
| Call<Version> call = mispService.pyMispVersion(); | |||
| call.enqueue(new Callback<Version>() { | |||
| @Override | |||
| public void onResponse(@NonNull Call<Version> call, @NonNull Response<Version> response) { | |||
| @@ -177,7 +182,7 @@ public class MispRestClient { | |||
| } | |||
| public void getRoles(final AllRolesCallback callback) { | |||
| Call<List<MispRole>> call = mispRestInterface.getRoles(); | |||
| Call<List<MispRole>> call = mispService.getRoles(); | |||
| call.enqueue(new Callback<List<MispRole>>() { | |||
| @Override | |||
| public void onResponse(Call<List<MispRole>> call, Response<List<MispRole>> response) { | |||
| @@ -213,7 +218,7 @@ public class MispRestClient { | |||
| * @param callback {@link UserCallback} wrapper to return user directly | |||
| */ | |||
| public void getMyUser(final UserCallback callback) { | |||
| Call<MispUser> call = mispRestInterface.getMyUserInformation(); | |||
| Call<MispUser> call = mispService.getMyUserInformation(); | |||
| call.enqueue(new Callback<MispUser>() { | |||
| @Override | |||
| @@ -245,7 +250,7 @@ public class MispRestClient { | |||
| */ | |||
| public void getUser(int userId, final UserCallback callback) { | |||
| Call<MispUser> call = mispRestInterface.getUser(userId); | |||
| Call<MispUser> call = mispService.getUser(userId); | |||
| call.enqueue(new Callback<MispUser>() { | |||
| @Override | |||
| @@ -291,7 +296,7 @@ public class MispRestClient { | |||
| } | |||
| public void getAllUsers(final AllUsersCallback callback) { | |||
| Call<List<MispUser>> call = mispRestInterface.getAllUsers(); | |||
| Call<List<MispUser>> call = mispService.getAllUsers(); | |||
| call.enqueue(new Callback<List<MispUser>>() { | |||
| @Override | |||
| @@ -327,7 +332,7 @@ public class MispRestClient { | |||
| * @param callback {@link UserCallback} wrapper to return the created user directly | |||
| */ | |||
| public void addUser(User user, final UserCallback callback) { | |||
| Call<MispUser> call = mispRestInterface.addUser(user); | |||
| Call<MispUser> call = mispService.addUser(user); | |||
| call.enqueue(new Callback<MispUser>() { | |||
| @Override | |||
| @@ -357,7 +362,7 @@ public class MispRestClient { | |||
| * @param callback {@link OrganisationCallback} wrapper to return a organisation directly | |||
| */ | |||
| public void getOrganisation(int orgId, final OrganisationCallback callback) { | |||
| Call<MispOrganisation> call = mispRestInterface.getOrganisation(orgId); | |||
| Call<MispOrganisation> call = mispService.getOrganisation(orgId); | |||
| call.enqueue(new Callback<MispOrganisation>() { | |||
| @Override | |||
| @@ -402,7 +407,7 @@ public class MispRestClient { | |||
| } | |||
| public void getAllOrganisations(final AllOrganisationsCallback callback) { | |||
| Call<List<MispOrganisation>> call = mispRestInterface.getAllOrganisations(); | |||
| Call<List<MispOrganisation>> call = mispService.getAllOrganisations(); | |||
| call.enqueue(new Callback<List<MispOrganisation>>() { | |||
| @Override | |||
| @@ -438,7 +443,7 @@ public class MispRestClient { | |||
| * @param callback {@link OrganisationCallback} wrapper to return the created organisation directly | |||
| */ | |||
| public void addOrganisation(Organisation organisation, final OrganisationCallback callback) { | |||
| Call<MispOrganisation> call = mispRestInterface.addOrganisation(organisation); | |||
| Call<MispOrganisation> call = mispService.addOrganisation(organisation); | |||
| call.enqueue(new Callback<MispOrganisation>() { | |||
| @Override | |||
| @@ -466,7 +471,7 @@ public class MispRestClient { | |||
| * @param callback {@link OrganisationCallback} wrapper to return a list of servers directly | |||
| */ | |||
| public void getAllServers(final AllServersCallback callback) { | |||
| Call<List<MispServer>> call = mispRestInterface.getAllServers(); | |||
| Call<List<MispServer>> call = mispService.getAllServers(); | |||
| call.enqueue(new Callback<List<MispServer>>() { | |||
| @Override | |||
| @@ -494,7 +499,7 @@ public class MispRestClient { | |||
| } | |||
| public void getAllServers(final AllRawServersCallback callback) { | |||
| Call<List<MispServer>> call = mispRestInterface.getAllServers(); | |||
| Call<List<MispServer>> call = mispService.getAllServers(); | |||
| call.enqueue(new Callback<List<MispServer>>() { | |||
| @Override | |||
| @@ -520,7 +525,7 @@ public class MispRestClient { | |||
| * @param callback {@link ServerCallback} wrapper to return the created server directly | |||
| */ | |||
| public void addServer(Server server, final ServerCallback callback) { | |||
| Call<Server> call = mispRestInterface.addServer(server); | |||
| Call<Server> call = mispService.addServer(server); | |||
| call.enqueue(new Callback<Server>() { | |||
| @Override | |||
| @@ -21,7 +21,7 @@ import javax.crypto.BadPaddingException; | |||
| import javax.crypto.IllegalBlockSizeException; | |||
| import javax.crypto.NoSuchPaddingException; | |||
| import lu.circl.mispbump.models.UploadInformation; | |||
| import lu.circl.mispbump.models.SyncInformation; | |||
| import lu.circl.mispbump.models.restModels.Organisation; | |||
| import lu.circl.mispbump.models.restModels.Role; | |||
| import lu.circl.mispbump.models.restModels.User; | |||
| @@ -36,7 +36,7 @@ public class PreferenceManager { | |||
| private static final String USER_INFOS = "user_infos"; | |||
| private static final String USER_ORG_INFOS = "user_org_infos"; | |||
| private static final String UPLOAD_INFO = "upload_info"; | |||
| private static final String SYNC_INFO = "sync_info"; | |||
| private static final String MISP_ROLES = "misp_roles"; | |||
| @@ -101,21 +101,11 @@ public class PreferenceManager { | |||
| */ | |||
| public void setUserInfo(User user) { | |||
| try { | |||
| String userStr = new Gson().toJson(user); | |||
| KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.USER_INFO_ALIAS); | |||
| String encryptedUserInfo = keyStoreWrapper.encrypt(userStr); | |||
| SharedPreferences.Editor editor = preferences.edit(); | |||
| editor.putString(USER_INFOS, encryptedUserInfo); | |||
| KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.USER_INFO_ALIAS); | |||
| editor.putString(USER_INFOS, keyStoreWrapper.encrypt(new Gson().toJson(user))); | |||
| editor.apply(); | |||
| } catch (NoSuchPaddingException e) { | |||
| e.printStackTrace(); | |||
| } catch (NoSuchAlgorithmException e) { | |||
| e.printStackTrace(); | |||
| } catch (InvalidKeyException e) { | |||
| e.printStackTrace(); | |||
| } catch (BadPaddingException e) { | |||
| e.printStackTrace(); | |||
| } catch (IllegalBlockSizeException e) { | |||
| } catch (BadPaddingException | InvalidKeyException | NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| } | |||
| @@ -135,17 +125,7 @@ public class PreferenceManager { | |||
| KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.USER_INFO_ALIAS); | |||
| String decrypted = keyStoreWrapper.decrypt(preferences.getString(USER_INFOS, "")); | |||
| return new Gson().fromJson(decrypted, User.class); | |||
| } catch (NoSuchPaddingException e) { | |||
| e.printStackTrace(); | |||
| } catch (NoSuchAlgorithmException e) { | |||
| e.printStackTrace(); | |||
| } catch (InvalidAlgorithmParameterException e) { | |||
| e.printStackTrace(); | |||
| } catch (InvalidKeyException e) { | |||
| e.printStackTrace(); | |||
| } catch (BadPaddingException e) { | |||
| e.printStackTrace(); | |||
| } catch (IllegalBlockSizeException e) { | |||
| } catch (BadPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| @@ -243,58 +223,57 @@ public class PreferenceManager { | |||
| } | |||
| private List<UploadInformation> cachedUploadInformationList; | |||
| private List<SyncInformation> cachedSyncInformationList; | |||
| private void loadUploadInformationList() { | |||
| KeyStoreWrapper ksw = new KeyStoreWrapper(KeyStoreWrapper.UPLOAD_INFORMATION_ALIAS); | |||
| String storedUploadInfoString = preferences.getString(UPLOAD_INFO, null); | |||
| private void loadSyncInformationList() { | |||
| KeyStoreWrapper ksw = new KeyStoreWrapper(KeyStoreWrapper.SYNC_INFORMATION_ALIAS); | |||
| String storedSyncInfoString = preferences.getString(SYNC_INFO, null); | |||
| Type type = new TypeToken<List<UploadInformation>>() { | |||
| }.getType(); | |||
| Type type = new TypeToken<List<SyncInformation>>() {}.getType(); | |||
| if (storedUploadInfoString == null || storedUploadInfoString.isEmpty()) { | |||
| cachedUploadInformationList = new ArrayList<>(); | |||
| if (storedSyncInfoString == null || storedSyncInfoString.isEmpty()) { | |||
| cachedSyncInformationList = new ArrayList<>(); | |||
| } else { | |||
| try { | |||
| storedUploadInfoString = ksw.decrypt(storedUploadInfoString); | |||
| cachedUploadInformationList = new Gson().fromJson(storedUploadInfoString, type); | |||
| storedSyncInfoString = ksw.decrypt(storedSyncInfoString); | |||
| cachedSyncInformationList = new Gson().fromJson(storedSyncInfoString, type); | |||
| } catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchPaddingException | NoSuchAlgorithmException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| } | |||
| } | |||
| private void saveUploadInformationList() { | |||
| private void saveSyncInformationList() { | |||
| try { | |||
| KeyStoreWrapper ksw = new KeyStoreWrapper(KeyStoreWrapper.UPLOAD_INFORMATION_ALIAS); | |||
| String cipherText = ksw.encrypt(new Gson().toJson(cachedUploadInformationList)); | |||
| KeyStoreWrapper ksw = new KeyStoreWrapper(KeyStoreWrapper.SYNC_INFORMATION_ALIAS); | |||
| String cipherText = ksw.encrypt(new Gson().toJson(cachedSyncInformationList)); | |||
| SharedPreferences.Editor editor = preferences.edit(); | |||
| editor.putString(UPLOAD_INFO, cipherText); | |||
| editor.putString(SYNC_INFO, cipherText); | |||
| editor.apply(); | |||
| } catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| } | |||
| public List<UploadInformation> getUploadInformationList() { | |||
| if (cachedUploadInformationList == null) { | |||
| loadUploadInformationList(); | |||
| public List<SyncInformation> getSyncInformationList() { | |||
| if (cachedSyncInformationList == null) { | |||
| loadSyncInformationList(); | |||
| } | |||
| return cachedUploadInformationList; | |||
| return cachedSyncInformationList; | |||
| } | |||
| public void setUploadInformationList(List<UploadInformation> uploadInformationList) { | |||
| cachedUploadInformationList = uploadInformationList; | |||
| saveUploadInformationList(); | |||
| public void setSyncInformationList(List<SyncInformation> uploadInformationList) { | |||
| cachedSyncInformationList = uploadInformationList; | |||
| saveSyncInformationList(); | |||
| } | |||
| public UploadInformation getUploadInformation(UUID uuid) { | |||
| if (cachedUploadInformationList == null) { | |||
| loadUploadInformationList(); | |||
| public SyncInformation getSyncInformation(UUID uuid) { | |||
| if (cachedSyncInformationList == null) { | |||
| loadSyncInformationList(); | |||
| } | |||
| for (UploadInformation ui : cachedUploadInformationList) { | |||
| for (SyncInformation ui : cachedSyncInformationList) { | |||
| if (ui.getUuid().compareTo(uuid) == 0) { | |||
| return ui; | |||
| } | |||
| @@ -303,51 +282,50 @@ public class PreferenceManager { | |||
| return null; | |||
| } | |||
| public void addUploadInformation(UploadInformation uploadInformation) { | |||
| if (cachedUploadInformationList == null) { | |||
| loadUploadInformationList(); | |||
| public void addSyncInformation(SyncInformation syncInformation) { | |||
| if (cachedSyncInformationList == null) { | |||
| loadSyncInformationList(); | |||
| } | |||
| // update if exists | |||
| for (int i = 0; i < cachedUploadInformationList.size(); i++) { | |||
| if (cachedUploadInformationList.get(i).getUuid().compareTo(uploadInformation.getUuid()) == 0) { | |||
| cachedUploadInformationList.set(i, uploadInformation); | |||
| saveUploadInformationList(); | |||
| for (int i = 0; i < cachedSyncInformationList.size(); i++) { | |||
| if (cachedSyncInformationList.get(i).getUuid().compareTo(syncInformation.getUuid()) == 0) { | |||
| cachedSyncInformationList.set(i, syncInformation); | |||
| saveSyncInformationList(); | |||
| return; | |||
| } | |||
| } | |||
| // else: add | |||
| cachedUploadInformationList.add(uploadInformation); | |||
| saveUploadInformationList(); | |||
| cachedSyncInformationList.add(syncInformation); | |||
| saveSyncInformationList(); | |||
| } | |||
| public void removeUploadInformation(UUID uuid) { | |||
| if (cachedUploadInformationList == null) { | |||
| loadUploadInformationList(); | |||
| if (cachedSyncInformationList == null) { | |||
| loadSyncInformationList(); | |||
| } | |||
| for (UploadInformation ui : cachedUploadInformationList) { | |||
| for (SyncInformation ui : cachedSyncInformationList) { | |||
| if (ui.getUuid().compareTo(uuid) == 0) { | |||
| // if is last element, then clear everything including IV and key in KeyStore | |||
| if (cachedUploadInformationList.size() == 1) { | |||
| if (cachedSyncInformationList.size() == 1) { | |||
| clearUploadInformation(); | |||
| } else { | |||
| cachedUploadInformationList.remove(ui); | |||
| saveUploadInformationList(); | |||
| cachedSyncInformationList.remove(ui); | |||
| saveSyncInformationList(); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| public void clearUploadInformation() { | |||
| cachedUploadInformationList.clear(); | |||
| cachedSyncInformationList.clear(); | |||
| KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.UPLOAD_INFORMATION_ALIAS); | |||
| KeyStoreWrapper keyStoreWrapper = new KeyStoreWrapper(KeyStoreWrapper.SYNC_INFORMATION_ALIAS); | |||
| keyStoreWrapper.deleteStoredKey(); | |||
| SharedPreferences.Editor editor = preferences.edit(); | |||
| editor.remove(UPLOAD_INFO); | |||
| editor.remove(SYNC_INFO); | |||
| editor.apply(); | |||
| } | |||
| @@ -18,7 +18,6 @@ import lu.circl.mispbump.R; | |||
| public class MaterialPasswordView extends ConstraintLayout { | |||
| private TextView titleView, passwordView; | |||
| private OnCopyClickListener onCopyClickListener; | |||
| public MaterialPasswordView(Context context, AttributeSet attrs) { | |||
| @@ -31,14 +30,6 @@ public class MaterialPasswordView extends ConstraintLayout { | |||
| final String password = a.getString(R.styleable.MaterialPasswordView_password); | |||
| a.recycle(); | |||
| ImageButton copyButton = view.findViewById(R.id.copy); | |||
| copyButton.setOnClickListener(new OnClickListener() { | |||
| @Override | |||
| public void onClick(View v) { | |||
| onCopyClickListener.onClick(title, getPassword()); | |||
| } | |||
| }); | |||
| titleView = view.findViewById(R.id.material_password_title); | |||
| titleView.setText(title); | |||
| @@ -47,14 +38,11 @@ public class MaterialPasswordView extends ConstraintLayout { | |||
| passwordView.setText(password); | |||
| ImageButton visibleToggle = findViewById(R.id.visibleToggle); | |||
| visibleToggle.setOnClickListener(new OnClickListener() { | |||
| @Override | |||
| public void onClick(View v) { | |||
| if (passwordView.getTransformationMethod() == null) { | |||
| passwordView.setTransformationMethod(new PasswordTransformationMethod()); | |||
| } else { | |||
| passwordView.setTransformationMethod(null); | |||
| } | |||
| visibleToggle.setOnClickListener(v -> { | |||
| if (passwordView.getTransformationMethod() == null) { | |||
| passwordView.setTransformationMethod(new PasswordTransformationMethod()); | |||
| } else { | |||
| passwordView.setTransformationMethod(null); | |||
| } | |||
| }); | |||
| } | |||
| @@ -84,13 +72,4 @@ public class MaterialPasswordView extends ConstraintLayout { | |||
| } | |||
| } | |||
| public void addOnCopyClickedListener(OnCopyClickListener listener) { | |||
| onCopyClickListener = listener; | |||
| } | |||
| public interface OnCopyClickListener { | |||
| void onClick(String title, String password); | |||
| } | |||
| } | |||
| @@ -0,0 +1,146 @@ | |||
| package lu.circl.mispbump.customViews; | |||
| import android.content.Context; | |||
| import android.content.res.ColorStateList; | |||
| import android.content.res.TypedArray; | |||
| import android.graphics.drawable.Drawable; | |||
| import android.util.AttributeSet; | |||
| import android.view.LayoutInflater; | |||
| import android.view.View; | |||
| import android.widget.ImageView; | |||
| import android.widget.LinearLayout; | |||
| import android.widget.ProgressBar; | |||
| import android.widget.TextView; | |||
| import androidx.annotation.Nullable; | |||
| import lu.circl.mispbump.R; | |||
| public class ProgressActionView extends LinearLayout { | |||
| private Context context; | |||
| private ImageView icon; | |||
| private ProgressBar progressBar; | |||
| private TextView title, feedback; | |||
| private Drawable pendingIcon, doneIcon, errorIcon; | |||
| public ProgressActionView(Context context) { | |||
| this(context, null); | |||
| } | |||
| public ProgressActionView(Context context, @Nullable AttributeSet attrs) { | |||
| this(context, attrs, 0); | |||
| } | |||
| public ProgressActionView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { | |||
| this(context, attrs, defStyleAttr, 0); | |||
| } | |||
| public ProgressActionView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { | |||
| super(context, attrs, defStyleAttr, defStyleRes); | |||
| this.context = context; | |||
| initViews(attrs); | |||
| } | |||
| private void initViews(AttributeSet attrs) { | |||
| View v = LayoutInflater.from(context).inflate(R.layout.view_upload_action, this, true); | |||
| icon = v.findViewById(R.id.progressIcon); | |||
| progressBar = v.findViewById(R.id.progressBar); | |||
| title = v.findViewById(R.id.title); | |||
| feedback = v.findViewById(R.id.error); | |||
| TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ProgressActionView); | |||
| title.setText(a.getString(R.styleable.ProgressActionView_action_title)); | |||
| pendingIcon = context.getDrawable(a.getResourceId(R.styleable.ProgressActionView_action_pending_icon, 0)); | |||
| doneIcon = context.getDrawable(a.getResourceId(R.styleable.ProgressActionView_action_done_icon, 0)); | |||
| errorIcon = context.getDrawable(a.getResourceId(R.styleable.ProgressActionView_action_error_icon, 0)); | |||
| a.recycle(); | |||
| pendingIcon.setTint(context.getColor(R.color.status_amber)); | |||
| doneIcon.setTint(context.getColor(R.color.status_green)); | |||
| errorIcon.setTint(context.getColor(R.color.status_red)); | |||
| pending(); | |||
| icon.setImageTintList(ColorStateList.valueOf(context.getColor(R.color.status_amber))); | |||
| } | |||
| public void pending() { | |||
| progressBar.setVisibility(GONE); | |||
| switchIcon(pendingIcon, R.color.status_amber); | |||
| icon.setVisibility(VISIBLE); | |||
| } | |||
| public void start() { | |||
| progressBar.setVisibility(VISIBLE); | |||
| icon.setVisibility(GONE); | |||
| feedback.setVisibility(GONE); | |||
| } | |||
| public void done() { | |||
| done(""); | |||
| } | |||
| public void done(String message) { | |||
| progressBar.setVisibility(GONE); | |||
| switchIcon(doneIcon, R.color.status_green); | |||
| icon.setVisibility(VISIBLE); | |||
| if (message.isEmpty()) { | |||
| feedback.setVisibility(GONE); | |||
| } else { | |||
| feedback.setTextColor(context.getColor(R.color.status_amber)); | |||
| feedback.setText(message); | |||
| feedback.setVisibility(VISIBLE); | |||
| } | |||
| } | |||
| public void error(String error) { | |||
| progressBar.setVisibility(GONE); | |||
| switchIcon(errorIcon, R.color.status_red); | |||
| icon.setVisibility(VISIBLE); | |||
| feedback.setTextColor(context.getColor(R.color.status_red)); | |||
| feedback.setText(error); | |||
| feedback.setVisibility(VISIBLE); | |||
| } | |||
| public void info(String info) { | |||
| progressBar.setVisibility(GONE); | |||
| switchIcon(errorIcon, R.color.status_amber); | |||
| icon.setVisibility(VISIBLE); | |||
| this.feedback.setTextColor(context.getColor(R.color.status_amber)); | |||
| this.feedback.setText(info); | |||
| this.feedback.setVisibility(VISIBLE); | |||
| } | |||
| private void switchIcon(Drawable d, int color) { | |||
| icon.setImageDrawable(d); | |||
| icon.setImageTintList(ColorStateList.valueOf(context.getColor(color))); | |||
| } | |||
| public void setTitle(String title) { | |||
| this.title.setText(title); | |||
| } | |||
| public void setErrorIconDrawable(Drawable d) { | |||
| errorIcon = d; | |||
| } | |||
| public void setPendingIconDrawable(Drawable d) { | |||
| pendingIcon = d; | |||
| } | |||
| public void setDoneIconDrawable(Drawable d) { | |||
| doneIcon = d; | |||
| } | |||
| } | |||
| @@ -3,7 +3,6 @@ package lu.circl.mispbump.customViews; | |||
| import android.content.Context; | |||
| import android.content.res.ColorStateList; | |||
| import android.content.res.TypedArray; | |||
| import android.util.AttributeSet; | |||
| import android.view.LayoutInflater; | |||
| import android.view.View; | |||
| @@ -45,15 +44,15 @@ public class UploadAction extends ConstraintLayout { | |||
| View baseView = LayoutInflater.from(context).inflate(R.layout.view_upload_action, this); | |||
| TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.UploadAction); | |||
| // TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.UploadAction); | |||
| // | |||
| // titleView = baseView.findViewById(R.id.title); | |||
| // titleView.setText(a.getString(R.styleable.UploadAction_description)); | |||
| titleView = baseView.findViewById(R.id.title); | |||
| titleView.setText(a.getString(R.styleable.UploadAction_description)); | |||
| a.recycle(); | |||
| // a.recycle(); | |||
| errorView = baseView.findViewById(R.id.error); | |||
| stateView = baseView.findViewById(R.id.stateView); | |||
| stateView = baseView.findViewById(R.id.progressIcon); | |||
| progressBar = baseView.findViewById(R.id.progressBar); | |||
| setCurrentUploadState(UploadState.PENDING); | |||
| @@ -1,68 +0,0 @@ | |||
| package lu.circl.mispbump.fragments; | |||
| import android.content.ClipData; | |||
| import android.content.ClipboardManager; | |||
| import android.content.Context; | |||
| import android.os.Bundle; | |||
| import android.view.LayoutInflater; | |||
| import android.view.View; | |||
| import android.view.ViewGroup; | |||
| import androidx.fragment.app.Fragment; | |||
| import com.google.android.material.snackbar.Snackbar; | |||
| import lu.circl.mispbump.R; | |||
| import lu.circl.mispbump.customViews.MaterialPasswordView; | |||
| import lu.circl.mispbump.customViews.MaterialPreferenceText; | |||
| import lu.circl.mispbump.models.UploadInformation; | |||
| public class UploadCredentialsFragment extends Fragment { | |||
| private View rootLayout; | |||
| private UploadInformation uploadInformation; | |||
| public UploadCredentialsFragment() { | |||
| } | |||
| public UploadCredentialsFragment(UploadInformation uploadInformation) { | |||
| this.uploadInformation = uploadInformation; | |||
| } | |||
| private MaterialPasswordView.OnCopyClickListener onCopyClickListener = new MaterialPasswordView.OnCopyClickListener() { | |||
| @Override | |||
| public void onClick(String title, String password) { | |||
| ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); | |||
| ClipData clip = ClipData.newPlainText(title, password); | |||
| clipboard.setPrimaryClip(clip); | |||
| Snackbar.make(rootLayout, "Copied to clipboard", Snackbar.LENGTH_LONG).show(); | |||
| } | |||
| }; | |||
| @Override | |||
| public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { | |||
| View v = inflater.inflate(R.layout.fragment_upload_credentials, container, false); | |||
| rootLayout = v.findViewById(R.id.rootLayout); | |||
| MaterialPreferenceText baseUrl = v.findViewById(R.id.baseUrl); | |||
| baseUrl.setSubtitle(uploadInformation.getRemote().baseUrl); | |||
| MaterialPreferenceText email = v.findViewById(R.id.email); | |||
| email.setSubtitle(uploadInformation.getLocal().syncUserEmail); | |||
| MaterialPasswordView authkey = v.findViewById(R.id.authkey); | |||
| authkey.setPassword(uploadInformation.getRemote().syncUserAuthkey); | |||
| authkey.addOnCopyClickedListener(onCopyClickListener); | |||
| MaterialPasswordView password = v.findViewById(R.id.password); | |||
| password.setPassword(uploadInformation.getRemote().syncUserPassword); | |||
| password.addOnCopyClickedListener(onCopyClickListener); | |||
| return v; | |||
| } | |||
| } | |||
| @@ -1,89 +0,0 @@ | |||
| package lu.circl.mispbump.fragments; | |||
| import android.os.Bundle; | |||
| import android.view.LayoutInflater; | |||
| import android.view.View; | |||
| import android.view.ViewGroup; | |||
| import androidx.annotation.NonNull; | |||
| import androidx.fragment.app.Fragment; | |||
| import lu.circl.mispbump.R; | |||
| import lu.circl.mispbump.customViews.MaterialPreferenceSwitch; | |||
| import lu.circl.mispbump.models.UploadInformation; | |||
| public class UploadSettingsFragment extends Fragment { | |||
| private MaterialPreferenceSwitch allowSelfSigned, push, pull, cache; | |||
| private UploadInformation uploadInformation; | |||
| public UploadSettingsFragment() { | |||
| } | |||
| public UploadSettingsFragment(UploadInformation uploadInformation) { | |||
| this.uploadInformation = uploadInformation; | |||
| } | |||
| @Override | |||
| public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { | |||
| View v = inflater.inflate(R.layout.fragment_upload_settings, container, false); | |||
| allowSelfSigned = v.findViewById(R.id.self_signed_switch); | |||
| push = v.findViewById(R.id.push_switch); | |||
| pull = v.findViewById(R.id.pull_switch); | |||
| cache = v.findViewById(R.id.cache_switch); | |||
| populateContent(); | |||
| return v; | |||
| } | |||
| private void populateContent() { | |||
| if (uploadInformation == null) { | |||
| return; | |||
| } | |||
| allowSelfSigned.setChecked(uploadInformation.isAllowSelfSigned()); | |||
| push.setChecked(uploadInformation.isPush()); | |||
| pull.setChecked(uploadInformation.isPull()); | |||
| cache.setChecked(uploadInformation.isCached()); | |||
| } | |||
| public void setUploadInfo(UploadInformation uploadInfo) { | |||
| this.uploadInformation = uploadInfo; | |||
| } | |||
| public boolean getAllowSelfSigned() { | |||
| return allowSelfSigned.isChecked(); | |||
| } | |||
| public void setAllowSelfSigned(boolean allowSelfSigned) { | |||
| this.allowSelfSigned.setChecked(allowSelfSigned); | |||
| } | |||
| public boolean getPush() { | |||
| return push.isChecked(); | |||
| } | |||
| public void setPush(boolean push) { | |||
| this.push.setChecked(push); | |||
| } | |||
| public boolean getPull() { | |||
| return pull.isChecked(); | |||
| } | |||
| public void setPull(boolean pull) { | |||
| this.pull.setChecked(pull); | |||
| } | |||
| public boolean getCache() { | |||
| return cache.isChecked(); | |||
| } | |||
| public void setCache(boolean cache) { | |||
| this.cache.setChecked(cache); | |||
| } | |||
| } | |||
| @@ -21,7 +21,7 @@ import retrofit2.http.Path; | |||
| /** | |||
| * RetroFit2 interface for communication with misp instances | |||
| */ | |||
| public interface MispRestInterface { | |||
| public interface MispService { | |||
| // settings routes | |||
| @@ -0,0 +1,44 @@ | |||
| package lu.circl.mispbump.models; | |||
| import androidx.annotation.NonNull; | |||
| import lu.circl.mispbump.models.restModels.Organisation; | |||
| import lu.circl.mispbump.models.restModels.Server; | |||
| import lu.circl.mispbump.models.restModels.User; | |||
| public class ExchangeInformation { | |||
| private Organisation organisation; | |||
| private User syncUser; | |||
| private Server server; | |||
| public Organisation getOrganisation() { | |||
| return organisation; | |||
| } | |||
| public void setOrganisation(Organisation organisation) { | |||
| this.organisation = organisation; | |||
| } | |||
| public User getSyncUser() { | |||
| return syncUser; | |||
| } | |||
| public void setSyncUser(User syncUser) { | |||
| this.syncUser = syncUser; | |||
| } | |||
| public Server getServer() { | |||
| return server; | |||
| } | |||
| public void setServer(Server server) { | |||
| this.server = server; | |||
| } | |||
| @NonNull | |||
| @Override | |||
| public String toString() { | |||
| return "Exchange Information: \n" + organisation.toString() + "\n" + syncUser.toString() + "\n" + server.toString(); | |||
| } | |||
| } | |||
| @@ -3,29 +3,118 @@ package lu.circl.mispbump.models; | |||
| import androidx.annotation.NonNull; | |||
| import com.google.gson.annotations.SerializedName; | |||
| import java.text.SimpleDateFormat; | |||
| import java.util.Calendar; | |||
| import java.util.Date; | |||
| import java.util.Locale; | |||
| import java.util.UUID; | |||
| import lu.circl.mispbump.models.restModels.Organisation; | |||
| import lu.circl.mispbump.models.restModels.Server; | |||
| import lu.circl.mispbump.models.restModels.User; | |||
| /** | |||
| * A Class that holds the information needed synchronize two misp instances. | |||
| * Class that holds the information needed synchronize two misp instances. | |||
| */ | |||
| public class SyncInformation { | |||
| public Organisation organisation; | |||
| public String syncUserEmail; | |||
| public String syncUserPassword; | |||
| public String syncUserAuthkey; | |||
| public String baseUrl; | |||
| private UUID uuid; | |||
| private Date date, lastModified; | |||
| @SerializedName("organisation") | |||
| private Organisation remoteOrganisation; | |||
| private User syncUser; | |||
| private Server syncServer; | |||
| private ExchangeInformation remote; | |||
| private ExchangeInformation local; | |||
| public SyncInformation() { | |||
| uuid = UUID.randomUUID(); | |||
| setSyncDate(); | |||
| } | |||
| public UUID getUuid() { | |||
| return uuid; | |||
| } | |||
| public void setUuid(UUID uuid) { | |||
| this.uuid = uuid; | |||
| } | |||
| public Date getLastModified() { | |||
| return lastModified; | |||
| } | |||
| public void setLastModified() { | |||
| setLastModified(Calendar.getInstance().getTime()); | |||
| } | |||
| public void setLastModified(Date date) { | |||
| lastModified = date; | |||
| } | |||
| public void setSyncDate() { | |||
| setSyncDate(Calendar.getInstance().getTime()); | |||
| } | |||
| public void setSyncDate(Date date) { | |||
| this.date = date; | |||
| this.lastModified = date; | |||
| } | |||
| public Date getSyncDate() { | |||
| return date; | |||
| } | |||
| private String getSyncDateString() { | |||
| SimpleDateFormat df = new SimpleDateFormat("dd.MM.yyyy", Locale.getDefault()); | |||
| return df.format(date); | |||
| } | |||
| public Organisation getRemoteOrganisation() { | |||
| return remoteOrganisation; | |||
| } | |||
| public void setRemoteOrganisation(Organisation organisation) { | |||
| this.remoteOrganisation = organisation; | |||
| } | |||
| public User getSyncUser() { | |||
| return syncUser; | |||
| } | |||
| public void setSyncUser(User syncUser) { | |||
| this.syncUser = syncUser; | |||
| } | |||
| public Server getSyncServer() { | |||
| return syncServer; | |||
| } | |||
| public void setSyncServer(Server syncServer) { | |||
| this.syncServer = syncServer; | |||
| } | |||
| public ExchangeInformation getLocal() { | |||
| return local; | |||
| } | |||
| public void setLocal(ExchangeInformation local) { | |||
| this.local = local; | |||
| } | |||
| public void populateRemoteExchangeInformation(ExchangeInformation exchangeInformation) { | |||
| this.remoteOrganisation = exchangeInformation.getOrganisation(); | |||
| this.syncUser = exchangeInformation.getSyncUser(); | |||
| this.syncServer = exchangeInformation.getServer(); | |||
| } | |||
| @NonNull | |||
| @Override | |||
| public String toString() { | |||
| return "SyncInformation{" + | |||
| "organisation=" + organisation + | |||
| ", syncUserEmail='" + syncUserEmail + '\'' + | |||
| ", syncUserPassword='" + syncUserPassword + '\'' + | |||
| ", syncUserAuthkey='" + syncUserAuthkey + '\'' + | |||
| ", baseUrl='" + baseUrl + '\'' + | |||
| '}'; | |||
| return "Sync Information: \n" + | |||
| "UUID = " + uuid + "\n" + | |||
| "Date = " + getSyncDateString() + "\n" + | |||
| remoteOrganisation.toString() + "\n" + | |||
| syncUser.toString() + "\n" + | |||
| syncServer.toString() + "\n" + | |||
| local.toString(); | |||
| } | |||
| } | |||
| @@ -1,91 +0,0 @@ | |||
| package lu.circl.mispbump.models; | |||
| import androidx.annotation.NonNull; | |||
| import java.util.UUID; | |||
| import lu.circl.mispbump.auxiliary.MispRestClient; | |||
| import lu.circl.mispbump.models.restModels.Organisation; | |||
| import lu.circl.mispbump.models.restModels.Server; | |||
| public class SyncModel { | |||
| private UUID uuid; | |||
| private Server server; | |||
| private Organisation organisation; | |||
| private Organisation remoteOrganisation; | |||
| public Server getServer() { | |||
| return server; | |||
| } | |||
| public void setServer(Server server) { | |||
| this.server = server; | |||
| } | |||
| public Organisation getOrganisation() { | |||
| return organisation; | |||
| } | |||
| public void setOrganisation(Organisation organisation) { | |||
| this.organisation = organisation; | |||
| } | |||
| public Organisation getRemoteOrganisation() { | |||
| return remoteOrganisation; | |||
| } | |||
| public void setRemoteOrganisation(Organisation remoteOrganisation) { | |||
| this.remoteOrganisation = remoteOrganisation; | |||
| } | |||
| public static void createFromServer(MispRestClient mispRestClient, Server server, InitializeWithServerObject callback) { | |||
| SyncModel syncModel = new SyncModel(); | |||
| syncModel.server = server; | |||
| mispRestClient.getOrganisation(server.getOrg_id(), new MispRestClient.OrganisationCallback() { | |||
| @Override | |||
| public void success(Organisation organisation) { | |||
| syncModel.organisation = organisation; | |||
| mispRestClient.getOrganisation(server.getRemote_org_id(), new MispRestClient.OrganisationCallback() { | |||
| @Override | |||
| public void success(Organisation organisation) { | |||
| syncModel.remoteOrganisation = organisation; | |||
| callback.success(syncModel); | |||
| } | |||
| @Override | |||
| public void failure(String error) { | |||
| callback.failure(error); | |||
| } | |||
| }); | |||
| } | |||
| @Override | |||
| public void failure(String error) { | |||
| callback.failure(error); | |||
| } | |||
| }); | |||
| } | |||
| @NonNull | |||
| @Override | |||
| public String toString() { | |||
| return server.toString() + "\n" + organisation.toString() + "\n" + remoteOrganisation.toString(); | |||
| } | |||
| public interface InitializeWithServerObject { | |||
| void success(SyncModel syncModel); | |||
| void failure(String error); | |||
| } | |||
| } | |||
| @@ -1,130 +0,0 @@ | |||
| package lu.circl.mispbump.models; | |||
| import androidx.annotation.NonNull; | |||
| import java.text.SimpleDateFormat; | |||
| import java.util.Calendar; | |||
| import java.util.Date; | |||
| import java.util.Locale; | |||
| import java.util.UUID; | |||
| public class UploadInformation { | |||
| public enum SyncStatus { | |||
| COMPLETE, | |||
| FAILURE, | |||
| PENDING | |||
| } | |||
| private UUID uuid; | |||
| private SyncStatus currentSyncStatus = SyncStatus.PENDING; | |||
| private boolean allowSelfSigned, pull, push, cached; | |||
| private SyncInformation local; | |||
| private SyncInformation remote; | |||
| private Date date; | |||
| public UploadInformation() { | |||
| uuid = UUID.randomUUID(); | |||
| date = Calendar.getInstance().getTime(); | |||
| } | |||
| // getter and setter | |||
| public void setCurrentSyncStatus(SyncStatus status) { | |||
| currentSyncStatus = status; | |||
| } | |||
| public SyncStatus getCurrentSyncStatus() { | |||
| return currentSyncStatus; | |||
| } | |||
| public void setLocal(SyncInformation local) { | |||
| this.local = local; | |||
| } | |||
| public SyncInformation getLocal() { | |||
| return local; | |||
| } | |||
| public void setRemote(SyncInformation remote) { | |||
| this.remote = remote; | |||
| } | |||
| public SyncInformation getRemote() { | |||
| return remote; | |||
| } | |||
| public UUID getUuid() { | |||
| return uuid; | |||
| } | |||
| public void setUuid(UUID uuid) { | |||
| this.uuid = uuid; | |||
| } | |||
| public void setDate() { | |||
| setDate(Calendar.getInstance().getTime()); | |||
| } | |||
| public void setDate(Date date) { | |||
| this.date = date; | |||
| } | |||
| public Date getDate() { | |||
| return date; | |||
| } | |||
| public String getDateString() { | |||
| SimpleDateFormat df = new SimpleDateFormat("dd.MM.yyyy", Locale.getDefault()); | |||
| return df.format(date); | |||
| } | |||
| public boolean isAllowSelfSigned() { | |||
| return allowSelfSigned; | |||
| } | |||
| public void setAllowSelfSigned(boolean allowSelfSigned) { | |||
| this.allowSelfSigned = allowSelfSigned; | |||
| } | |||
| public boolean isPull() { | |||
| return pull; | |||
| } | |||
| public void setPull(boolean pull) { | |||
| this.pull = pull; | |||
| } | |||
| public boolean isPush() { | |||
| return push; | |||
| } | |||
| public void setPush(boolean push) { | |||
| this.push = push; | |||
| } | |||
| public boolean isCached() { | |||
| return cached; | |||
| } | |||
| public void setCached(boolean cached) { | |||
| this.cached = cached; | |||
| } | |||
| @NonNull | |||
| @Override | |||
| public String toString() { | |||
| return "UploadInformation{" + | |||
| "currentSyncStatus=" + currentSyncStatus + | |||
| ", local=" + local.toString() + | |||
| ", remote=" + remote.toString() + | |||
| ", date=" + date + | |||
| '}'; | |||
| } | |||
| } | |||
| @@ -3,6 +3,8 @@ package lu.circl.mispbump.models.restModels; | |||
| import androidx.annotation.NonNull; | |||
| import java.util.Arrays; | |||
| /** | |||
| * Information gathered from Misp API about a organisation. | |||
| @@ -20,22 +22,18 @@ public class Organisation { | |||
| private String description; | |||
| private Boolean local; | |||
| private String uuid; | |||
| // private String[] restricted_to_domain; | |||
| private String[] restricted_to_domain; | |||
| private String created_by; | |||
| private Integer user_count; | |||
| public Organisation() { | |||
| } | |||
| public Organisation toSyncOrganisation() { | |||
| Organisation organisation = new Organisation(); | |||
| organisation.local = true; // TODO REMOVE FROME HERE! | |||
| organisation.name = name; | |||
| organisation.uuid = uuid; | |||
| organisation.description = description; | |||
| organisation.nationality = nationality; | |||
| organisation.sector = sector; | |||
| organisation.type = "Sync organisation"; | |||
| organisation.contacts = contacts; | |||
| return organisation; | |||
| @@ -130,13 +128,13 @@ public class Organisation { | |||
| this.uuid = uuid; | |||
| } | |||
| // public String[] getRestricted_to_domain() { | |||
| // return restricted_to_domain; | |||
| // } | |||
| public String[] getRestricted_to_domain() { | |||
| return restricted_to_domain; | |||
| } | |||
| // public void setRestricted_to_domain(String[] restricted_to_domain) { | |||
| // this.restricted_to_domain = restricted_to_domain; | |||
| // } | |||
| public void setRestricted_to_domain(String[] restricted_to_domain) { | |||
| this.restricted_to_domain = restricted_to_domain; | |||
| } | |||
| public String getCreated_by() { | |||
| return created_by; | |||
| @@ -157,21 +155,20 @@ public class Organisation { | |||
| @NonNull | |||
| @Override | |||
| public String toString() { | |||
| return "Organisation{" + | |||
| "id=" + id + | |||
| ", name='" + name + '\'' + | |||
| ", date_created='" + date_created + '\'' + | |||
| ", date_modified='" + date_modified + '\'' + | |||
| ", type='" + type + '\'' + | |||
| ", nationality='" + nationality + '\'' + | |||
| ", sector='" + sector + '\'' + | |||
| ", contacts='" + contacts + '\'' + | |||
| ", description='" + description + '\'' + | |||
| ", local=" + local + | |||
| ", uuid='" + uuid + '\'' + | |||
| // ", restricted_to_domain='" + Arrays.toString(restricted_to_domain) + '\'' + | |||
| ", created_by='" + created_by + '\'' + | |||
| ", user_count=" + user_count + | |||
| '}'; | |||
| return "Organisation: \n" + | |||
| "\t id = " + id + "\n" + | |||
| "\t name = " + name + '\n' + | |||
| "\t date_created = " + date_created + '\n' + | |||
| "\t date_modified = " + date_modified + '\n' + | |||
| "\t type = " + type + '\n' + | |||
| "\t nationality = " + nationality + '\n' + | |||
| "\t sector = " + sector + '\n' + | |||
| "\t contacts = " + contacts + '\n' + | |||
| "\t description = " + description + '\n' + | |||
| "\t local = " + local + '\n' + | |||
| "\t uuid = " + uuid + '\n' + | |||
| "\t restricted_to_domain = " + Arrays.toString(restricted_to_domain) + '\n' + | |||
| "\t created_by = " + created_by + '\n' + | |||
| "\t user_count = " + user_count; | |||
| } | |||
| } | |||
| @@ -11,25 +11,30 @@ public class Server { | |||
| private String url; | |||
| private String authkey; | |||
| private Integer org_id; | |||
| private Boolean push; | |||
| private Boolean pull; | |||
| private Boolean push = false; | |||
| private Boolean pull = false; | |||
| private Object lastpulledid; | |||
| private Object lastpushedid; | |||
| private Object organization; | |||
| private Integer remote_org_id; | |||
| private Boolean publish_without_email; | |||
| private Boolean unpublish_event; | |||
| private Boolean self_signed; | |||
| private Boolean publish_without_email = false; | |||
| private Boolean unpublish_event = false; | |||
| private Boolean self_signed = false; | |||
| private String pull_rules; | |||
| private String push_rules; | |||
| private Object cert_file; | |||
| private Object client_cert_file; | |||
| private Boolean internal; | |||
| private Boolean skip_proxy; | |||
| private Boolean caching_enabled; | |||
| private Boolean caching_enabled = false; | |||
| private Boolean cache_timestamp; | |||
| public Server(String url) { | |||
| this.url = url; | |||
| } | |||
| public Integer getId() { | |||
| return id; | |||
| } | |||
| @@ -209,29 +214,28 @@ public class Server { | |||
| @NonNull | |||
| @Override | |||
| public String toString() { | |||
| return "Server{" + | |||
| "id=" + id + | |||
| ", name='" + name + '\'' + | |||
| ", url='" + url + '\'' + | |||
| ", authkey='" + authkey + '\'' + | |||
| ", org_id=" + org_id + | |||
| ", push=" + push + | |||
| ", pull=" + pull + | |||
| ", lastpulledid=" + lastpulledid + | |||
| ", lastpushedid=" + lastpushedid + | |||
| ", organization=" + organization + | |||
| ", remote_org_id=" + remote_org_id + | |||
| ", publish_without_email=" + publish_without_email + | |||
| ", unpublish_event=" + unpublish_event + | |||
| ", self_signed=" + self_signed + | |||
| ", pull_rules='" + pull_rules + '\'' + | |||
| ", push_rules='" + push_rules + '\'' + | |||
| ", cert_file=" + cert_file + | |||
| ", client_cert_file=" + client_cert_file + | |||
| ", internal=" + internal + | |||
| ", skip_proxy=" + skip_proxy + | |||
| ", caching_enabled=" + caching_enabled + | |||
| ", cache_timestamp=" + cache_timestamp + | |||
| '}'; | |||
| return "Server: \n" + | |||
| "\t id = " + id + '\n' + | |||
| "\t name = " + name + '\n' + | |||
| "\t url = " + url + '\n' + | |||
| "\t authkey = " + authkey + '\n' + | |||
| "\t org_id = " + org_id + '\n' + | |||
| "\t push = " + push + '\n' + | |||
| "\t pull = " + pull + '\n' + | |||
| "\t lastpulledid = " + lastpulledid + '\n' + | |||
| "\t lastpushedid = " + lastpushedid + '\n' + | |||
| "\t organization = " + organization + '\n' + | |||
| "\t remote_org_id = " + remote_org_id + '\n' + | |||
| "\t publish_without_email = " + publish_without_email + '\n' + | |||
| "\t unpublish_event = " + unpublish_event + '\n' + | |||
| "\t self_signed = " + self_signed + '\n' + | |||
| "\t pull_rules = " + pull_rules + '\n' + | |||
| "\t push_rules = " + push_rules + '\n' + | |||
| "\t cert_file = " + cert_file + '\n' + | |||
| "\t client_cert_file = " + client_cert_file + '\n' + | |||
| "\t internal = " + internal + '\n' + | |||
| "\t skip_proxy = " + skip_proxy + '\n' + | |||
| "\t caching_enabled = " + caching_enabled + '\n' + | |||
| "\t cache_timestamp = " + cache_timestamp; | |||
| } | |||
| } | |||
| @@ -3,6 +3,8 @@ package lu.circl.mispbump.models.restModels; | |||
| import androidx.annotation.NonNull; | |||
| import lu.circl.mispbump.auxiliary.RandomString; | |||
| public class User { | |||
| @@ -30,6 +32,16 @@ public class User { | |||
| private String date_modified; | |||
| public User toSyncUser() { | |||
| User user = new User(); | |||
| user.email = email; | |||
| user.authkey = new RandomString(40).nextString(); | |||
| user.password = new RandomString(16).nextString(); | |||
| return user; | |||
| } | |||
| public Integer getId() { | |||
| return id; | |||
| } | |||
| @@ -210,29 +222,28 @@ public class User { | |||
| @NonNull | |||
| @Override | |||
| public String toString() { | |||
| return "User{" + | |||
| "id='" + id + '\'' + | |||
| ", password='" + password + '\'' + | |||
| ", org_id='" + org_id + '\'' + | |||
| ", email='" + email + '\'' + | |||
| ", autoalert=" + autoalert + | |||
| ", authkey='" + authkey + '\'' + | |||
| ", invited_by='" + invited_by + '\'' + | |||
| ", gpgkey=" + gpgkey + | |||
| ", certif_public='" + certif_public + '\'' + | |||
| ", nids_sid='" + nids_sid + '\'' + | |||
| ", termsaccepted=" + termsaccepted + | |||
| ", newsread='" + newsread + '\'' + | |||
| ", role_id='" + role_id + '\'' + | |||
| ", change_pw='" + change_pw + '\'' + | |||
| ", contactalert=" + contactalert + | |||
| ", disabled=" + disabled + | |||
| ", expiration=" + expiration + | |||
| ", current_login='" + current_login + '\'' + | |||
| ", last_login='" + last_login + '\'' + | |||
| ", force_logout=" + force_logout + | |||
| ", date_created=" + date_created + | |||
| ", date_modified='" + date_modified + '\'' + | |||
| '}'; | |||
| return "User: \n" + | |||
| "\t id = " + id + '\n' + | |||
| "\t password = " + password + '\n' + | |||
| "\t org_id = " + org_id + '\n' + | |||
| "\t email = " + email + '\n' + | |||
| "\t autoalert = " + autoalert + '\n' + | |||
| "\t authkey = " + authkey + '\n' + | |||
| "\t invited_by = " + invited_by + '\n' + | |||
| "\t gpgkey = " + gpgkey + '\n' + | |||
| "\t certif_public = " + certif_public + '\n' + | |||
| "\t nids_sid = " + nids_sid + '\n' + | |||
| "\t termsaccepted = " + termsaccepted + '\n' + | |||
| "\t newsread = " + newsread + '\n' + | |||
| "\t role_id = " + role_id + '\n' + | |||
| "\t change_pw = " + change_pw + '\n' + | |||
| "\t contactalert = " + contactalert + '\n' + | |||
| "\t disabled = " + disabled + '\n' + | |||
| "\t expiration = " + expiration + '\n' + | |||
| "\t current_login = " + current_login + '\n' + | |||
| "\t last_login = " + last_login + '\n' + | |||
| "\t force_logout = " + force_logout + '\n' + | |||
| "\t date_created = " + date_created + '\n' + | |||
| "\t date_modified = " + date_modified; | |||
| } | |||
| } | |||
| @@ -32,7 +32,7 @@ public class KeyStoreWrapper { | |||
| public static final String USER_INFO_ALIAS = "ALIAS_USER_INFO"; | |||
| public static final String USER_ORGANISATION_INFO_ALIAS = "ALIAS_USER_ORGANISATION_INFO"; | |||
| public static final String USER_CREDENTIALS_ALIAS = "ALIAS_USER_CREDENTIALS"; | |||
| public static final String UPLOAD_INFORMATION_ALIAS = "ALIAS_UPLOAD_INFORMATION"; | |||
| public static final String SYNC_INFORMATION_ALIAS = "ALIAS_UPLOAD_INFORMATION"; | |||
| private static final String KEYSTORE_PROVIDER = "AndroidKeyStore"; | |||
| private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding"; | |||
| @@ -0,0 +1,9 @@ | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:width="24dp" | |||
| android:height="24dp" | |||
| android:viewportWidth="24" | |||
| android:viewportHeight="24"> | |||
| <path | |||
| android:fillColor="#FF000000" | |||
| android:pathData="M8.12,9.29L12,13.17l3.88,-3.88c0.39,-0.39 1.02,-0.39 1.41,0 0.39,0.39 0.39,1.02 0,1.41l-4.59,4.59c-0.39,0.39 -1.02,0.39 -1.41,0L6.7,10.7c-0.39,-0.39 -0.39,-1.02 0,-1.41 0.39,-0.38 1.03,-0.39 1.42,0z"/> | |||
| </vector> | |||
| @@ -0,0 +1,9 @@ | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:width="48dp" | |||
| android:height="48dp" | |||
| android:viewportWidth="48" | |||
| android:viewportHeight="48"> | |||
| <path | |||
| android:fillColor="#FFFFFFFF" | |||
| android:pathData="M38.71,20.07C37.35,13.19 31.28,8 24,8c-5.78,0 -10.79,3.28 -13.3,8.07C4.69,16.72 0,21.81 0,28c0,6.63 5.37,12 12,12h26c5.52,0 10,-4.48 10,-10 0,-5.28 -4.11,-9.56 -9.29,-9.93zM34,26L24,36 14,26h6v-8h8v8h6z"/> | |||
| </vector> | |||
| @@ -0,0 +1,6 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <shape | |||
| xmlns:android="http://schemas.android.com/apk/res/android"> | |||
| <solid android:color="@color/white"/> | |||
| <corners android:radius="4dp"/> | |||
| </shape> | |||
| @@ -0,0 +1,8 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:tools="http://schemas.android.com/tools" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| tools:context=".activities.NetworkTestActivity"> | |||
| </androidx.constraintlayout.widget.ConstraintLayout> | |||
| @@ -0,0 +1,289 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <androidx.coordinatorlayout.widget.CoordinatorLayout | |||
| xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:app="http://schemas.android.com/apk/res-auto" | |||
| xmlns:tools="http://schemas.android.com/tools" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| tools:context=".activities.SyncInfoDetailActivity" | |||
| android:orientation="vertical"> | |||
| <com.google.android.material.appbar.AppBarLayout | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content"> | |||
| <androidx.appcompat.widget.Toolbar | |||
| android:id="@+id/toolbar" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="?attr/actionBarSize" | |||
| android:theme="@style/ToolbarTheme" | |||
| app:popupTheme="@style/PopupTheme" /> | |||
| </com.google.android.material.appbar.AppBarLayout> | |||
| <ScrollView | |||
| app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent"> | |||
| <LinearLayout | |||
| android:orientation="vertical" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content"> | |||
| <lu.circl.expandablecardview.ExpandableCardView | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:layout_margin="8dp" | |||
| app:card_header_background_color="@color/colorPrimary" | |||
| app:card_header_foreground_color="@color/white" | |||
| app:card_icon="@drawable/ic_info_outline" | |||
| app:card_title="Information"> | |||
| <LinearLayout | |||
| android:orientation="vertical" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content"> | |||
| <lu.circl.mispbump.customViews.MaterialPreferenceText | |||
| android:id="@+id/name" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:title="Name" | |||
| app:subtitle="No name" /> | |||
| <lu.circl.mispbump.customViews.MaterialPreferenceText | |||
| android:id="@+id/uuid" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toBottomOf="@id/name" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:title="UUID" | |||
| app:subtitle="No UUID" /> | |||
| <lu.circl.mispbump.customViews.MaterialPreferenceText | |||
| android:id="@+id/sector" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toBottomOf="@id/uuid" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:title="Sector" | |||
| app:subtitle="No sector" /> | |||
| <lu.circl.mispbump.customViews.MaterialPreferenceText | |||
| android:id="@+id/description" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toBottomOf="@id/uuid" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:title="@string/description" | |||
| app:subtitle="No description" /> | |||
| </LinearLayout> | |||
| </lu.circl.expandablecardview.ExpandableCardView> | |||
| <lu.circl.expandablecardview.ExpandableCardView | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:layout_margin="8dp" | |||
| app:card_header_background_color="@color/colorPrimary" | |||
| app:card_header_foreground_color="@color/white" | |||
| app:card_icon="@drawable/ic_settings" | |||
| app:card_title="@string/settings"> | |||
| <androidx.constraintlayout.widget.ConstraintLayout | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent"> | |||
| <CheckBox | |||
| android:id="@+id/checkbox_self_signed" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_marginTop="16dp" | |||
| android:text="@string/settings_self_signed_title" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" /> | |||
| <CheckBox | |||
| android:id="@+id/checkbox_push" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_marginTop="8dp" | |||
| android:text="@string/settings_push_title" | |||
| app:layout_constraintHorizontal_chainStyle="packed" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toBottomOf="@id/checkbox_self_signed" /> | |||
| <CheckBox | |||
| android:id="@+id/checkbox_pull" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="32dp" | |||
| android:layout_marginTop="8dp" | |||
| android:text="@string/settings_pull_title" | |||
| app:layout_constraintStart_toEndOf="@+id/checkbox_push" | |||
| app:layout_constraintTop_toBottomOf="@id/checkbox_self_signed" /> | |||
| <CheckBox | |||
| android:id="@+id/checkbox_cache" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_marginTop="8dp" | |||
| android:layout_marginBottom="16dp" | |||
| android:text="@string/settings_cache_title" | |||
| app:layout_constraintTop_toBottomOf="@id/checkbox_push" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintBottom_toBottomOf="parent" /> | |||
| </androidx.constraintlayout.widget.ConstraintLayout> | |||
| </lu.circl.expandablecardview.ExpandableCardView> | |||
| <lu.circl.expandablecardview.ExpandableCardView | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:layout_margin="8dp" | |||
| app:card_header_background_color="@color/colorPrimary" | |||
| app:card_header_foreground_color="@color/white" | |||
| app:card_icon="@drawable/ic_verified_user" | |||
| app:card_title="@string/credentials"> | |||
| <LinearLayout | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:orientation="vertical"> | |||
| <lu.circl.mispbump.customViews.MaterialPreferenceText | |||
| android:id="@+id/email" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:title="Email" | |||
| app:subtitle="Keine Ahnung" /> | |||
| <lu.circl.mispbump.customViews.MaterialPasswordView | |||
| android:id="@+id/password" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| app:title="Password" | |||
| app:password="Weiß ich leider auch nicht" /> | |||
| <lu.circl.mispbump.customViews.MaterialPasswordView | |||
| android:id="@+id/authkey" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| app:title="Authkey" | |||
| app:password="Den erst recht nicht ..." /> | |||
| </LinearLayout> | |||
| </lu.circl.expandablecardview.ExpandableCardView> | |||
| <View | |||
| android:layout_width="match_parent" | |||
| android:layout_height="86dp"/> | |||
| </LinearLayout> | |||
| </ScrollView> | |||
| <androidx.constraintlayout.widget.ConstraintLayout | |||
| android:layout_gravity="bottom|end" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| android:clipToPadding="false"> | |||
| <!-- <FrameLayout--> | |||
| <!-- android:layout_width="match_parent"--> | |||
| <!-- android:layout_height="match_parent"--> | |||
| <!-- android:background="@color/black_50"/>--> | |||
| <com.google.android.material.floatingactionbutton.FloatingActionButton | |||
| android:id="@+id/fab_main" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:src="@drawable/ic_keyboard_arrow_down" | |||
| android:tint="@color/white" | |||
| android:layout_margin="16dp" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintBottom_toBottomOf="parent"/> | |||
| <LinearLayout | |||
| android:id="@+id/layout_upload" | |||
| android:visibility="visible" | |||
| android:orientation="horizontal" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginEnd="16dp" | |||
| android:layout_marginBottom="16dp" | |||
| android:padding="8dp" | |||
| android:clipToPadding="false" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintBottom_toTopOf="@id/fab_main"> | |||
| <TextView | |||
| android:text="Upload to MISP instance" | |||
| android:textColor="@color/white" | |||
| android:padding="8dp" | |||
| android:background="@drawable/tooltip_background" | |||
| android:backgroundTint="@color/black_70" | |||
| android:layout_gravity="center_vertical" | |||
| android:layout_marginEnd="16dp" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content"/> | |||
| <com.google.android.material.floatingactionbutton.FloatingActionButton | |||
| android:id="@+id/fab_upload" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:src="@drawable/ic_cloud_upload" | |||
| app:fabSize="mini"/> | |||
| </LinearLayout> | |||
| <LinearLayout | |||
| android:visibility="visible" | |||
| android:id="@+id/layout_download" | |||
| android:orientation="horizontal" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginEnd="16dp" | |||
| android:layout_marginBottom="16dp" | |||
| android:padding="8dp" | |||
| android:clipToPadding="false" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintBottom_toTopOf="@id/layout_upload"> | |||
| <TextView | |||
| android:text="Download changes from MISP instance" | |||
| android:textColor="@color/white" | |||
| android:padding="8dp" | |||
| android:background="@drawable/tooltip_background" | |||
| android:backgroundTint="@color/black_70" | |||
| android:layout_gravity="center_vertical" | |||
| android:layout_marginEnd="16dp" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content"/> | |||
| <com.google.android.material.floatingactionbutton.FloatingActionButton | |||
| android:id="@+id/fab_download" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:src="@drawable/ic_cloud_download" | |||
| app:fabSize="mini"/> | |||
| </LinearLayout> | |||
| </androidx.constraintlayout.widget.ConstraintLayout> | |||
| </androidx.coordinatorlayout.widget.CoordinatorLayout> | |||
| @@ -6,7 +6,6 @@ | |||
| android:id="@+id/rootLayout" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| android:animateLayoutChanges="true" | |||
| tools:context=".activities.UploadActivity"> | |||
| <com.google.android.material.appbar.AppBarLayout | |||
| @@ -17,78 +16,78 @@ | |||
| android:id="@+id/toolbar" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="?attr/actionBarSize" | |||
| android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" | |||
| app:popupTheme="@style/ThemeOverlay.AppCompat.Light" | |||
| android:background="@color/colorPrimary"> | |||
| <TextView | |||
| android:id="@+id/toolbarTitle" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:textAppearance="@style/TextAppearance.AppCompat.Widget.ActionBar.Title" | |||
| android:text="@string/upload" | |||
| android:layout_gravity="center"/> | |||
| </androidx.appcompat.widget.Toolbar> | |||
| android:theme="@style/ToolbarTheme" | |||
| app:popupTheme="@style/PopupTheme"/> | |||
| </com.google.android.material.appbar.AppBarLayout> | |||
| <androidx.appcompat.widget.LinearLayoutCompat | |||
| <LinearLayout | |||
| app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" | |||
| android:orientation="vertical" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent" | |||
| android:animateLayoutChanges="true"> | |||
| android:layout_height="wrap_content" | |||
| android:orientation="vertical"> | |||
| <lu.circl.mispbump.customViews.UploadAction | |||
| android:id="@+id/availableAction" | |||
| <lu.circl.mispbump.customViews.ProgressActionView | |||
| android:layout_marginTop="8dp" | |||
| android:id="@+id/availableProgressAction" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginTop="16dp" | |||
| android:background="@color/white" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:description="@string/upload_action_available"/> | |||
| app:action_title="Instance available" | |||
| app:action_done_icon="@drawable/ic_check_outline" | |||
| app:action_error_icon="@drawable/ic_error_outline" | |||
| app:action_pending_icon="@drawable/ic_pending"/> | |||
| <lu.circl.mispbump.customViews.UploadAction | |||
| android:id="@+id/orgAction" | |||
| <View | |||
| android:background="@color/black_10" | |||
| android:layout_margin="8dp" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="1dp"/> | |||
| <lu.circl.mispbump.customViews.ProgressActionView | |||
| android:id="@+id/organisationProgressAction" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginTop="16dp" | |||
| android:background="@color/white" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:description="@string/upload_action_add_org"/> | |||
| app:action_title="Add Organisation" | |||
| app:action_done_icon="@drawable/ic_check_outline" | |||
| app:action_error_icon="@drawable/ic_error_outline" | |||
| app:action_pending_icon="@drawable/ic_pending"/> | |||
| <View | |||
| android:background="@color/black_10" | |||
| android:layout_margin="8dp" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="1dp"/> | |||
| <lu.circl.mispbump.customViews.UploadAction | |||
| android:id="@+id/userAction" | |||
| <lu.circl.mispbump.customViews.ProgressActionView | |||
| android:id="@+id/userProgressAction" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginTop="16dp" | |||
| android:background="@color/white" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| app:description="@string/upload_action_add_user"/> | |||
| app:action_title="Add Sync User" | |||
| app:action_done_icon="@drawable/ic_check_outline" | |||
| app:action_error_icon="@drawable/ic_error_outline" | |||
| app:action_pending_icon="@drawable/ic_pending"/> | |||
| <View | |||
| android:background="@color/black_10" | |||
| android:layout_margin="8dp" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="1dp"/> | |||
| <lu.circl.mispbump.customViews.UploadAction | |||
| android:id="@+id/serverAction" | |||
| <lu.circl.mispbump.customViews.ProgressActionView | |||
| android:id="@+id/serverProgressAction" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginTop="16dp" | |||
| android:background="@color/white" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| app:description="@string/upload_action_add_server"/> | |||
| </androidx.appcompat.widget.LinearLayoutCompat> | |||
| app:action_title="Add Sync Server" | |||
| app:action_done_icon="@drawable/ic_check_outline" | |||
| app:action_error_icon="@drawable/ic_error_outline" | |||
| app:action_pending_icon="@drawable/ic_pending"/> | |||
| <View | |||
| android:background="@color/black_10" | |||
| android:layout_margin="8dp" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="1dp"/> | |||
| </LinearLayout> | |||
| <com.google.android.material.floatingactionbutton.FloatingActionButton | |||
| android:id="@+id/fab" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_gravity="bottom|end" | |||
| android:layout_margin="16dp" | |||
| android:src="@drawable/ic_check"/> | |||
| </androidx.coordinatorlayout.widget.CoordinatorLayout> | |||
| @@ -1,51 +0,0 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <ScrollView | |||
| xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:app="http://schemas.android.com/apk/res-auto" | |||
| android:id="@+id/rootLayout" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent"> | |||
| <LinearLayout | |||
| android:orientation="vertical" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:clipToPadding="false"> | |||
| <lu.circl.mispbump.customViews.MaterialPreferenceText | |||
| android:id="@+id/baseUrl" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginTop="8dp" | |||
| android:background="@color/white" | |||
| app:subtitle="www.google.de" | |||
| app:title="@string/url"/> | |||
| <lu.circl.mispbump.customViews.MaterialPreferenceText | |||
| android:id="@+id/email" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginTop="8dp" | |||
| android:background="@color/white" | |||
| app:subtitle="syncuser_orga@mispx.yz" | |||
| app:title="@string/email"/> | |||
| <lu.circl.mispbump.customViews.MaterialPasswordView | |||
| android:id="@+id/password" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginTop="8dp" | |||
| android:background="@color/white" | |||
| app:title="@string/password" | |||
| app:password="abc"/> | |||
| <lu.circl.mispbump.customViews.MaterialPasswordView | |||
| android:id="@+id/authkey" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginTop="8dp" | |||
| android:background="@color/white" | |||
| app:title="@string/authkey" | |||
| app:password="abc"/> | |||
| </LinearLayout> | |||
| </ScrollView> | |||
| @@ -1,75 +0,0 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <androidx.coordinatorlayout.widget.CoordinatorLayout | |||
| xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:app="http://schemas.android.com/apk/res-auto" | |||
| android:id="@+id/rootLayout" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="match_parent"> | |||
| <androidx.constraintlayout.widget.ConstraintLayout | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> | |||
| <TextView | |||
| android:id="@+id/text" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_marginTop="16dp" | |||
| android:layout_marginEnd="16dp" | |||
| android:text="@string/fragment_upload_settings_title" | |||
| android:textAppearance="@style/TextAppearance.MaterialComponents.Overline" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent"/> | |||
| <lu.circl.mispbump.customViews.MaterialPreferenceSwitch | |||
| android:id="@+id/self_signed_switch" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginTop="24dp" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintHorizontal_bias="0.0" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toBottomOf="@+id/text" | |||
| app:onText="@string/settings_self_signed_on" | |||
| app:offText="@string/settings_self_signed_off" | |||
| app:title="@string/settings_self_signed_title"/> | |||
| <lu.circl.mispbump.customViews.MaterialPreferenceSwitch | |||
| android:id="@+id/push_switch" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toBottomOf="@id/self_signed_switch" | |||
| app:onText="@string/settings_push_on" | |||
| app:offText="@string/settings_push_off" | |||
| app:title="@string/settings_push_title"/> | |||
| <lu.circl.mispbump.customViews.MaterialPreferenceSwitch | |||
| android:id="@+id/pull_switch" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toBottomOf="@id/push_switch" | |||
| app:onText="@string/settings_pull_on" | |||
| app:offText="@string/settings_pull_off" | |||
| app:title="@string/settings_pull_title"/> | |||
| <lu.circl.mispbump.customViews.MaterialPreferenceSwitch | |||
| android:id="@+id/cache_switch" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toBottomOf="@id/pull_switch" | |||
| app:onText="@string/settings_cache_on" | |||
| app:offText="@string/settings_cache_off" | |||
| app:title="@string/settings_cache_title"/> | |||
| </androidx.constraintlayout.widget.ConstraintLayout> | |||
| </androidx.coordinatorlayout.widget.CoordinatorLayout> | |||
| @@ -24,25 +24,13 @@ | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent"/> | |||
| <ImageButton | |||
| android:id="@+id/copy" | |||
| android:layout_width="48dp" | |||
| android:layout_height="0dp" | |||
| android:layout_marginEnd="8dp" | |||
| android:background="?attr/selectableItemBackgroundBorderless" | |||
| android:src="@drawable/ic_file_copy" | |||
| android:tint="@color/colorIconDark" | |||
| app:layout_constraintBottom_toBottomOf="@+id/material_password" | |||
| app:layout_constraintEnd_toStartOf="@id/visibleToggle" | |||
| app:layout_constraintTop_toTopOf="parent"/> | |||
| <TextView | |||
| android:id="@+id/material_password_title" | |||
| android:layout_width="0dp" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginEnd="8dp" | |||
| android:textAppearance="@style/AppTheme.TextAppearance.ListTitle" | |||
| app:layout_constraintEnd_toStartOf="@+id/copy" | |||
| app:layout_constraintEnd_toStartOf="@+id/visibleToggle" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| tools:text="Title"/> | |||
| @@ -54,7 +42,7 @@ | |||
| android:layout_marginEnd="8dp" | |||
| android:textAppearance="@style/AppTheme.TextAppearance.ListSubtitle" | |||
| android:visibility="visible" | |||
| app:layout_constraintEnd_toStartOf="@+id/copy" | |||
| app:layout_constraintEnd_toStartOf="@+id/visibleToggle" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toBottomOf="@+id/material_password_title" | |||
| tools:ignore="TextViewEdits" | |||
| @@ -7,54 +7,65 @@ | |||
| android:id="@+id/rootLayout" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content" | |||
| android:background="@color/white" | |||
| android:foreground="?attr/selectableItemBackgroundBorderless" | |||
| android:layout_marginBottom="1dp" | |||
| android:padding="8dp"> | |||
| android:foreground="?attr/selectableItemBackgroundBorderless"> | |||
| <ImageView | |||
| android:id="@+id/syncStatus" | |||
| android:transitionName="icon" | |||
| android:transformPivotX="12dp" | |||
| android:transformPivotY="12dp" | |||
| <TextView | |||
| android:id="@+id/date_month" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_marginTop="16dp" | |||
| android:layout_margin="16dp" | |||
| android:textAppearance="@style/TextAppearance.MaterialComponents.Caption" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| android:textSize="14sp" | |||
| tools:text="Aug"/> | |||
| <TextView | |||
| android:id="@+id/date_day" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginTop="2dp" | |||
| android:layout_marginBottom="16dp" | |||
| android:tint="@color/status_red" | |||
| android:textAppearance="@style/TextAppearance.MaterialComponents.Caption" | |||
| app:layout_constraintBottom_toBottomOf="parent" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| app:srcCompat="@drawable/ic_error_outline"/> | |||
| app:layout_constraintTop_toBottomOf="@id/date_month" | |||
| app:layout_constraintStart_toStartOf="@id/date_month" | |||
| app:layout_constraintEnd_toEndOf="@id/date_month" | |||
| tools:text="16"/> | |||
| <TextView | |||
| android:id="@+id/orgName" | |||
| android:transitionName="title" | |||
| android:layout_width="0dp" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_marginStart="32dp" | |||
| android:layout_marginTop="16dp" | |||
| android:layout_marginEnd="8dp" | |||
| android:layout_marginBottom="16dp" | |||
| android:textAppearance="@style/AppTheme.TextAppearance.ListTitle" | |||
| app:layout_constraintBottom_toBottomOf="parent" | |||
| app:layout_constraintEnd_toStartOf="@+id/date" | |||
| app:layout_constraintStart_toEndOf="@+id/syncStatus" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| app:layout_constraintStart_toEndOf="@id/date_month" | |||
| tools:text="Organisation A"/> | |||
| <TextView | |||
| android:id="@+id/date" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginTop="16dp" | |||
| android:layout_marginEnd="16dp" | |||
| android:layout_marginBottom="16dp" | |||
| android:textAppearance="@style/TextAppearance.MaterialComponents.Caption" | |||
| <ImageView | |||
| android:id="@+id/syncStatus" | |||
| android:src="@drawable/ic_check_outline" | |||
| android:tint="@color/status_green" | |||
| android:layout_width="24dp" | |||
| android:layout_height="24dp" | |||
| android:layout_margin="16dp" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintBottom_toBottomOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" /> | |||
| <View | |||
| android:background="@color/black_10" | |||
| android:layout_width="0dp" | |||
| android:layout_height="1dp" | |||
| android:layout_marginStart="16dp" | |||
| app:layout_constraintStart_toEndOf="@id/date_month" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| tools:text="20.3.2019"/> | |||
| app:layout_constraintBottom_toBottomOf="parent"/> | |||
| </androidx.constraintlayout.widget.ConstraintLayout> | |||
| @@ -10,7 +10,7 @@ | |||
| android:animateLayoutChanges="true"> | |||
| <ImageView | |||
| android:id="@+id/stateView" | |||
| android:id="@+id/progressIcon" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:src="@drawable/ic_error_outline" | |||
| @@ -19,8 +19,8 @@ | |||
| app:layout_constraintTop_toTopOf="parent"/> | |||
| <ProgressBar | |||
| android:visibility="gone" | |||
| android:id="@+id/progressBar" | |||
| android:visibility="gone" | |||
| android:layout_width="24dp" | |||
| android:layout_height="24dp" | |||
| app:layout_constraintEnd_toEndOf="parent" | |||
| @@ -33,18 +33,19 @@ | |||
| android:layout_marginTop="2dp" | |||
| android:layout_marginEnd="16dp" | |||
| android:textAppearance="@style/AppTheme.TextAppearance.ListTitle" | |||
| app:layout_constraintEnd_toStartOf="@+id/stateView" | |||
| app:layout_constraintEnd_toStartOf="@+id/progressIcon" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toTopOf="parent" | |||
| tools:text="Add Organisation"/> | |||
| <TextView | |||
| android:visibility="gone" | |||
| android:id="@+id/error" | |||
| android:visibility="gone" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginTop="8dp" | |||
| android:textAppearance="@style/AppTheme.TextAppearance.ListSubtitle" | |||
| android:textColor="@color/status_red" | |||
| app:layout_constraintStart_toStartOf="parent" | |||
| app:layout_constraintTop_toBottomOf="@+id/title" | |||
| tools:text="Organisation B added successfully"/> | |||
| @@ -49,4 +49,5 @@ | |||
| <string name="preference_category_general">Allgemein</string> | |||
| <string name="preference_github_summary">Besuchen Sie das Github Projekt</string> | |||
| <string name="preference_category_information">App Informationen</string> | |||
| <string name="sync_details_activity_label">Synchronisations Details</string> | |||
| </resources> | |||
| @@ -21,8 +21,11 @@ | |||
| <attr name="password"/> | |||
| </declare-styleable> | |||
| <declare-styleable name="UploadAction"> | |||
| <attr name="description" format="string"/> | |||
| <declare-styleable name="ProgressActionView"> | |||
| <attr name="action_title" format="string"/> | |||
| <attr name="action_pending_icon" format="reference"/> | |||
| <attr name="action_done_icon" format="reference"/> | |||
| <attr name="action_error_icon" format="reference"/> | |||
| </declare-styleable> | |||
| <declare-styleable name="FixedAspectRatioFrameLayout"> | |||
| @@ -17,6 +17,10 @@ | |||
| <color name="grey_light">#F0F0F0</color> | |||
| <color name="black">#000</color> | |||
| <color name="black_70">#B3000000</color> | |||
| <color name="black_50">#80000000</color> | |||
| <color name="black_30">#4D000000</color> | |||
| <color name="black_10">#1A000000</color> | |||
| <color name="status_green">#4CAF50</color> | |||
| <color name="status_amber">#FB8C00</color> | |||
| @@ -55,4 +55,5 @@ | |||
| <string name="preference_category_general">General</string> | |||
| <string name="preference_category_information">App information</string> | |||
| <string name="preference_github_summary">Visit the Github project</string> | |||
| <string name="sync_details_activity_label">Synchronisation details</string> | |||
| </resources> | |||
| @@ -1,14 +1,14 @@ | |||
| // Top-level build file where you can add configuration options common to all sub-projects/modules. | |||
| buildscript { | |||
| ext.kotlin_version = '1.3.31' | |||
| ext.kotlin_version = '1.3.41' | |||
| repositories { | |||
| google() | |||
| jcenter() | |||
| } | |||
| dependencies { | |||
| classpath 'com.android.tools.build:gradle:3.4.2' | |||
| classpath 'com.android.tools.build:gradle:3.5.0' | |||
| classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" | |||
| // NOTE: Do not place your application dependencies here; they belong | |||
| @@ -0,0 +1 @@ | |||
| /build | |||
| @@ -0,0 +1,34 @@ | |||
| apply plugin: 'com.android.library' | |||
| android { | |||
| compileSdkVersion 29 | |||
| buildToolsVersion "29.0.1" | |||
| defaultConfig { | |||
| minSdkVersion 21 | |||
| targetSdkVersion 29 | |||
| versionCode 1 | |||
| versionName "1.0" | |||
| testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | |||
| } | |||
| buildTypes { | |||
| release { | |||
| minifyEnabled false | |||
| proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' | |||
| } | |||
| } | |||
| } | |||
| dependencies { | |||
| implementation fileTree(dir: 'libs', include: ['*.jar']) | |||
| implementation 'androidx.appcompat:appcompat:1.0.2' | |||
| testImplementation 'junit:junit:4.12' | |||
| androidTestImplementation 'androidx.test:runner:1.2.0' | |||
| androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' | |||
| } | |||
| @@ -0,0 +1,21 @@ | |||
| # Add project specific ProGuard rules here. | |||
| # You can control the set of applied configuration files using the | |||
| # proguardFiles setting in build.gradle. | |||
| # | |||
| # For more details, see | |||
| # http://developer.android.com/guide/developing/tools/proguard.html | |||
| # If your project uses WebView with JS, uncomment the following | |||
| # and specify the fully qualified class name to the JavaScript interface | |||
| # class: | |||
| #-keepclassmembers class fqcn.of.javascript.interface.for.webview { | |||
| # public *; | |||
| #} | |||
| # Uncomment this to preserve the line number information for | |||
| # debugging stack traces. | |||
| #-keepattributes SourceFile,LineNumberTable | |||
| # If you keep the line number information, uncomment this to | |||
| # hide the original source file name. | |||
| #-renamesourcefileattribute SourceFile | |||
| @@ -0,0 +1,29 @@ | |||
| package lu.circl.expandablecardview; | |||
| import android.content.Context; | |||
| import androidx.test.InstrumentationRegistry; | |||
| import androidx.test.runner.AndroidJUnit4; | |||
| import org.junit.Test; | |||
| import org.junit.runner.RunWith; | |||
| import static org.junit.Assert.assertEquals; | |||
| /** | |||
| * Instrumented test, which will execute on an Android device. | |||
| * | |||
| * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> | |||
| */ | |||
| @RunWith(AndroidJUnit4.class) | |||
| public class ExampleInstrumentedTest { | |||
| @Test | |||
| public void useAppContext() { | |||
| // Context of the app under test. | |||
| Context appContext = InstrumentationRegistry.getTargetContext(); | |||
| assertEquals("lu.circl.expandablecardview.test", appContext.getPackageName()); | |||
| } | |||
| } | |||
| @@ -0,0 +1 @@ | |||
| <manifest package="lu.circl.expandablecardview" /> | |||
| @@ -0,0 +1,177 @@ | |||
| package lu.circl.expandablecardview; | |||
| import android.content.Context; | |||
| import android.content.res.TypedArray; | |||
| import android.graphics.drawable.GradientDrawable; | |||
| import android.util.AttributeSet; | |||
| import android.view.LayoutInflater; | |||
| import android.view.View; | |||
| import android.view.ViewGroup; | |||
| import android.view.animation.Animation; | |||
| import android.view.animation.Transformation; | |||
| import android.widget.FrameLayout; | |||
| import android.widget.ImageButton; | |||
| import android.widget.ImageView; | |||
| import android.widget.LinearLayout; | |||
| import android.widget.TextView; | |||
| public class ExpandableCardView extends LinearLayout { | |||
| private Context context; | |||
| private FrameLayout contentLayout; | |||
| private int cardContentPadding; | |||
| private boolean isExpanded = true; | |||
| private int animationSpeed = 200; | |||
| public ExpandableCardView(Context context) { | |||
| this(context, null); | |||
| } | |||
| public ExpandableCardView(Context context, AttributeSet attrs) { | |||
| this(context, attrs, 0); | |||
| } | |||
| public ExpandableCardView(Context context, AttributeSet attrs, int defStyleAttr) { | |||
| super(context, attrs, defStyleAttr); | |||
| this.context = context; | |||
| setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); | |||
| setOrientation(VERTICAL); | |||
| setClipToOutline(true); | |||
| TypedArray customAttributes = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ExpandableCardView, defStyleAttr, 0); | |||
| // general | |||
| int cornerRadius = customAttributes.getDimensionPixelSize(R.styleable.ExpandableCardView_card_corner_radius, 12); | |||
| // header | |||
| String cardTitle = customAttributes.getString(R.styleable.ExpandableCardView_card_title); | |||
| int iconRes = customAttributes.getResourceId(R.styleable.ExpandableCardView_card_icon, 0x0); | |||
| int headerForegroundColor = customAttributes.getColor(R.styleable.ExpandableCardView_card_header_foreground_color, 0xFF000000); | |||
| int headerBackgroundColor = customAttributes.getColor(R.styleable.ExpandableCardView_card_header_background_color, 0xFFFFFFFF); | |||
| // content | |||
| cardContentPadding = customAttributes.getDimensionPixelSize(R.styleable.ExpandableCardView_card_content_padding, 0); | |||
| int cardContentBackgroundColor = customAttributes.getColor(R.styleable.ExpandableCardView_card_content_background_color, 0xFFFFFFFF); | |||
| customAttributes.recycle(); | |||
| GradientDrawable cardBackground = new GradientDrawable(); | |||
| cardBackground.setCornerRadius(cornerRadius); | |||
| cardBackground.setColor(cardContentBackgroundColor); | |||
| setBackground(cardBackground); | |||
| setElevation(10); | |||
| initHeader(cardTitle, iconRes, headerBackgroundColor, headerForegroundColor); | |||
| } | |||
| @Override | |||
| public void addView(View child, int index, ViewGroup.LayoutParams params) { | |||
| if (getChildCount() == 0) { | |||
| super.addView(child, index, params); // add header | |||
| } else { | |||
| if (contentLayout == null) { | |||
| contentLayout = new FrameLayout(context); | |||
| contentLayout.setLayoutParams(new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); | |||
| contentLayout.setPadding(cardContentPadding, cardContentPadding, cardContentPadding, cardContentPadding); | |||
| super.addView(contentLayout, index, new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); | |||
| } | |||
| contentLayout.addView(child); | |||
| } | |||
| } | |||
| private void initHeader(String title, int iconRes, int backgroundColor, int foregroundColor) { | |||
| View header = LayoutInflater.from(context).inflate(R.layout.expandable_card_view_header, this, true); | |||
| LinearLayout ll = header.findViewById(R.id.llRoot); | |||
| ll.setBackgroundColor(backgroundColor); | |||
| TextView titleTextView = header.findViewById(R.id.expandable_card_view_header_title); | |||
| titleTextView.setText(title); | |||
| titleTextView.setTextColor(foregroundColor); | |||
| ImageView iconView = header.findViewById(R.id.expandable_card_view_header_icon); | |||
| if (iconRes == 0x0) { | |||
| iconView.setVisibility(GONE); | |||
| } else { | |||
| iconView.setImageResource(iconRes); | |||
| iconView.setColorFilter(foregroundColor); | |||
| } | |||
| final ImageButton expandToggle = header.findViewById(R.id.expandable_card_view_header_toggle); | |||
| expandToggle.setColorFilter(foregroundColor); | |||
| expandToggle.setOnClickListener(new OnClickListener() { | |||
| @Override | |||
| public void onClick(View view) { | |||
| if (isExpanded) { | |||
| collapse(contentLayout); | |||
| expandToggle.animate().rotation(0).setDuration(animationSpeed); | |||
| } else { | |||
| expand(contentLayout); | |||
| expandToggle.animate().rotation(180).setDuration(animationSpeed); | |||
| } | |||
| isExpanded = !isExpanded; | |||
| } | |||
| }); | |||
| } | |||
| private void expand(final View v) { | |||
| int matchParentMeasureSpec = View.MeasureSpec.makeMeasureSpec(((View) v.getParent()).getWidth(), View.MeasureSpec.EXACTLY); | |||
| int wrapContentMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); | |||
| v.measure(matchParentMeasureSpec, wrapContentMeasureSpec); | |||
| final int targetHeight = v.getMeasuredHeight(); | |||
| v.getLayoutParams().height = 1; | |||
| v.setVisibility(View.VISIBLE); | |||
| Animation a = new Animation() { | |||
| @Override | |||
| protected void applyTransformation(float interpolatedTime, Transformation t) { | |||
| v.getLayoutParams().height = interpolatedTime == 1 | |||
| ? LayoutParams.WRAP_CONTENT | |||
| : (int) (targetHeight * interpolatedTime); | |||
| v.requestLayout(); | |||
| } | |||
| @Override | |||
| public boolean willChangeBounds() { | |||
| return true; | |||
| } | |||
| }; | |||
| a.setDuration(animationSpeed); | |||
| v.startAnimation(a); | |||
| } | |||
| private void collapse(final View v) { | |||
| final int initialHeight = v.getMeasuredHeight(); | |||
| Animation a = new Animation() { | |||
| @Override | |||
| protected void applyTransformation(float interpolatedTime, Transformation t) { | |||
| if (interpolatedTime == 1) { | |||
| v.setVisibility(View.GONE); | |||
| } else { | |||
| v.getLayoutParams().height = initialHeight - (int) (initialHeight * interpolatedTime); | |||
| v.requestLayout(); | |||
| } | |||
| } | |||
| @Override | |||
| public boolean willChangeBounds() { | |||
| return true; | |||
| } | |||
| }; | |||
| a.setDuration(animationSpeed); | |||
| v.startAnimation(a); | |||
| } | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:width="24dp" | |||
| android:height="24dp" | |||
| android:viewportWidth="24.0" | |||
| android:viewportHeight="24.0"> | |||
| <path | |||
| android:fillColor="#FF000000" | |||
| android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/> | |||
| </vector> | |||
| @@ -0,0 +1,9 @@ | |||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | |||
| android:width="24dp" | |||
| android:height="24dp" | |||
| android:viewportWidth="24.0" | |||
| android:viewportHeight="24.0"> | |||
| <path | |||
| android:fillColor="#FF000000" | |||
| android:pathData="M7.41,7.84L12,12.42l4.59,-4.58L18,9.25l-6,6 -6,-6z"/> | |||
| </vector> | |||
| @@ -0,0 +1,37 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <LinearLayout | |||
| xmlns:android="http://schemas.android.com/apk/res/android" | |||
| xmlns:tools="http://schemas.android.com/tools" | |||
| android:id="@+id/llRoot" | |||
| android:orientation="horizontal" | |||
| android:layout_width="match_parent" | |||
| android:layout_height="wrap_content"> | |||
| <ImageView | |||
| android:id="@+id/expandable_card_view_header_icon" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_marginTop="16dp" | |||
| android:layout_marginBottom="16dp" | |||
| tools:src="@drawable/ic_info_outline"/> | |||
| <TextView | |||
| android:id="@+id/expandable_card_view_header_title" | |||
| android:layout_width="0dp" | |||
| android:layout_height="wrap_content" | |||
| android:layout_weight="1" | |||
| android:layout_marginStart="16dp" | |||
| android:layout_gravity="center_vertical" | |||
| tools:text="Card Title"/> | |||
| <ImageButton | |||
| android:id="@+id/expandable_card_view_header_toggle" | |||
| android:layout_width="wrap_content" | |||
| android:layout_height="wrap_content" | |||
| android:layout_margin="8dp" | |||
| android:padding="8dp" | |||
| android:src="@drawable/ic_keyboard_arrow_down" | |||
| android:background="?attr/selectableItemBackgroundBorderless"/> | |||
| </LinearLayout> | |||
| @@ -0,0 +1,17 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
| <resources> | |||
| <declare-styleable name="ExpandableCardView"> | |||
| // general | |||
| <attr name="card_corner_radius" format="dimension"/> | |||
| // header | |||
| <attr name="card_title" format="string"/> | |||
| <attr name="card_icon" format="reference"/> | |||
| <attr name="card_header_background_color" format="color"/> | |||
| <attr name="card_header_foreground_color" format="color"/> | |||
| // content | |||
| <attr name="card_content_padding" format="dimension"/> | |||
| <attr name="card_content_background_color" format="color"/> | |||
| </declare-styleable> | |||
| </resources> | |||
| @@ -0,0 +1,3 @@ | |||
| <resources> | |||
| <string name="app_name">ExpandableCardView</string> | |||
| </resources> | |||
| @@ -0,0 +1,19 @@ | |||
| package lu.circl.expandablecardview; | |||
| import org.junit.Test; | |||
| import static org.junit.Assert.assertEquals; | |||
| /** | |||
| * Example local unit test, which will execute on the development machine (host). | |||
| * | |||
| * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> | |||
| */ | |||
| public class ExampleUnitTest { | |||
| @Test | |||
| public void addition_isCorrect() { | |||
| assertEquals(4, 2 + 2); | |||
| } | |||
| } | |||
| @@ -1,6 +1,6 @@ | |||
| #Wed May 08 12:00:08 CEST 2019 | |||
| #Wed Aug 21 13:50:22 CEST 2019 | |||
| distributionBase=GRADLE_USER_HOME | |||
| distributionPath=wrapper/dists | |||
| zipStoreBase=GRADLE_USER_HOME | |||
| zipStorePath=wrapper/dists | |||
| distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip | |||
| distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip | |||
| @@ -1 +1 @@ | |||
| include ':app' | |||
| include ':app', ':expandablecardview' | |||