commit c11d12932c8d171b7aebc55979fefb53157e3c42 Author: Felix Prahl-Kamps Date: Sun Jun 10 16:23:33 2018 +0200 Completely ported to IntelliJ Idea diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..39b6783 --- /dev/null +++ b/.gitignore @@ -0,0 +1,65 @@ +# Built application files +*.apk +*.ap_ + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +.idea/caches + +# Keystore files +# Uncomment the following line if you do not want to check your keystore files in. +#*.jks + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# Google Services (e.g. APIs or Firebase) +google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..c4408b4 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..c298687 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..e96534f --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Screenshots/main-screen.png b/Screenshots/main-screen.png new file mode 100755 index 0000000..9861fab Binary files /dev/null and b/Screenshots/main-screen.png differ diff --git a/Screenshots/my-org-screen.png b/Screenshots/my-org-screen.png new file mode 100755 index 0000000..7cded1f Binary files /dev/null and b/Screenshots/my-org-screen.png differ diff --git a/Screenshots/scan-screen.png b/Screenshots/scan-screen.png new file mode 100755 index 0000000..43043ab Binary files /dev/null and b/Screenshots/scan-screen.png differ diff --git a/Screenshots/share-screen.png b/Screenshots/share-screen.png new file mode 100755 index 0000000..50fa435 Binary files /dev/null and b/Screenshots/share-screen.png differ diff --git a/Screenshots/upload-screen.png b/Screenshots/upload-screen.png new file mode 100755 index 0000000..c4dadfc Binary files /dev/null and b/Screenshots/upload-screen.png differ diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..5072aa0 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,34 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 27 + buildToolsVersion "27.0.3" + defaultConfig { + applicationId "de.overview.wg.its.mispauth" + minSdkVersion 21 + targetSdkVersion 27 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:27.1.1' + implementation 'com.android.support.constraint:constraint-layout:1.1.1' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + implementation 'com.android.support:cardview-v7:27.1.1' + implementation 'com.android.support:design:27.1.1' + + implementation 'com.android.volley:volley:1.1.0' + implementation 'com.github.kenglxn.QRGen:android:2.4.0' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -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 diff --git a/app/src/androidTest/java/de/overview/wg/its/mispauth/ExampleInstrumentedTest.java b/app/src/androidTest/java/de/overview/wg/its/mispauth/ExampleInstrumentedTest.java new file mode 100644 index 0000000..4435194 --- /dev/null +++ b/app/src/androidTest/java/de/overview/wg/its/mispauth/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package de.overview.wg.its.mispauth; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("de.overview.wg.its.mispauth", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b80b394 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/de/overview/wg/its/mispauth/activity/MainActivity.java b/app/src/main/java/de/overview/wg/its/mispauth/activity/MainActivity.java new file mode 100644 index 0000000..23703c1 --- /dev/null +++ b/app/src/main/java/de/overview/wg/its/mispauth/activity/MainActivity.java @@ -0,0 +1,119 @@ +package de.overview.wg.its.mispauth.activity; + +import android.content.Intent; +import android.os.Bundle; +import android.support.design.widget.FloatingActionButton; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.DividerItemDecoration; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import de.overview.wg.its.mispauth.R; +import de.overview.wg.its.mispauth.adapter.ExtOrgAdapter; +import de.overview.wg.its.mispauth.auxiliary.PreferenceManager; +import de.overview.wg.its.mispauth.model.Organisation; + +public class MainActivity extends AppCompatActivity { + + private Organisation[] externalOrganisations; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + getExternalOrganisations(); + setUpRecyclerView(); + + FloatingActionButton fabAdd = findViewById(R.id.fab_add); + final FloatingActionButton fabSync = findViewById(R.id.fab_sync); + + fabAdd.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if(fabSync.getVisibility() == View.GONE){ + fabSync.setVisibility(View.VISIBLE); + } else { + fabSync.setVisibility(View.GONE); + } + } + }); + + fabSync.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + startSyncActivity(); + } + }); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; + } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + + if (id == R.id.menu_item_settings) { + startActivity(new Intent(this, SettingsActivity.class)); + return true; + } + + return super.onOptionsItemSelected(item); + } + + private void setUpRecyclerView() { + RecyclerView orgRecyclerView = findViewById(R.id.orgRecyclerView); + orgRecyclerView.setHasFixedSize(true); + + RecyclerView.LayoutManager orgLayoutManager = new LinearLayoutManager(this); + orgRecyclerView.setLayoutManager(orgLayoutManager); + + DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(orgRecyclerView.getContext(), 1); + orgRecyclerView.addItemDecoration(dividerItemDecoration); + + RecyclerView.Adapter orgAdapter = new ExtOrgAdapter(this, externalOrganisations); + orgRecyclerView.setAdapter(orgAdapter); + + if(externalOrganisations.length == 0){ + orgRecyclerView.setVisibility(View.GONE); + findViewById(R.id.empty_view).setVisibility(View.VISIBLE); + } else { + orgRecyclerView.setVisibility(View.VISIBLE); + findViewById(R.id.empty_view).setVisibility(View.GONE); + } + + final SwipeRefreshLayout refreshLayout = findViewById(R.id.recycler_refresh); + refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { + @Override + public void onRefresh() { + // TODO do stuff + // refreshLayout.setRefreshing(false); + } + }); + } + + private void getExternalOrganisations(){ + Organisation a = new Organisation(); + a.setName("Ferrari"); + a.setDescription("Ferrari has nothing to share"); + a.setSector("Fast cars"); + a.setNationality("Italy"); + a.setUserCount(67); + + externalOrganisations = new Organisation[] {a}; + } + + private void startSyncActivity(){ + startActivity(new Intent(this, SyncActivity.class)); + } +} diff --git a/app/src/main/java/de/overview/wg/its/mispauth/activity/SettingsActivity.java b/app/src/main/java/de/overview/wg/its/mispauth/activity/SettingsActivity.java new file mode 100644 index 0000000..b4ac54b --- /dev/null +++ b/app/src/main/java/de/overview/wg/its/mispauth/activity/SettingsActivity.java @@ -0,0 +1,211 @@ +package de.overview.wg.its.mispauth.activity; + +import android.os.Bundle; +import android.support.design.widget.Snackbar; +import android.support.design.widget.TextInputLayout; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.ProgressBar; +import android.widget.TextView; +import com.android.volley.VolleyError; +import de.overview.wg.its.mispauth.R; +import de.overview.wg.its.mispauth.auxiliary.PreferenceManager; +import de.overview.wg.its.mispauth.auxiliary.ReadableError; +import de.overview.wg.its.mispauth.model.Organisation; +import de.overview.wg.its.mispauth.model.User; +import de.overview.wg.its.mispauth.network.MispRequest; +import org.json.JSONObject; + +public class SettingsActivity extends AppCompatActivity { + + private static final String TAG = "DEBUG"; + + private PreferenceManager preferenceManager; + private ProgressBar progressBar; + private TextInputLayout serverUrlLayout, apiKeyLayout; + private EditText serverUrlText, apiKeyText; + + private Organisation org; + private User user; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_settings); + + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + getSupportActionBar().setDisplayShowHomeEnabled(true); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + serverUrlLayout = findViewById(R.id.input_layout_server_url); + apiKeyLayout = findViewById(R.id.input_layout_api_key); + serverUrlText = findViewById(R.id.edit_server_url); + apiKeyText = findViewById(R.id.edit_api_key); + progressBar = findViewById(R.id.progressBar); + + findViewById(R.id.fab_download_own_org_info).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + downloadMyOrgInfo(); + } + }); + + apiKeyText.setOnKeyListener(new View.OnKeyListener() { + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == 66) { + hideKeyboard(v); + apiKeyText.clearFocus(); + return true; + } + return false; + } + }); + + restoreSavedValues(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_settings, menu); + return true; + } + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + + if (id == R.id.menu_item_deleteData) { + serverUrlText.setText(""); + apiKeyText.setText(""); + preferenceManager.deleteAllLocalData(); + return true; + } + + return super.onOptionsItemSelected(item); + } + + private void setOrganisation(Organisation org) { + + if(org == null) { + return; + } + + TextView title = findViewById(R.id.organisation_title); + TextView uuid = findViewById(R.id.organisation_uuid); + TextView description = findViewById(R.id.organisation_description); + TextView nationality = findViewById(R.id.organisation_nationality); + TextView sector = findViewById(R.id.organisation_sector); + TextView userCount = findViewById(R.id.organisation_user_count); + + title.setText(org.getName()); + uuid.setText(org.getUuid()); + description.setText(org.getDescription()); + nationality.setText(org.getNationality()); + sector.setText(org.getSector()); + userCount.setText("" + org.getUserCount()); + } + + private void restoreSavedValues() { + preferenceManager = PreferenceManager.Instance(this); + + serverUrlText.setText(preferenceManager.getMyServerUrl()); + apiKeyText.setText(preferenceManager.getMyServerApiKey()); + + setOrganisation(preferenceManager.getMyOrganisation()); + } + + private void downloadMyOrgInfo(){ + user = new User(); + org = new Organisation(); + + boolean failed = false; + + String tmpServerUrl = serverUrlText.getText().toString(); + String tmpApiKey = apiKeyText.getText().toString(); + + if(tmpServerUrl.isEmpty()) { + serverUrlLayout.setError("Server URL is required"); + failed = true; + } + + if(tmpApiKey.isEmpty()) { + apiKeyLayout.setError("API Key is required"); + failed = true; + } + + if(failed) { + return; + } else { + serverUrlLayout.setError(null); + apiKeyLayout.setError(null); + } + + final MispRequest request = MispRequest.Instance(this); + request.setServerCredentials(tmpServerUrl, tmpApiKey); + + progressBar.setVisibility(View.VISIBLE); + + request.myUserInformation(new MispRequest.UserInformationCallback() { + + @Override + public void onResult(JSONObject myUserInformation) { + + user.fromJSON(myUserInformation); + preferenceManager.setMyUser(user); + + int orgID = user.getOrgId(); + + request.OrganisationInformation(orgID, new MispRequest.OrganisationInformationCallback() { + + @Override + public void onResult(JSONObject organisationInformation) { + progressBar.setVisibility(View.GONE); + + org.fromJSON(organisationInformation); + + preferenceManager.setMyOrganisation(org); + + setOrganisation(org); + } + + @Override + public void onError(VolleyError volleyError) { + progressBar.setVisibility(View.GONE); + MakeSnackbar(ReadableError.toReadable(volleyError)); + Log.e(TAG, "onError: " + volleyError.toString()); + } + }); + } + + @Override + public void onError(VolleyError volleyError) { + progressBar.setVisibility(View.GONE); + MakeSnackbar(ReadableError.toReadable(volleyError)); + } + }); + + // If auth was successful: save new credentials + preferenceManager.setMyServerUrl(tmpServerUrl); + preferenceManager.setMyServerApiKey(tmpApiKey); + } + + private void MakeSnackbar(String msg){ + View contextView = findViewById(R.id.coordinator); + Snackbar.make(contextView, msg, Snackbar.LENGTH_LONG).show(); + } + + private void hideKeyboard(View view) { + InputMethodManager manager = (InputMethodManager) view.getContext().getSystemService(INPUT_METHOD_SERVICE); + if (manager != null) { + manager.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + } +} diff --git a/app/src/main/java/de/overview/wg/its/mispauth/activity/SyncActivity.java b/app/src/main/java/de/overview/wg/its/mispauth/activity/SyncActivity.java new file mode 100644 index 0000000..4f7477c --- /dev/null +++ b/app/src/main/java/de/overview/wg/its/mispauth/activity/SyncActivity.java @@ -0,0 +1,14 @@ +package de.overview.wg.its.mispauth.activity; + +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import de.overview.wg.its.mispauth.R; + +public class SyncActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_sync); + } +} diff --git a/app/src/main/java/de/overview/wg/its/mispauth/adapter/ExtOrgAdapter.java b/app/src/main/java/de/overview/wg/its/mispauth/adapter/ExtOrgAdapter.java new file mode 100644 index 0000000..4c623ec --- /dev/null +++ b/app/src/main/java/de/overview/wg/its/mispauth/adapter/ExtOrgAdapter.java @@ -0,0 +1,65 @@ +package de.overview.wg.its.mispauth.adapter; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.RelativeLayout; +import android.widget.TextView; +import de.overview.wg.its.mispauth.R; +import de.overview.wg.its.mispauth.auxiliary.OrganisationDialog; +import de.overview.wg.its.mispauth.model.Organisation; + +public class ExtOrgAdapter extends RecyclerView.Adapter { + + private Context context; + private Organisation[] dataSet; + + public ExtOrgAdapter(Context context, Organisation[] dataSet) { + this.context = context; + this.dataSet = dataSet; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View extOrgView = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_holder_ext_org, parent, false); + return new ViewHolder(extOrgView); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, final int position) { + holder.orgTitle.setText(dataSet[position].getName()); + holder.subTitle.setText(dataSet[position].getDescription()); + + holder.parentLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + OrganisationDialog d = new OrganisationDialog(context); + d.createDialog(dataSet[position]); + } + }); + } + + @Override + public int getItemCount() { + return dataSet.length; + } + + public static class ViewHolder extends RecyclerView.ViewHolder{ + + RelativeLayout parentLayout; + TextView orgTitle; + TextView subTitle; + + public ViewHolder(View v) { + super(v); + parentLayout = v.findViewById(R.id.parent_layout); + orgTitle = v.findViewById(R.id.ext_org_title); + subTitle = v.findViewById(R.id.ext_org_sub_title); + } + } +} diff --git a/app/src/main/java/de/overview/wg/its/mispauth/auxiliary/OrganisationDialog.java b/app/src/main/java/de/overview/wg/its/mispauth/auxiliary/OrganisationDialog.java new file mode 100644 index 0000000..5c340eb --- /dev/null +++ b/app/src/main/java/de/overview/wg/its/mispauth/auxiliary/OrganisationDialog.java @@ -0,0 +1,45 @@ +package de.overview.wg.its.mispauth.auxiliary; + +import android.app.Activity; +import android.content.Context; +import android.support.v7.app.AlertDialog; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; +import de.overview.wg.its.mispauth.R; +import de.overview.wg.its.mispauth.model.Organisation; + +public class OrganisationDialog { + + private AlertDialog.Builder dialogBuilder; + private LayoutInflater inflater; + + public OrganisationDialog(Context context) { + dialogBuilder = new AlertDialog.Builder(context); + inflater = ((Activity)context).getLayoutInflater(); + } + + public void createDialog(Organisation org) { + + View dialogContent = inflater.inflate(R.layout.view_holder_organisation, null); + dialogBuilder.setView(dialogContent); + + TextView title = dialogContent.findViewById(R.id.organisation_title); + title.setText(org.getName()); + + TextView description = dialogContent.findViewById(R.id.organisation_description); + description.setText(org.getDescription()); + + TextView sector = dialogContent.findViewById(R.id.organisation_sector); + sector.setText(org.getSector()); + + TextView nationality = dialogContent.findViewById(R.id.organisation_nationality); + nationality.setText(org.getNationality()); + + TextView userCount = dialogContent.findViewById(R.id.organisation_user_count); + userCount.setText("" + org.getUserCount()); + + dialogBuilder.setPositiveButton("OK", null); + dialogBuilder.show(); + } +} diff --git a/app/src/main/java/de/overview/wg/its/mispauth/auxiliary/PreferenceManager.java b/app/src/main/java/de/overview/wg/its/mispauth/auxiliary/PreferenceManager.java new file mode 100644 index 0000000..9efd6b2 --- /dev/null +++ b/app/src/main/java/de/overview/wg/its/mispauth/auxiliary/PreferenceManager.java @@ -0,0 +1,95 @@ +package de.overview.wg.its.mispauth.auxiliary; + +import android.content.Context; +import android.content.SharedPreferences; +import de.overview.wg.its.mispauth.model.Organisation; +import de.overview.wg.its.mispauth.model.User; +import org.json.JSONException; +import org.json.JSONObject; + +public class PreferenceManager { + + private static PreferenceManager instance; + private SharedPreferences sharedPreferences; + + private static String PREF_KEY_SERVER_URL = "key_server_url"; + private static String PREF_KEY_SERVER_API_KEY = "key_server_api_key"; + private static String PREF_KEY_MY_ORGANISATION = "key_my_organisation"; + private static String PREF_KEY_MY_USER = "key_my_user"; + + private PreferenceManager(Context context) { + sharedPreferences = android.preference.PreferenceManager.getDefaultSharedPreferences(context); + } + + /** + * @return own Organisation if available, else null + */ + public Organisation getMyOrganisation() { + try { + JSONObject jsonObject = new JSONObject(sharedPreferences.getString(PREF_KEY_MY_ORGANISATION, "")); + Organisation org = new Organisation(); + org.fromJSON(jsonObject); + return org; + } catch (JSONException e) { + e.printStackTrace(); + } + + return null; + } + public void setMyOrganisation(Organisation org) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(PREF_KEY_MY_ORGANISATION, org.toJSON().toString()); + editor.apply(); + } + + public User getMyUser() { + try { + JSONObject jsonObject = new JSONObject(sharedPreferences.getString(PREF_KEY_MY_USER, "")); + User user = new User(); + user.fromJSON(jsonObject); + return user; + } catch (JSONException e) { + e.printStackTrace(); + } + + return null; + } + public void setMyUser(User user) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(PREF_KEY_MY_USER, user.toJSON().toString()); + editor.apply(); + } + + public String getMyServerUrl() { + return sharedPreferences.getString(PREF_KEY_SERVER_URL, ""); + } + public void setMyServerUrl(String serverUrl) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(PREF_KEY_SERVER_URL, serverUrl); + editor.apply(); + } + + public String getMyServerApiKey() { + return sharedPreferences.getString(PREF_KEY_SERVER_API_KEY, ""); + } + public void setMyServerApiKey(String apiKey) { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(PREF_KEY_SERVER_API_KEY, apiKey); + editor.apply(); + } + + public void deleteAllLocalData() { + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.clear(); + editor.apply(); + } + + public static PreferenceManager Instance(Context context) { + if(instance == null) { + instance = new PreferenceManager(context); + } + + return instance; + } +} + diff --git a/app/src/main/java/de/overview/wg/its/mispauth/auxiliary/ReadableError.java b/app/src/main/java/de/overview/wg/its/mispauth/auxiliary/ReadableError.java new file mode 100644 index 0000000..9484723 --- /dev/null +++ b/app/src/main/java/de/overview/wg/its/mispauth/auxiliary/ReadableError.java @@ -0,0 +1,19 @@ +package de.overview.wg.its.mispauth.auxiliary; + +import com.android.volley.AuthFailureError; +import com.android.volley.NoConnectionError; +import com.android.volley.VolleyError; + +public class ReadableError { + + public static String toReadable(VolleyError volleyError) { + + if(volleyError instanceof NoConnectionError) { + return "Connection failed"; + } else if(volleyError instanceof AuthFailureError) { + return "Authentication failed"; + } + + return "Unknown error"; + } +} diff --git a/app/src/main/java/de/overview/wg/its/mispauth/model/Organisation.java b/app/src/main/java/de/overview/wg/its/mispauth/model/Organisation.java new file mode 100644 index 0000000..aaa4fa5 --- /dev/null +++ b/app/src/main/java/de/overview/wg/its/mispauth/model/Organisation.java @@ -0,0 +1,186 @@ +package de.overview.wg.its.mispauth.model; + +import org.json.JSONException; +import org.json.JSONObject; + +public class Organisation { + + private static String ID_KEY = "id"; + private static String NAME_KEY = "name"; + private static String DATE_CREATED_KEY = "date_created"; + private static String DATE_MODIFIED_KEY = "date_modified"; + private static String TYPE_KEY = "type"; + private static String NATIONALITY_KEY = "nationality"; + private static String SECTOR_KEY = "sector"; + private static String CONTACTS_KEY = "contacts"; + private static String DESCRIPTION_KEY = "description"; + private static String LOCAL_KEY = "local"; + private static String UUID_KEY = "uuid"; + private static String RESTRICTED_TO_DOMAIN_KEY = "restricted_to_domain"; + private static String CREATED_BY_KEY = "created_by"; + private static String USER_COUNT_KEY = "user_count"; + + private int id; + private String name; + private String dateCreated, dateModified; + private String type; + private String nationality; + private String sector; + private String contacts; + private String description; + private boolean local; + private String uuid; + private String restrictedToDomain; + private int createdBy; + private int userCount; + + public Organisation() {} + + public void fromJSON(JSONObject org) { + + try { + id = org.getInt(ID_KEY); + dateCreated = org.getString(DATE_CREATED_KEY); + dateModified = org.getString(DATE_MODIFIED_KEY); + name = org.getString(NAME_KEY); + type = org.getString(TYPE_KEY); + nationality = org.getString(NATIONALITY_KEY); + sector = org.getString(SECTOR_KEY); + contacts = org.getString(CONTACTS_KEY); + description = org.getString(DESCRIPTION_KEY); + local = org.getBoolean(LOCAL_KEY); + uuid = org.getString(UUID_KEY); + restrictedToDomain = org.getString(RESTRICTED_TO_DOMAIN_KEY); + createdBy = org.getInt(CREATED_BY_KEY); + userCount = org.getInt(USER_COUNT_KEY); + + } catch (JSONException e) { + e.printStackTrace(); + } + } + + public JSONObject toJSON() { + JSONObject org = new JSONObject(); + + try { + org.put(ID_KEY, id); + org.put(NAME_KEY, name); + org.put(DATE_CREATED_KEY, dateCreated); + org.put(DATE_MODIFIED_KEY, dateModified); + org.put(TYPE_KEY, type); + org.put(NATIONALITY_KEY, nationality); + org.put(SECTOR_KEY, sector); + org.put(CONTACTS_KEY, contacts); + org.put(DESCRIPTION_KEY, description); + org.put(LOCAL_KEY, local); + org.put(UUID_KEY, uuid); + org.put(RESTRICTED_TO_DOMAIN_KEY, restrictedToDomain); + org.put(CREATED_BY_KEY, createdBy); + org.put(USER_COUNT_KEY, userCount); + + } catch (JSONException e) { + e.printStackTrace(); + } + + return org; + } + + + public void setName(String name) { + this.name = name; + } + public String getName(){ + return name; + } + + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } + + public String getSector() { + return sector; + } + public void setSector(String sector) { + this.sector = sector; + } + + public String getNationality() { + return nationality; + } + public void setNationality(String nationality) { + this.nationality = nationality; + } + + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + + public String getDateCreated() { + return dateCreated; + } + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateModified() { + return dateModified; + } + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } + + public String getType() { + return type; + } + public void setType(String type) { + this.type = type; + } + + public String getContacts() { + return contacts; + } + public void setContacts(String contacts) { + this.contacts = contacts; + } + + public boolean isLocal() { + return local; + } + public void setLocal(boolean local) { + this.local = local; + } + + public String getUuid() { + return uuid; + } + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getRestrictedToDomain() { + return restrictedToDomain; + } + public void setRestrictedToDomain(String restrictedToDomain) { + this.restrictedToDomain = restrictedToDomain; + } + + public int getCreatedBy() { + return createdBy; + } + public void setCreatedBy(int createdBy) { + this.createdBy = createdBy; + } + + public int getUserCount() { + return userCount; + } + public void setUserCount(int userCount) { + this.userCount = userCount; + } +} diff --git a/app/src/main/java/de/overview/wg/its/mispauth/model/User.java b/app/src/main/java/de/overview/wg/its/mispauth/model/User.java new file mode 100644 index 0000000..bb06663 --- /dev/null +++ b/app/src/main/java/de/overview/wg/its/mispauth/model/User.java @@ -0,0 +1,298 @@ +package de.overview.wg.its.mispauth.model; + +import org.json.JSONException; +import org.json.JSONObject; + +public class User { + + private static String ID_KEY = "id"; + private static String PASSWORD_KEY = "password"; + private static String ORG_ID_KEY = "org_id"; + private static String EMAIL_KEY = "email"; + private static String AUTOALERT_KEY = "autoalert"; + private static String AUTHKEY_KEY = "authkey"; + private static String INVITED_BY_KEY = "invited_by"; + private static String GPGKEY_KEY = "gpgkey"; + private static String CERTIF_PUBLIC = "certif_public"; + private static String NIDS_SID = "nids_sid"; + private static String TERMS_ACCEPTED_KEY = "termsaccepted"; + private static String NEWSREAD_KEY = "newsread"; + private static String ROLE_ID_KEY = "role_id"; + private static String CHANGE_PW_KEY = "change_pw"; + private static String CONTACT_ALERT_KEY = "contactalert"; + private static String DISABLED_KEY = "disabled"; + private static String EXPIRATION_KEY = "expiration"; + private static String CURRENT_LOGIN_KEY = "current_login"; + private static String LAST_LOGIN_KEY = "last_login"; + private static String FORCE_LOGOUT_KEY = "force_logout"; + private static String DATE_CREATED_KEY = "date_created"; + private static String DATE_MODIFIED_KEY = "date_modified"; + + private int id; + private String password; + private int orgId; + private String email; + private boolean autoAlert; + private String authkey; + private int invitedBy; + private String gpgKey; + private String certifPublic; + private int nidsSid; + private boolean termsAccepted; + private int newsRead; // Integer?? + private int roleId; + private String changePw; + private boolean contactAlert; + private boolean disabled; + private String expiration; + private String currentLogin; + private String lastLogin; + private boolean forceLogout; + private String dateCreated; + private String dateModified; + + public User() {} + + public void fromJSON(JSONObject user) { + try { + + id = user.getInt(ID_KEY); + password = user.getString(PASSWORD_KEY); + orgId = user.getInt(ORG_ID_KEY); + email = user.getString(EMAIL_KEY); + autoAlert = user.getBoolean(AUTOALERT_KEY); + authkey = user.getString(AUTHKEY_KEY); + invitedBy = user.getInt(INVITED_BY_KEY); + gpgKey = user.getString(GPGKEY_KEY); + certifPublic = user.getString(CERTIF_PUBLIC); + nidsSid = user.getInt(NIDS_SID); + termsAccepted = user.getBoolean(TERMS_ACCEPTED_KEY); + newsRead = user.getInt(NEWSREAD_KEY); + roleId = user.getInt(ROLE_ID_KEY); + changePw = user.getString(CHANGE_PW_KEY); + contactAlert = user.getBoolean(CONTACT_ALERT_KEY); + disabled = user.getBoolean(DISABLED_KEY); + expiration = user.getString(EXPIRATION_KEY); + currentLogin = user.getString(CURRENT_LOGIN_KEY); + lastLogin = user.getString(LAST_LOGIN_KEY); + forceLogout = user.getBoolean(FORCE_LOGOUT_KEY); + dateCreated = user.getString(DATE_CREATED_KEY); + dateModified = user.getString(DATE_MODIFIED_KEY); + + } catch (JSONException e) { + e.printStackTrace(); + } + + } + + public JSONObject toJSON() { + JSONObject user = new JSONObject(); + + try { + + user.put(ID_KEY, id); + user.put(PASSWORD_KEY, password); + user.put(ORG_ID_KEY, orgId); + user.put(EMAIL_KEY, email); + user.put(AUTOALERT_KEY, autoAlert); + user.put(AUTHKEY_KEY, authkey); + user.put(INVITED_BY_KEY, invitedBy); + user.put(GPGKEY_KEY, gpgKey); + user.put(CERTIF_PUBLIC, certifPublic); + user.put(NIDS_SID, nidsSid); + user.put(TERMS_ACCEPTED_KEY, termsAccepted); + user.put(NEWSREAD_KEY, newsRead); + user.put(ROLE_ID_KEY, roleId); + user.put(CHANGE_PW_KEY, changePw); + user.put(CONTACT_ALERT_KEY, contactAlert); + user.put(DISABLED_KEY, disabled); + user.put(EXPIRATION_KEY, expiration); + user.put(CURRENT_LOGIN_KEY, currentLogin); + user.put(LAST_LOGIN_KEY, lastLogin); + user.put(FORCE_LOGOUT_KEY, forceLogout); + user.put(DATE_CREATED_KEY, dateCreated); + user.put(DATE_MODIFIED_KEY, dateModified); + + } catch (JSONException e) { + e.printStackTrace(); + } + + return user; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public int getOrgId() { + return orgId; + } + + public void setOrgId(int orgId) { + this.orgId = orgId; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public boolean isAutoAlert() { + return autoAlert; + } + + public void setAutoAlert(boolean autoAlert) { + this.autoAlert = autoAlert; + } + + public String getAuthkey() { + return authkey; + } + + public void setAuthkey(String authkey) { + this.authkey = authkey; + } + + public int getInvitedBy() { + return invitedBy; + } + + public void setInvitedBy(int invitedBy) { + this.invitedBy = invitedBy; + } + + public String getGpgKey() { + return gpgKey; + } + + public void setGpgKey(String gpgKey) { + this.gpgKey = gpgKey; + } + + public String getCertifPublic() { + return certifPublic; + } + + public void setCertifPublic(String certifPublic) { + this.certifPublic = certifPublic; + } + + public int getNidsSid() { + return nidsSid; + } + + public void setNidsSid(int nidsSid) { + this.nidsSid = nidsSid; + } + + public boolean isTermsAccepted() { + return termsAccepted; + } + + public void setTermsAccepted(boolean termsAccepted) { + this.termsAccepted = termsAccepted; + } + + public int getNewsRead() { + return newsRead; + } + + public void setNewsRead(int newsRead) { + this.newsRead = newsRead; + } + + public int getRoleId() { + return roleId; + } + + public void setRoleId(int roleId) { + this.roleId = roleId; + } + + public String getChangePw() { + return changePw; + } + + public void setChangePw(String changePw) { + this.changePw = changePw; + } + + public boolean isContactAlert() { + return contactAlert; + } + + public void setContactAlert(boolean contactAlert) { + this.contactAlert = contactAlert; + } + + public boolean isDisabled() { + return disabled; + } + + public void setDisabled(boolean disabled) { + this.disabled = disabled; + } + + public String getExpiration() { + return expiration; + } + + public void setExpiration(String expiration) { + this.expiration = expiration; + } + + public String getCurrentLogin() { + return currentLogin; + } + + public void setCurrentLogin(String currentLogin) { + this.currentLogin = currentLogin; + } + + public String getLastLogin() { + return lastLogin; + } + + public void setLastLogin(String lastLogin) { + this.lastLogin = lastLogin; + } + + public boolean isForceLogout() { + return forceLogout; + } + + public void setForceLogout(boolean forceLogout) { + this.forceLogout = forceLogout; + } + + public String getDateCreated() { + return dateCreated; + } + + public void setDateCreated(String dateCreated) { + this.dateCreated = dateCreated; + } + + public String getDateModified() { + return dateModified; + } + + public void setDateModified(String dateModified) { + this.dateModified = dateModified; + } +} diff --git a/app/src/main/java/de/overview/wg/its/mispauth/network/MispRequest.java b/app/src/main/java/de/overview/wg/its/mispauth/network/MispRequest.java new file mode 100644 index 0000000..2005fcb --- /dev/null +++ b/app/src/main/java/de/overview/wg/its/mispauth/network/MispRequest.java @@ -0,0 +1,168 @@ +package de.overview.wg.its.mispauth.network; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.util.Log; +import com.android.volley.Request; +import com.android.volley.RequestQueue; +import com.android.volley.Response; +import com.android.volley.VolleyError; +import com.android.volley.toolbox.JsonObjectRequest; +import com.android.volley.toolbox.Volley; +import de.overview.wg.its.mispauth.auxiliary.PreferenceManager; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.Map; + +/** + * Simple JSON based API to communicate with MISP-Instances + */ +public class MispRequest { + + private static final String TAG = "MISP-TAG"; + private static MispRequest instance; + + private RequestQueue requestQueue; + private PreferenceManager preferenceManager; + + private String serverUrl, apiKey; + + /** + * @param context for Volley and PreferenceManager + */ + private MispRequest(Context context) { + requestQueue = Volley.newRequestQueue(context); + preferenceManager = PreferenceManager.Instance(context); + } + + /** + * @param orgId organisation ID on the MISP-Instance + * @param callback returns a single Organisation-JSON + */ + public void OrganisationInformation(int orgId, final OrganisationInformationCallback callback) { + + Response.Listener listener = new Response.Listener() { + @Override + public void onResponse(JSONObject response) { + try { + callback.onResult(response.getJSONObject("Organisation")); + return; + } catch (JSONException e) { + e.printStackTrace(); + } + + callback.onResult(response); + } + }; + + Response.ErrorListener errorListener = new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + callback.onError(error); + } + }; + + + Request r = objectRequest(Request.Method.GET, + serverUrl + "/organisations/view/"+orgId, + null, + listener, + errorListener); + + requestQueue.add(r); + } + + /** + * Typically used to get the organisation linked with this user + * @param callback return user associated with this API-Key + */ + public void myUserInformation(final UserInformationCallback callback) { + + Response.Listener listener = new Response.Listener() { + @Override + public void onResponse(JSONObject response) { + + try { + callback.onResult(response.getJSONObject("User")); + return; + } catch (JSONException e) { + e.printStackTrace(); + } + + callback.onResult(response); + } + }; + + Response.ErrorListener errorListener = new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + Log.e(TAG, "onErrorResponse: " + error.toString()); + callback.onError(error); + } + }; + + if(serverUrl.isEmpty() || apiKey.isEmpty()) { + Log.e(TAG, "myUserInformation: server or api key is empty!"); + return; + } + + Request r = objectRequest( + Request.Method.GET, + serverUrl + "/users/view/me", + null, + listener, + errorListener); + + requestQueue.add(r); + } + + + private JsonObjectRequest objectRequest(int method, + String url, + @Nullable JSONObject body, + Response.Listener listener, + Response.ErrorListener errorListener){ + + return new JsonObjectRequest(method, url, body, listener, errorListener) { + + @Override + public Map getHeaders() { + Map params = new HashMap<>(); + + params.put("Authorization", apiKey); + params.put("Accept", "application/json"); + params.put("Content-Type", "application/json; utf-8"); + + return params; + } + + }; + } + + + public void setServerCredentials(String serverUrl, String apiKey) { + this.serverUrl = serverUrl; + this.apiKey = apiKey; + } + + + public static MispRequest Instance(Context context) { + if(instance == null) { + instance = new MispRequest(context); + } + + return instance; + } + + + public interface OrganisationInformationCallback { + void onResult(JSONObject organisationInformation); + void onError(VolleyError volleyError); + } + public interface UserInformationCallback { + void onResult(JSONObject myOrganisationInformation); + void onError(VolleyError volleyError); + } +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..c7bd21d --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/background_rounded_main.xml b/app/src/main/res/drawable/background_rounded_main.xml new file mode 100644 index 0000000..f7c1e47 --- /dev/null +++ b/app/src/main/res/drawable/background_rounded_main.xml @@ -0,0 +1,13 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_add_white.xml b/app/src/main/res/drawable/ic_add_white.xml new file mode 100644 index 0000000..e3979cd --- /dev/null +++ b/app/src/main/res/drawable/ic_add_white.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_back_white.xml b/app/src/main/res/drawable/ic_arrow_back_white.xml new file mode 100644 index 0000000..71d5bbd --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_back_white.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_check.xml b/app/src/main/res/drawable/ic_check.xml new file mode 100644 index 0000000..17aca2a --- /dev/null +++ b/app/src/main/res/drawable/ic_check.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_cloud_download_white.xml b/app/src/main/res/drawable/ic_cloud_download_white.xml new file mode 100644 index 0000000..0feb270 --- /dev/null +++ b/app/src/main/res/drawable/ic_cloud_download_white.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_edit_white_24dp.xml b/app/src/main/res/drawable/ic_edit_white_24dp.xml new file mode 100644 index 0000000..46462b5 --- /dev/null +++ b/app/src/main/res/drawable/ic_edit_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_flag_white_24dp.xml b/app/src/main/res/drawable/ic_flag_white_24dp.xml new file mode 100644 index 0000000..f2e075a --- /dev/null +++ b/app/src/main/res/drawable/ic_flag_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_info_white.xml b/app/src/main/res/drawable/ic_info_white.xml new file mode 100644 index 0000000..7be0147 --- /dev/null +++ b/app/src/main/res/drawable/ic_info_white.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..d5fccc5 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_perm_contact_calendar_white_24dp.xml b/app/src/main/res/drawable/ic_perm_contact_calendar_white_24dp.xml new file mode 100644 index 0000000..b07fbed --- /dev/null +++ b/app/src/main/res/drawable/ic_perm_contact_calendar_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_qr_aim.xml b/app/src/main/res/drawable/ic_qr_aim.xml new file mode 100644 index 0000000..3a353b0 --- /dev/null +++ b/app/src/main/res/drawable/ic_qr_aim.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_sector.xml b/app/src/main/res/drawable/ic_sector.xml new file mode 100644 index 0000000..7b7f112 --- /dev/null +++ b/app/src/main/res/drawable/ic_sector.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_settings_white.xml b/app/src/main/res/drawable/ic_settings_white.xml new file mode 100644 index 0000000..79af3ab --- /dev/null +++ b/app/src/main/res/drawable/ic_settings_white.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_sync_white.xml b/app/src/main/res/drawable/ic_sync_white.xml new file mode 100644 index 0000000..7ae478f --- /dev/null +++ b/app/src/main/res/drawable/ic_sync_white.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/test_image.jpg b/app/src/main/res/drawable/test_image.jpg new file mode 100644 index 0000000..22c1196 Binary files /dev/null and b/app/src/main/res/drawable/test_image.jpg differ diff --git a/app/src/main/res/drawable/test_image_1.jpg b/app/src/main/res/drawable/test_image_1.jpg new file mode 100644 index 0000000..84f38c3 Binary files /dev/null and b/app/src/main/res/drawable/test_image_1.jpg differ diff --git a/app/src/main/res/drawable/test_image_2.jpg b/app/src/main/res/drawable/test_image_2.jpg new file mode 100755 index 0000000..7c91d8d Binary files /dev/null and b/app/src/main/res/drawable/test_image_2.jpg differ diff --git a/app/src/main/res/drawable/test_image_low_res.jpg b/app/src/main/res/drawable/test_image_low_res.jpg new file mode 100644 index 0000000..4b7e275 Binary files /dev/null and b/app/src/main/res/drawable/test_image_low_res.jpg differ diff --git a/app/src/main/res/drawable/test_image_no_meta.jpg b/app/src/main/res/drawable/test_image_no_meta.jpg new file mode 100644 index 0000000..a4e46d0 Binary files /dev/null and b/app/src/main/res/drawable/test_image_no_meta.jpg differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..c446141 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml new file mode 100644 index 0000000..aa439e7 --- /dev/null +++ b/app/src/main/res/layout/activity_settings.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_sync.xml b/app/src/main/res/layout/activity_sync.xml new file mode 100644 index 0000000..069dd4a --- /dev/null +++ b/app/src/main/res/layout/activity_sync.xml @@ -0,0 +1,270 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +