diff --git a/app/build.gradle b/app/build.gradle index 74065d0..ec09982 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,7 @@ -buildscript { +plugins { + id 'com.android.application' + id 'kotlin-android' } -apply plugin: 'com.android.application' repositories { maven { url 'https://dl.bintray.com/galtashma/maven' } @@ -31,13 +32,18 @@ android { } } +configurations { + cleanedAnnotations + implementation.exclude group: 'org.jetbrains' , module:'annotations' +} + dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'com.google.android.material:material:1.4.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.1' testImplementation 'junit:junit:4.13.1' - androidTestImplementation 'androidx.test:runner:1.4.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' implementation 'com.squareup.okhttp3:logging-interceptor:3.8.1' diff --git a/app/src/androidTest/java/com/galtashma/parsedashboard/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/galtashma/parsedashboard/ExampleInstrumentedTest.java deleted file mode 100644 index 7b7bcba..0000000 --- a/app/src/androidTest/java/com/galtashma/parsedashboard/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.galtashma.parsedashboard; - -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.*; - -/** - * 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("com.galtashma.parsedashboard", appContext.getPackageName()); - } -} diff --git a/app/src/androidTest/java/com/galtashma/parsedashboard/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/galtashma/parsedashboard/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..955ea8f --- /dev/null +++ b/app/src/androidTest/java/com/galtashma/parsedashboard/ExampleInstrumentedTest.kt @@ -0,0 +1,22 @@ +package com.galtashma.parsedashboard + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.galtashma.parsedashboard", appContext.packageName) + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7fc8700..c154b55 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,7 +18,8 @@ + android:theme="@style/AppTheme.NoActionBar.Dark" + android:exported="true"> diff --git a/app/src/main/java/com/galtashma/parsedashboard/Const.java b/app/src/main/java/com/galtashma/parsedashboard/Const.java deleted file mode 100644 index b0c0fa2..0000000 --- a/app/src/main/java/com/galtashma/parsedashboard/Const.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.galtashma.parsedashboard; - -/** - * Created by gal on 3/14/18. - */ - -public class Const { - public static final String TAG = "ParseDashboard"; - - public static final String BUNDLE_KEY_PARSE_APP_NAME = "app_name"; - public static final String BUNDLE_KEY_CLASS_NAME = "table_name"; - public static final String BUNDLE_KEY_CLASS_FIELDS_NAME = "table_fields"; - public static final String BUNDLE_KEY_OBJECT_ID = "object_id"; - -} diff --git a/app/src/main/java/com/galtashma/parsedashboard/Const.kt b/app/src/main/java/com/galtashma/parsedashboard/Const.kt new file mode 100644 index 0000000..bcd5dfc --- /dev/null +++ b/app/src/main/java/com/galtashma/parsedashboard/Const.kt @@ -0,0 +1,13 @@ +package com.galtashma.parsedashboard + +/** + * Created by gal on 3/16/18, rewritten by Cyb3rKo on 05/12/22. + */ + +object Const { + const val TAG = "ParseDashboard" + const val BUNDLE_KEY_PARSE_APP_NAME = "app_name" + const val BUNDLE_KEY_CLASS_NAME = "table_name" + const val BUNDLE_KEY_CLASS_FIELDS_NAME = "table_fields" + const val BUNDLE_KEY_OBJECT_ID = "object_id" +} diff --git a/app/src/main/java/com/galtashma/parsedashboard/Hash.java b/app/src/main/java/com/galtashma/parsedashboard/Hash.java deleted file mode 100644 index 182f7d7..0000000 --- a/app/src/main/java/com/galtashma/parsedashboard/Hash.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.galtashma.parsedashboard; - -import android.util.Log; - -import java.security.MessageDigest; - -public class Hash { - private static String salt = "1m4bqk"; - public static String sha1(String value) { - try { - MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); - messageDigest.update((value + salt).getBytes("UTF-8")); - byte[] bytes = messageDigest.digest(); - StringBuilder buffer = new StringBuilder(); - for (byte b : bytes) { - buffer.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1)); - } - String result = buffer.toString(); - Log.d(Const.TAG, "Hash result " + result); - - return result; - } catch (Exception ignored) { - ignored.printStackTrace(); - return null; - } - } -} diff --git a/app/src/main/java/com/galtashma/parsedashboard/Hash.kt b/app/src/main/java/com/galtashma/parsedashboard/Hash.kt new file mode 100644 index 0000000..e35ad14 --- /dev/null +++ b/app/src/main/java/com/galtashma/parsedashboard/Hash.kt @@ -0,0 +1,32 @@ +package com.galtashma.parsedashboard + +import android.util.Log +import java.security.MessageDigest +import kotlin.experimental.and + +/** + * Created by gal on 3/16/18, rewritten by Cyb3rKo on 05/12/22. + */ + +object Hash { + private const val salt = "1m4bqk" + + fun sha1(value: String): String? { + try { + val messageDigest = MessageDigest.getInstance("SHA-1") + messageDigest.update((value + salt).toByteArray(Charsets.UTF_8)) + val bytes = messageDigest.digest() + val buffer = StringBuilder() + bytes.forEach { + buffer.append(((it and 0xff.toByte()) + 0x100).toString(16).drop(1)) + } + val result = buffer.toString() + Log.d(Const.TAG, "Hash result $result") + + return result + } catch (ignored: Exception) { + ignored.printStackTrace() + return null + } + } +} diff --git a/app/src/main/java/com/galtashma/parsedashboard/ListPreferenceStore.java b/app/src/main/java/com/galtashma/parsedashboard/ListPreferenceStore.java deleted file mode 100644 index 8a76620..0000000 --- a/app/src/main/java/com/galtashma/parsedashboard/ListPreferenceStore.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.galtashma.parsedashboard; - -import com.appizona.yehiahd.fastsave.FastSave; - -import java.util.ArrayList; -import java.util.List; - -public class ListPreferenceStore { - private List list; - private String prefId; - - public ListPreferenceStore(String prefId) { - this.prefId = prefId; - list = load(); - } - - public List getList() { - return list; - } - - public void add(String key) { - if (!exists(key)) { - list.add(key); - save(); - } - } - - public void remove(String key) { - if (list.contains(key)) { - list.remove(key); - } - save(); - } - - public void reset() { - list = new ArrayList<>(); - save(); - } - - public boolean exists(String key) { - return list.contains(key); - } - - public boolean isEmpty() { - return list.isEmpty(); - } - - public int size() { - return list.size(); - } - - private void save() { - FastSave.getInstance().saveObjectsList(prefId, list); - } - - private List load() { - List l = FastSave.getInstance().getObjectsList(prefId, String.class); - if (l != null) { - return l; - } - return new ArrayList<>(); - } -} diff --git a/app/src/main/java/com/galtashma/parsedashboard/ListPreferenceStore.kt b/app/src/main/java/com/galtashma/parsedashboard/ListPreferenceStore.kt new file mode 100644 index 0000000..52b2867 --- /dev/null +++ b/app/src/main/java/com/galtashma/parsedashboard/ListPreferenceStore.kt @@ -0,0 +1,48 @@ +package com.galtashma.parsedashboard + +import com.appizona.yehiahd.fastsave.FastSave + +/** + * Created by gal on 3/16/18, rewritten by Cyb3rKo on 05/12/22. + */ + +class ListPreferenceStore(private val prefId: String) { + var list = load() + + fun add(key: String) { + if (!exists(key)) { + list.add(key) + save() + } + } + + fun remove(key: String) { + if (list.contains(key)) { + list.remove(key) + } + save() + } + + fun reset() { + list = mutableListOf() + save() + } + + fun exists(key: String) = list.contains(key) + + fun isEmpty() = list.isEmpty() + + fun size() = list.size + + private fun save() { + FastSave.getInstance().saveObjectsList(prefId, list) + } + + private fun load(): MutableList { + val l = FastSave.getInstance().getObjectsList(prefId, String::class.java) + if (l != null) { + return l + } + return mutableListOf() + } +} diff --git a/app/src/main/java/com/galtashma/parsedashboard/ParseDashboardApplication.java b/app/src/main/java/com/galtashma/parsedashboard/ParseDashboardApplication.java deleted file mode 100644 index adc257f..0000000 --- a/app/src/main/java/com/galtashma/parsedashboard/ParseDashboardApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.galtashma.parsedashboard; - -import android.app.Application; - -import com.appizona.yehiahd.fastsave.FastSave; - -public class ParseDashboardApplication extends Application { - @Override - public void onCreate() { - super.onCreate(); - FastSave.init(getApplicationContext()); - } -} diff --git a/app/src/main/java/com/galtashma/parsedashboard/ParseDashboardApplication.kt b/app/src/main/java/com/galtashma/parsedashboard/ParseDashboardApplication.kt new file mode 100644 index 0000000..a2c44d0 --- /dev/null +++ b/app/src/main/java/com/galtashma/parsedashboard/ParseDashboardApplication.kt @@ -0,0 +1,15 @@ +package com.galtashma.parsedashboard + +import android.app.Application +import com.appizona.yehiahd.fastsave.FastSave + +/** + * Created by gal on 3/16/18, rewritten by Cyb3rKo on 05/12/22. + */ + +class ParseDashboardApplication : Application() { + override fun onCreate() { + super.onCreate() + FastSave.init(applicationContext) + } +} diff --git a/app/src/main/java/com/galtashma/parsedashboard/ParseField.java b/app/src/main/java/com/galtashma/parsedashboard/ParseField.java deleted file mode 100644 index 01f16a3..0000000 --- a/app/src/main/java/com/galtashma/parsedashboard/ParseField.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.galtashma.parsedashboard; - -/** - * Created by gal on 3/16/18. - */ - -public class ParseField { - - public String value; - public String key; - - public ParseField(String key, String value) { - this.key = key; - this.value = value; - } -} diff --git a/app/src/main/java/com/galtashma/parsedashboard/ParseField.kt b/app/src/main/java/com/galtashma/parsedashboard/ParseField.kt new file mode 100644 index 0000000..e8170b7 --- /dev/null +++ b/app/src/main/java/com/galtashma/parsedashboard/ParseField.kt @@ -0,0 +1,7 @@ +package com.galtashma.parsedashboard + +/** + * Created by gal on 3/16/18, rewritten by Cyb3rKo on 05/12/22. + */ + +class ParseField(val key: String, val value: String) diff --git a/app/src/main/java/com/galtashma/parsedashboard/ParseServerConfig.java b/app/src/main/java/com/galtashma/parsedashboard/ParseServerConfig.java deleted file mode 100644 index 9e7a936..0000000 --- a/app/src/main/java/com/galtashma/parsedashboard/ParseServerConfig.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.galtashma.parsedashboard; - -/** - * Created by gal on 3/16/18. - */ - -public class ParseServerConfig { - - public String appName; - public String appId; - public String masterKey; - public String serverUrl; - - public ParseServerConfig(){} - - public ParseServerConfig(String appName, String appId, String masterKey, String appUrl) { - this.appName = appName; - this.appId = appId; - this.masterKey = masterKey; - this.serverUrl = appUrl; - } -} diff --git a/app/src/main/java/com/galtashma/parsedashboard/ParseServerConfig.kt b/app/src/main/java/com/galtashma/parsedashboard/ParseServerConfig.kt new file mode 100644 index 0000000..8731525 --- /dev/null +++ b/app/src/main/java/com/galtashma/parsedashboard/ParseServerConfig.kt @@ -0,0 +1,12 @@ +package com.galtashma.parsedashboard + +/** + * Created by gal on 3/16/18, rewritten by Cyb3rKo on 05/12/22. + */ + +class ParseServerConfig( + val appName: String = "", + val appId: String = "", + val masterKey: String = "", + val serverUrl: String = "" +) diff --git a/app/src/main/java/com/galtashma/parsedashboard/ParseServerConfigStorage.java b/app/src/main/java/com/galtashma/parsedashboard/ParseServerConfigStorage.java deleted file mode 100644 index eb788bc..0000000 --- a/app/src/main/java/com/galtashma/parsedashboard/ParseServerConfigStorage.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.galtashma.parsedashboard; - -import android.content.Context; -import android.content.SharedPreferences; -import com.afollestad.ason.AsonArray; - -import java.util.List; - -/** - * Created by gal on 3/16/18. - */ - -public class ParseServerConfigStorage { - - private static final String PREF_KEY = "parse_server_key"; - private static final String PREF_SERVERS_KEY = "parse_server_config_key"; - - private final Context context; - - public ParseServerConfigStorage(Context context) { - this.context = context; - } - - public void saveServer(ParseServerConfig config) { - AsonArray servers = getServersAson(); - servers.add(config); - overrideServersAson(servers); - } - - public void deleteServer(String appId) { - List servers = getServers(); - ParseServerConfig toRemove = null; - - for (ParseServerConfig server : servers) { - if (server.appId.equals(appId)) { - toRemove = server; - } - } - - if (toRemove != null ) { - servers.remove(toRemove); - overrideServers(servers); - } - } - - public List getServers() { - AsonArray servers = getServersAson(); - return servers.deserializeList(ParseServerConfig.class); - } - - private AsonArray getServersAson() { - SharedPreferences pref = context.getSharedPreferences(PREF_KEY, Context.MODE_PRIVATE); - String input = pref.getString(PREF_SERVERS_KEY, "[]"); - return new AsonArray<>(input); - } - - private void overrideServers(List servers) { - AsonArray asonArray = new AsonArray<>(); - for (ParseServerConfig s : servers) { - asonArray.add(s); - } - - overrideServersAson(asonArray); - } - - private void overrideServersAson(AsonArray servers) { - SharedPreferences pref = context.getSharedPreferences(PREF_KEY, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = pref.edit(); - editor.putString(PREF_SERVERS_KEY, servers.toString()); - editor.commit(); - } -} diff --git a/app/src/main/java/com/galtashma/parsedashboard/ParseServerConfigStorage.kt b/app/src/main/java/com/galtashma/parsedashboard/ParseServerConfigStorage.kt new file mode 100644 index 0000000..ddf7b95 --- /dev/null +++ b/app/src/main/java/com/galtashma/parsedashboard/ParseServerConfigStorage.kt @@ -0,0 +1,62 @@ +package com.galtashma.parsedashboard + +import android.content.Context +import com.afollestad.ason.AsonArray + +/** + * Created by gal on 3/16/18, rewritten by Cyb3rKo on 05/12/22. + */ + +private const val PREF_KEY = "parse_server_key" +private const val PREF_SERVERS_KEY = "parse_server_config_key" + +class ParseServerConfigStorage(val context: Context) { + fun saveServer(config: ParseServerConfig) { + val servers = getServersAson() + servers.add(config) + overrideServersAson(servers) + } + + fun deleteServer(appId: String) { + val servers = getServers() + var toRemove: ParseServerConfig? = null + + servers.forEach { + if (it.appId == appId) { + toRemove = it + } + } + + if (toRemove != null) { + servers.remove(toRemove) + overrideServers(servers) + } + } + + fun getServers(): MutableList { + val servers = getServersAson() + return servers.deserializeList(ParseServerConfig::class.java) + } + + private fun getServersAson(): AsonArray { + val pref = context.getSharedPreferences(PREF_KEY, Context.MODE_PRIVATE) + val input = pref.getString(PREF_SERVERS_KEY, "[]") + return AsonArray(input) + } + + private fun overrideServers(servers: List) { + val asonArray = AsonArray() + servers.forEach { + asonArray.add(it) + } + + overrideServersAson(asonArray) + } + + private fun overrideServersAson(servers: AsonArray) { + val pref = context.getSharedPreferences(PREF_KEY, Context.MODE_PRIVATE) + val editor = pref.edit() + editor.putString(PREF_SERVERS_KEY, servers.toString()) + editor.apply() + } +} diff --git a/app/src/main/java/com/galtashma/parsedashboard/SortPreferenceStore.java b/app/src/main/java/com/galtashma/parsedashboard/SortPreferenceStore.java deleted file mode 100644 index 74a9d9c..0000000 --- a/app/src/main/java/com/galtashma/parsedashboard/SortPreferenceStore.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.galtashma.parsedashboard; - -import com.appizona.yehiahd.fastsave.FastSave; - -public class SortPreferenceStore { - private String prefId; - - public SortPreferenceStore(String prefId) { - this.prefId = prefId; - } - - public void update(String key, boolean asc) { - FastSave.getInstance().saveObject(this.prefId, new SortPreferenceItem(key, asc)); - } - - public String getKey() { - if (isEmpty()) { - return ""; - } - return getSavedItem().key; - } - - public boolean isAsc() { - if (isEmpty()) { - return false; - } - return getSavedItem().asc; - } - - public boolean isEmpty() { - return !FastSave.getInstance().isKeyExists(this.prefId); - } - - private SortPreferenceItem getSavedItem() { - return FastSave.getInstance().getObject(this.prefId, SortPreferenceItem.class); - } - - class SortPreferenceItem { - private String key; - private boolean asc; - - SortPreferenceItem(String key, boolean asc) { - this.key = key; - this.asc = asc; - } - } -} diff --git a/app/src/main/java/com/galtashma/parsedashboard/SortPreferenceStore.kt b/app/src/main/java/com/galtashma/parsedashboard/SortPreferenceStore.kt new file mode 100644 index 0000000..9e9e47c --- /dev/null +++ b/app/src/main/java/com/galtashma/parsedashboard/SortPreferenceStore.kt @@ -0,0 +1,31 @@ +package com.galtashma.parsedashboard + +import com.appizona.yehiahd.fastsave.FastSave + +/** + * Created by gal on 3/16/18, rewritten by Cyb3rKo on 05/12/22. + */ + +class SortPreferenceStore(private val prefId: String) { + fun update(key: String, asc: Boolean) { + FastSave.getInstance().saveObject(prefId, SortPreferenceItem(key, asc)) + } + + fun getKey() = if (!isEmpty()) getSavedItem().key else "" + + fun isAsc(): Boolean { + return if (isEmpty()) { + false + } else { + getSavedItem().asc + } + } + + fun isEmpty() = !FastSave.getInstance().isKeyExists(this.prefId) + + private fun getSavedItem(): SortPreferenceItem { + return FastSave.getInstance().getObject(this.prefId, SortPreferenceItem::class.java) + } + + class SortPreferenceItem(val key: String, val asc: Boolean) +} diff --git a/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseAppsAdapter.java b/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseAppsAdapter.java deleted file mode 100644 index e228352..0000000 --- a/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseAppsAdapter.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.galtashma.parsedashboard.adapters; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import androidx.annotation.NonNull; - -import com.galtashma.parsedashboard.ParseServerConfig; -import com.galtashma.parsedashboard.R; -import com.lucasurbas.listitemview.ListItemView; - -import java.util.List; - -/** - * Created by gal on 3/16/18. - */ - -public class ParseAppsAdapter extends ArrayAdapter { - - public interface ParseAppAdapterListener{ - void onClickOpen(ParseServerConfig config); - void onClickEdit(ParseServerConfig config); - void onClickDelete(ParseServerConfig config); - } - - private ParseAppAdapterListener listener; - - public ParseAppsAdapter(@NonNull Context context, @NonNull List objects) { - super(context, R.layout.list_item_card, objects); - } - - @NonNull - public View getView(int position, View convertView, @NonNull ViewGroup parent) { - final ParseServerConfig server = getItem(position); - - if(convertView == null) { - convertView = LayoutInflater.from(this.getContext()).inflate(R.layout.list_item_card, parent, false); - } - - ListItemView item = convertView.findViewById(R.id.parse_server_list_item); - item.setTitle(server.appName); - item.setSubtitle(server.serverUrl+"\n"+server.appId); - - item.setOnMenuItemClickListener(clickedItem -> { - if (clickedItem.getItemId() == R.id.action_edit) { - notifyEdit(server); - } else if (clickedItem.getItemId() == R.id.action_remove) { - notifyDelete(server); - } - }); - item.setOnClickListener(view -> notifyClick(server)); - - return convertView; - } - - private void notifyClick(ParseServerConfig config){ - if (listener!=null){ - listener.onClickOpen(config); - } - } - - private void notifyEdit(ParseServerConfig config){ - if (listener != null){ - listener.onClickEdit(config); - } - } - - private void notifyDelete(ParseServerConfig config){ - if (listener != null){ - this.listener.onClickDelete(config); - } - } - - public void setListener(ParseAppAdapterListener listener) { - this.listener = listener; - } -} diff --git a/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseAppsAdapter.kt b/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseAppsAdapter.kt new file mode 100644 index 0000000..9636730 --- /dev/null +++ b/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseAppsAdapter.kt @@ -0,0 +1,76 @@ +package com.galtashma.parsedashboard.adapters + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import androidx.annotation.NonNull +import com.galtashma.parsedashboard.ParseServerConfig +import com.galtashma.parsedashboard.R +import com.lucasurbas.listitemview.ListItemView + +/** + * Created by gal on 3/16/18, rewritten by Cyb3rKo on 05/12/22. + */ + +class ParseAppsAdapter( + @NonNull context: Context, + @NonNull objects: List +) : ArrayAdapter(context, R.layout.list_item_card, objects) { + + interface ParseAppAdapterListener { + fun onClickOpen(config: ParseServerConfig) + fun onClickEdit(config: ParseServerConfig) + fun onClickDelete(config: ParseServerConfig) + } + + private lateinit var listener: ParseAppAdapterListener + + override fun getView( + position: Int, + convertView: View?, + parent: ViewGroup + ): View { + val server = getItem(position) + + var finalView = convertView + if (convertView == null) { + finalView = LayoutInflater.from(context) + .inflate(R.layout.list_item_card, parent, false) + } + + val item = finalView?.findViewById(R.id.parse_server_list_item) + item?.title = server?.appName + item?.subtitle = "${server?.serverUrl}\n${server?.appId}" + + if (server != null) { + item?.setOnMenuItemClickListener { clickedItem -> + if (clickedItem.itemId == R.id.action_edit) { + notifyEdit(server) + } else if (clickedItem.itemId == R.id.action_remove) { + notifyDelete(server) + } + } + item?.setOnClickListener { notifyClick(server) } + } + + return finalView!! + } + + private fun notifyClick(config: ParseServerConfig) { + listener.onClickOpen(config) + } + + private fun notifyEdit(config: ParseServerConfig) { + listener.onClickEdit(config) + } + + private fun notifyDelete(config: ParseServerConfig) { + listener.onClickDelete(config) + } + + fun setListener(listener: ParseAppAdapterListener) { + this.listener = listener + } +} diff --git a/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseClassesAdapter.java b/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseClassesAdapter.java deleted file mode 100644 index 4f10040..0000000 --- a/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseClassesAdapter.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.galtashma.parsedashboard.adapters; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import androidx.annotation.NonNull; - -import com.galtashma.parsedashboard.R; -import com.lucasurbas.listitemview.ListItemView; -import com.parse.ParseSchema; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - - -public class ParseClassesAdapter extends ArrayAdapter { - - public interface OnClickListener { - void onSchemaClicked(ParseSchema schema); - } - - private OnClickListener listener; - - public ParseClassesAdapter(@NonNull Context context) { - super(context, R.layout.list_item); - } - - public ParseClassesAdapter(@NonNull Context context, @NonNull List objects) { - super(context, R.layout.list_item, objects); - } - - @NonNull - public View getView(int position, View convertView, @NonNull ViewGroup parent) { - final ParseSchema schema = getItem(position); - if (convertView == null) { - convertView = LayoutInflater.from(this.getContext()).inflate(R.layout.list_item, parent, false); - } - - ListItemView item = (ListItemView) convertView; - - item.setTitle(schema.getName()); - item.setSubtitle(getItemCountString(schema) + getSchemaString(schema)); - - item.setOnClickListener(view -> { - if (listener != null){ - listener.onSchemaClicked(schema); - } - }); - - return convertView; - } - - private String getItemCountString(ParseSchema schema) { - int itemCount = schema.getCountIfFetched(); - if (itemCount != -1) { - return "("+itemCount+")\t"; - } - - schema.setOnCountListener((count, exception) -> notifyDataSetChanged()); - - return ""; - } - - private String getSchemaString(ParseSchema schema){ - List list = new ArrayList<>(schema.getFields().keySet()); - Collections.sort(list); - return list.toString().replaceAll("\\[|\\]","").replaceAll(","," "); - } - - public void setListener(OnClickListener listener) { - this.listener = listener; - } -} diff --git a/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseClassesAdapter.kt b/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseClassesAdapter.kt new file mode 100644 index 0000000..cb5f22c --- /dev/null +++ b/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseClassesAdapter.kt @@ -0,0 +1,73 @@ +package com.galtashma.parsedashboard.adapters + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import androidx.annotation.NonNull +import com.galtashma.parsedashboard.R +import com.lucasurbas.listitemview.ListItemView +import com.parse.ParseSchema + +/** + * Created by gal on 3/16/18, rewritten by Cyb3rKo on 05/12/22. + */ + +class ParseClassesAdapter( + @NonNull context: Context +) : ArrayAdapter(context, R.layout.list_item) { + + interface OnClickListener { + fun onSchemaCLicked(schema: ParseSchema) + } + + private lateinit var localListener: OnClickListener + + override fun getView( + position: Int, + convertView: View?, + parent: ViewGroup + ): View { + var finalView = convertView + val schema = getItem(position) + if (convertView == null) { + finalView = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false) + } + + val item = finalView as ListItemView + + item.title = schema?.name + if (schema != null) { + item.subtitle = getItemCountString(schema) + getSchemaString(schema) + } + + item.setOnClickListener { + if (schema != null) { + localListener.onSchemaCLicked(schema) + } + } + + return finalView + } + + private fun getItemCountString(schema: ParseSchema): String { + val itemCount = schema.countIfFetched + if (itemCount != -1) { + return "($itemCount)\t" + } + + schema.setOnCountListener { _, _ -> notifyDataSetChanged() } + + return "" + } + + private fun getSchemaString(schema: ParseSchema): String { + val list = schema.fields.keys.toList().sorted() + return list.toString().replace("\\[|\\]", "").replace(",", " ") + } + + fun setListener(listener: OnClickListener) { + this.localListener = listener + } +} diff --git a/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseObjectFieldsAdapter.java b/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseObjectFieldsAdapter.java deleted file mode 100644 index 2d7693d..0000000 --- a/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseObjectFieldsAdapter.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.galtashma.parsedashboard.adapters; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import androidx.annotation.NonNull; - -import com.galtashma.parsedashboard.ParseField; -import com.galtashma.parsedashboard.R; -import com.lucasurbas.listitemview.ListItemView; - -import java.util.List; - -/** - * Created by gal on 3/16/18. - */ - -public class ParseObjectFieldsAdapter extends ArrayAdapter { - - private View.OnLongClickListener longClickListener = null; - - public ParseObjectFieldsAdapter(@NonNull Context context, @NonNull List objects) { - super(context, R.layout.list_item, objects); - } - - @NonNull - public View getView(int position, View convertView, @NonNull ViewGroup parent) { - ParseField details = getItem(position); - if (convertView == null) { - convertView = LayoutInflater.from(this.getContext()).inflate(R.layout.list_item, parent, false); - } - - ListItemView view = (ListItemView) convertView; - - if (details == null) { - return convertView; - } - - if (details.value == null || details.value.isEmpty()) { - view.setTitle(""); - } else { - view.setTitle(details.value); - } - - view.setSubtitle(details.key); - view.setOnLongClickListener(longClickListener); - - return convertView; - } - - public void setLongClickListener(View.OnLongClickListener longClickListener) { - this.longClickListener = longClickListener; - } -} diff --git a/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseObjectFieldsAdapter.kt b/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseObjectFieldsAdapter.kt new file mode 100644 index 0000000..361418a --- /dev/null +++ b/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseObjectFieldsAdapter.kt @@ -0,0 +1,53 @@ +package com.galtashma.parsedashboard.adapters + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import androidx.annotation.NonNull +import com.galtashma.parsedashboard.ParseField +import com.galtashma.parsedashboard.R +import com.lucasurbas.listitemview.ListItemView + +/** + * Created by gal on 3/16/18, rewritten by Cyb3rKo on 05/12/22. + */ + +class ParseObjectFieldsAdapter( + @NonNull context: Context, + @NonNull objects: List +) : ArrayAdapter(context, R.layout.list_item, objects) { + + private var longClickListener: View.OnLongClickListener? = null + + override fun getView( + position: Int, + convertView: View?, + parent: ViewGroup + ): View { + val details = getItem(position) + + var finalView = convertView + if (convertView == null) { + finalView = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false) + } + + val view = finalView as ListItemView + + if (details == null) { + return finalView + } + + view.title = details.value.ifBlank { "" } + + view.subtitle = details.key + view.setOnLongClickListener(longClickListener) + + return finalView + } + + fun setLongClickListener(longClickListener: View.OnLongClickListener) { + this.longClickListener = longClickListener + } +} diff --git a/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseObjectsAdapter.java b/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseObjectsAdapter.java deleted file mode 100644 index 613c4b4..0000000 --- a/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseObjectsAdapter.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.galtashma.parsedashboard.adapters; - -import android.content.Context; -import android.view.View; -import android.view.ViewGroup; -import androidx.annotation.NonNull; - -import com.galtashma.lazyparse.LazyList; -import com.galtashma.lazyparse.LazyParseObjectHolder; -import com.galtashma.lazyparse.ScrollInfiniteAdapter; -import com.galtashma.parsedashboard.R; -import com.lucasurbas.listitemview.ListItemView; -import com.parse.ParseObject; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Created by gal on 3/14/18. - */ - -public class ParseObjectsAdapter extends ScrollInfiniteAdapter { - - private List previewFieldNames; - - public ParseObjectsAdapter(Context context, LazyList lazyValues) { - super(context, lazyValues, R.layout.list_item, 15); - previewFieldNames = Arrays.asList("createdAt", "updatedAt"); - } - - public ParseObjectsAdapter(Context context, LazyList lazyValues, List previewFields) { - super(context, lazyValues, R.layout.list_item, 15); - this.previewFieldNames = previewFields; - } - - public void updatePreviewFields(List previewFields){ - this.previewFieldNames = previewFields; - this.notifyDataSetChanged(); - } - - @Override - public View renderReadyLazyObject(ParseObject t, View view, @NonNull ViewGroup viewGroup) { - ListItemView item = (ListItemView) view; - item.setTitle(t.getObjectId()); - item.setMultiline(true); - - Map fields = new HashMap<>(); - for (String fieldName : previewFieldNames) { - String value = formatParseField(t, fieldName); - if (value != null) { - fields.put(fieldName, formatParseField(t, fieldName)); - } - } - item.setSubtitle(mapToString(fields)); - return view; - } - - private String formatParseField(ParseObject t, String key) { - if (key.equals("createdAt")) { - return t.getCreatedAt().toString(); - } - if (key.equals("updatedAt")) { - return t.getCreatedAt().toString(); - } - - if (t.has(key)) { - return t.get(key).toString(); - } - - return null; - } - - @Override - public View renderLoadingLazyObject(LazyParseObjectHolder lazyParseObjectHolder, View view, @NonNull ViewGroup viewGroup) { - ListItemView item = (ListItemView) view; - item.setTitle("Loading..."); - return view; - } - - private String mapToString(Map map) { - if (map.size() <= 0) { - return ""; - } - - StringBuilder sb = new StringBuilder(); - for (String k : map.keySet()) { - sb.append(k); - sb.append(": "); - sb.append(map.get(k)); - sb.append("\n"); - } - - sb.delete(sb.length()-1, sb.length()); // remove last \n - - return sb.toString(); - } -} diff --git a/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseObjectsAdapter.kt b/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseObjectsAdapter.kt new file mode 100644 index 0000000..360f34b --- /dev/null +++ b/app/src/main/java/com/galtashma/parsedashboard/adapters/ParseObjectsAdapter.kt @@ -0,0 +1,86 @@ +package com.galtashma.parsedashboard.adapters + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import androidx.annotation.NonNull +import com.galtashma.lazyparse.LazyList +import com.galtashma.lazyparse.LazyParseObjectHolder +import com.galtashma.lazyparse.ScrollInfiniteAdapter +import com.galtashma.parsedashboard.R +import com.lucasurbas.listitemview.ListItemView +import com.parse.ParseObject + +/** + * Created by gal on 3/16/18, rewritten by Cyb3rKo on 05/12/22. + */ + +class ParseObjectsAdapter( + context: Context, + lazyValues: LazyList, + private var previewFieldNames: List +) : ScrollInfiniteAdapter(context, lazyValues, R.layout.list_item, 15) { + + fun updatePreviewFields(previewFields: List){ + previewFieldNames = previewFields + this.notifyDataSetChanged() + } + + override fun renderReadyLazyObject( + parseObject: ParseObject, + view: View?, + @NonNull viewGroup: ViewGroup + ): View { + val item = view as ListItemView + item.title = parseObject.objectId + item.setMultiline(true) + + val fields = HashMap() + for (fieldName in previewFieldNames) { + val value = formatParseField(parseObject, fieldName) + if (value != null) { + fields[fieldName] = formatParseField(parseObject, fieldName) + } + } + item.subtitle = mapToString(fields) + return view + } + + private fun formatParseField(parseObject: ParseObject, key: String): String? { + if (key == "createdAt") { + return parseObject.createdAt.toString() + } + if (key == "updatedAt") { + return parseObject.createdAt.toString() + } + + if (parseObject.has(key)) { + return parseObject.get(key).toString() + } + + return null + } + + override fun renderLoadingLazyObject( + lazyParseObjectHolder: LazyParseObjectHolder?, + view: View?, + viewGroup: ViewGroup + ): View { + val item = view as ListItemView + item.title = "Loading..." + return view + } + + private fun mapToString(map: Map): String { + if (map.isEmpty()) return "" + + val sb = StringBuilder() + for (k in map.keys) { + sb.append("$k: ${map[k]}\n") + } + + sb.dropLast(1) // remove last \n + + return sb.toString() + } +} diff --git a/app/src/main/java/com/galtashma/parsedashboard/screens/AppsMenuParseActivity.java b/app/src/main/java/com/galtashma/parsedashboard/screens/AppsMenuParseActivity.java deleted file mode 100644 index 5d08bfa..0000000 --- a/app/src/main/java/com/galtashma/parsedashboard/screens/AppsMenuParseActivity.java +++ /dev/null @@ -1,193 +0,0 @@ -package com.galtashma.parsedashboard.screens; - -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.util.Log; -import android.view.View; -import android.widget.EditText; -import android.widget.ListView; -import androidx.annotation.NonNull; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; - -import com.afollestad.materialdialogs.DialogAction; -import com.afollestad.materialdialogs.MaterialDialog; -import com.galtashma.parsedashboard.ParseServerConfig; -import com.galtashma.parsedashboard.ParseServerConfigStorage; -import com.galtashma.parsedashboard.R; -import com.galtashma.parsedashboard.adapters.ParseAppsAdapter; -import com.galtashma.parsedashboard.Const; -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.google.android.material.snackbar.Snackbar; -import com.parse.Parse; -import com.vlonjatg.progressactivity.ProgressRelativeLayout; -import com.vorlonsoft.android.rate.AppRate; - -import java.util.List; - -import okhttp3.OkHttpClient; -import okhttp3.logging.HttpLoggingInterceptor; - - -public class AppsMenuParseActivity extends AppCompatActivity implements MaterialDialog.SingleButtonCallback, ParseAppsAdapter.ParseAppAdapterListener { - - private ParseServerConfigStorage storage; - - private ParseAppsAdapter adapter; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_apps_menu); - Toolbar toolbar = findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - - FloatingActionButton fab = findViewById(R.id.fab); - fab.setOnClickListener(view -> showDialog()); - - storage = new ParseServerConfigStorage(getApplicationContext()); - toggleMainScreen(isMainScreenEmpty()); - - List list = storage.getServers(); - adapter = new ParseAppsAdapter(this, list); - ListView listView = findViewById(R.id.list_view); - listView.setAdapter(adapter); - adapter.setListener(this); - - AppRate.with(this) - .setInstallDays((byte) 2) - .setLaunchTimes((byte) 4) - .setShowLaterButton(true) - .setShowNeverButton(true) - .monitor(); - - // Show dialog after 40 seconds - final Handler handler = new Handler(); - handler.postDelayed(() -> AppRate.showRateDialogIfMeetsConditions(AppsMenuParseActivity.this), 1000*40); - } - - private boolean isMainScreenEmpty() { - return storage.getServers().isEmpty(); - } - - private void toggleMainScreen(boolean isEmpty) { - ProgressRelativeLayout layout = findViewById(R.id.stateful_layout); - if (isEmpty) { - layout.showEmpty(R.drawable.ic_parse_24dp, getString(R.string.empty_state_apps_screen_short), getString(R.string.empty_state_apps_screen_long)); - } else { - layout.showContent(); - } - } - - private void showDialog() { - new MaterialDialog.Builder(this) - .title("Add Parse Server") - .customView(R.layout.dialog_add_app, true) - .positiveText("OK") - .onPositive(this) - .show(); - } - - @Override - public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { - ParseServerConfig serverConfig = getConfigFromDialog(dialog); - storage.saveServer(serverConfig); - adapter.add(serverConfig); - adapter.notifyDataSetChanged(); - toggleMainScreen(isMainScreenEmpty()); - } - - private ParseServerConfig getConfigFromDialog(MaterialDialog dialog) { - View v = dialog.getCustomView(); - EditText appName = v.findViewById(R.id.inputAppName); - EditText appId = v.findViewById(R.id.inputAppId); - EditText masterKey = v.findViewById(R.id.inputAppMasterKey); - EditText serverUrl = v.findViewById(R.id.inputServerUrl); - return new ParseServerConfig( - appName.getText().toString(), - appId.getText().toString(), - masterKey.getText().toString(), - serverUrl.getText().toString()); - } - - @Override - public void onClickOpen(ParseServerConfig config) { - String error = checkForParseConfigError(config); - if (error != null) { - Snackbar.make(findViewById(R.id.stateful_layout), error, Snackbar.LENGTH_LONG).show(); - return; - } - - // Re init parse sdk so we can open a new parse app - Parse.destroy(); - initParse(config.appId, config.serverUrl, config.masterKey); - - Intent i = new Intent(this, SingleAppParseActivity.class); - i.putExtra(Const.BUNDLE_KEY_PARSE_APP_NAME, config.appName); - this.startActivityForResult(i, 1); - } - - // Validate parse server config. If No error returns null, otherwise returns error message. - private String checkForParseConfigError(ParseServerConfig config) { - if (config.appId.equals("")) { - return getString(R.string.error_app_id_missing); - } - if (config.serverUrl.equals("")) { - return getString(R.string.error_server_url_missing); - } - if (!config.serverUrl.startsWith("http://") && !config.serverUrl.startsWith("https://")){ - return getString(R.string.error_server_url_malformed); - } - if (config.masterKey.equals("")) { - return getString(R.string.error_master_key_missing); - } - return null; - } - - @Override - public void onClickEdit(final ParseServerConfig config) { - MaterialDialog dialog = new MaterialDialog.Builder(this) - .title("Edit Parse Server") - .customView(R.layout.dialog_add_app, true) - .positiveText("OK") - .onPositive((thisDialog, which) -> { - adapter.remove(config); - storage.deleteServer(config.appId); - ParseServerConfig newConfig = getConfigFromDialog(thisDialog); - adapter.add(newConfig); - storage.saveServer(newConfig); - adapter.notifyDataSetChanged(); - }) - .show(); - - View v = dialog.getCustomView(); - ((EditText)v.findViewById(R.id.inputAppName)).setText(config.appName); - ((EditText)v.findViewById(R.id.inputAppId)).setText(config.appId); - ((EditText)v.findViewById(R.id.inputAppMasterKey)).setText(config.masterKey); - ((EditText)v.findViewById(R.id.inputServerUrl)).setText(config.serverUrl); - } - - @Override - public void onClickDelete(ParseServerConfig config) { - storage.deleteServer(config.appId); - adapter.remove(config); - adapter.notifyDataSetChanged(); - toggleMainScreen(isMainScreenEmpty()); - } - - private void initParse(String appId, String serverUrl, String masterKey) { - Log.i("ParseDashboard", "Starting client for " + serverUrl + " appId: " + appId); - OkHttpClient.Builder builder = new OkHttpClient.Builder(); - HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(); - httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); - builder.networkInterceptors().add(httpLoggingInterceptor); - - Parse.initialize(new Parse.Configuration.Builder(this) - .applicationId(appId) - .server(serverUrl) - .masterKey(masterKey) - .clientBuilder(builder) - .build()); - } -} diff --git a/app/src/main/java/com/galtashma/parsedashboard/screens/AppsMenuParseActivity.kt b/app/src/main/java/com/galtashma/parsedashboard/screens/AppsMenuParseActivity.kt new file mode 100644 index 0000000..7d3ac47 --- /dev/null +++ b/app/src/main/java/com/galtashma/parsedashboard/screens/AppsMenuParseActivity.kt @@ -0,0 +1,210 @@ +package com.galtashma.parsedashboard.screens + +import android.content.Intent +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.os.PersistableBundle +import android.util.Log +import android.widget.EditText +import android.widget.ListView +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.Toolbar +import com.afollestad.materialdialogs.DialogAction +import com.afollestad.materialdialogs.MaterialDialog +import com.galtashma.parsedashboard.Const +import com.galtashma.parsedashboard.ParseServerConfig +import com.galtashma.parsedashboard.ParseServerConfigStorage +import com.galtashma.parsedashboard.R +import com.galtashma.parsedashboard.adapters.ParseAppsAdapter +import com.google.android.material.floatingactionbutton.FloatingActionButton +import com.google.android.material.snackbar.Snackbar +import com.parse.Parse +import com.vlonjatg.progressactivity.ProgressRelativeLayout +import com.vorlonsoft.android.rate.AppRate +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor + +/** + * Created by gal on 3/16/18, rewritten by Cyb3rKo on 05/12/22. + */ + +class AppsMenuParseActivity : + AppCompatActivity(), + MaterialDialog.SingleButtonCallback, + ParseAppsAdapter.ParseAppAdapterListener { + + private lateinit var storage: ParseServerConfigStorage + private lateinit var adapter: ParseAppsAdapter + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_apps_menu) + val toolbar = findViewById(R.id.toolbar) + setSupportActionBar(toolbar) + + val fab = findViewById(R.id.fab) + fab.setOnClickListener { showDialog() } + + storage = ParseServerConfigStorage(applicationContext) + toggleMainScreen(isMainScreenEmpty()) + + val list = storage.getServers() + adapter = ParseAppsAdapter(this, list) + val listView = findViewById(R.id.list_view) + listView.adapter = adapter + adapter.setListener(this) + + AppRate.with(this) + .setInstallDays(2) + .setLaunchTimes(4) + .setShowLaterButton(true) + .setShowNeverButton(true) + .monitor() + + // Show dialog after 40 seconds + Handler(Looper.getMainLooper()).postDelayed({ + AppRate.showRateDialogIfMeetsConditions(this) + }, 1000L * 40) + } + + private fun isMainScreenEmpty() = storage.getServers().isEmpty() + + private fun toggleMainScreen(isEmpty: Boolean) { + val layout = findViewById(R.id.stateful_layout) + if (isEmpty) { + layout.showEmpty( + R.drawable.ic_parse_24dp, + getString(R.string.empty_state_apps_screen_short), + getString(R.string.empty_state_apps_screen_long) + ) + } else { + layout.showContent() + } + } + + private fun showDialog() { + MaterialDialog.Builder(this) + .title("Add Parse Server") + .customView(R.layout.dialog_add_app, true) + .positiveText("OK") + .onPositive(this) + .show() + } + + override fun onClick(dialog: MaterialDialog, which: DialogAction) { + val serverConfig = getConfigFromDialog(dialog) + storage.saveServer(serverConfig) + adapter.add(serverConfig) + adapter.notifyDataSetChanged() + toggleMainScreen(isMainScreenEmpty()) + } + + private fun getConfigFromDialog(dialog: MaterialDialog): ParseServerConfig { + val view = dialog.customView + view?.apply { + val appName = findViewById(R.id.inputAppName) + val appId = findViewById(R.id.inputAppId) + val masterKey = findViewById(R.id.inputAppMasterKey) + val serverUrl = findViewById(R.id.inputServerUrl) + + return ParseServerConfig( + appName?.text.toString(), + appId?.text.toString(), + masterKey?.text.toString(), + serverUrl?.text.toString() + ) + } + + // If view is somehow null + return ParseServerConfig( + "", + "", + "", + "" + ) + } + + override fun onClickOpen(config: ParseServerConfig) { + val error = checkForParseConfigError(config) + if (error != null) { + Snackbar.make(findViewById(R.id.stateful_layout), error, Snackbar.LENGTH_LONG).show() + return + } + + // Re init parse sdk so we can open a new parse app + Parse.destroy() + initParse(config.appId, config.serverUrl, config.masterKey) + + val i = Intent(this, SingleAppParseActivity::class.java) + i.putExtra(Const.BUNDLE_KEY_PARSE_APP_NAME, config.appName) + startActivityForResult(i, 1) + } + + // Validate parse server config. If no error returns null, otherwise returns error message. + private fun checkForParseConfigError(config: ParseServerConfig): String? { + if (config.appId == "") { + return getString(R.string.error_app_id_missing) + } + if (config.serverUrl == "") { + return getString(R.string.error_server_url_missing) + } + if (!config.serverUrl.startsWith("http://") && !config.serverUrl.startsWith("https://")) { + return getString(R.string.error_server_url_malformed) + } + if (config.masterKey == "") { + return getString(R.string.error_master_key_missing) + } + return null + } + + override fun onClickEdit(config: ParseServerConfig) { + val dialog = MaterialDialog.Builder(this) + .title("Edit Parse Server") + .customView(R.layout.dialog_add_app, true) + .positiveText("OK") + .onPositive { thisDialog, _ -> + adapter.remove(config) + storage.deleteServer(config.appId) + val newConfig = getConfigFromDialog(thisDialog) + adapter.add(newConfig) + storage.saveServer(newConfig) + adapter.notifyDataSetChanged() + } + .show() + + val view = dialog.customView + view?.apply { + findViewById(R.id.inputAppName).setText(config.appName) + findViewById(R.id.inputAppId).setText(config.appId) + findViewById(R.id.inputAppMasterKey).setText(config.masterKey) + findViewById(R.id.inputServerUrl).setText(config.serverUrl) + } + } + + override fun onClickDelete(config: ParseServerConfig) { + storage.deleteServer(config.appId) + adapter.remove(config) + adapter.notifyDataSetChanged() + toggleMainScreen(isMainScreenEmpty()) + } + + private fun initParse( + appId: String, + serverUrl: String, + clientKey: String + ) { + Log.i("ParseDashboard", "Starting client for $serverUrl appId: $appId") + val builder = OkHttpClient.Builder() + val httpLoggingInterceptor = HttpLoggingInterceptor() + httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY + builder.networkInterceptors().add(httpLoggingInterceptor) + + Parse.initialize(Parse.Configuration.Builder(this) + .applicationId(appId) + .server(serverUrl) + .masterKey(clientKey) + .clientBuilder(builder) + .build()) + } +} diff --git a/app/src/main/java/com/galtashma/parsedashboard/screens/SingleAppParseActivity.java b/app/src/main/java/com/galtashma/parsedashboard/screens/SingleAppParseActivity.java deleted file mode 100644 index 8102ddf..0000000 --- a/app/src/main/java/com/galtashma/parsedashboard/screens/SingleAppParseActivity.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.galtashma.parsedashboard.screens; - -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; -import android.widget.ListView; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; - -import com.galtashma.parsedashboard.Const; -import com.galtashma.parsedashboard.R; -import com.galtashma.parsedashboard.adapters.ParseClassesAdapter; -import com.parse.ParseSchema; -import com.vlonjatg.progressactivity.ProgressRelativeLayout; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import bolts.Continuation; - - -public class SingleAppParseActivity extends AppCompatActivity { - - private ParseClassesAdapter adapter; - private ProgressRelativeLayout statefulLayout; - - private Map schemas = new HashMap<>(); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_single_app); - Toolbar toolbar = findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - String appName = ""; - - Bundle extra = getIntent().getExtras(); - if (extra == null) { - extra = savedInstanceState; - } - - if (extra != null && extra.containsKey(Const.BUNDLE_KEY_PARSE_APP_NAME)) { - appName = extra.getString(Const.BUNDLE_KEY_PARSE_APP_NAME); - } - - setTitle(appName); - - statefulLayout = findViewById(R.id.stateful_layout); - adapter = new ParseClassesAdapter(this); - adapter.setListener(schema -> showTable(schema.getName())); - ListView listView = findViewById(R.id.list_view); - listView.setAdapter(adapter); - fetchSchemasAsync(); - } - - private void fetchSchemasAsync() { - statefulLayout.showLoading(); - ParseSchema.getParseSchemasAsync().continueWith((Continuation, Void>) task -> { - if (task.isFaulted() || task.isCancelled()) { - showErrorOnUIThread(getString(R.string.schemas_screen_error_title), task.getError()); - Log.e("ParseDashboard","Error fetching from " + " parse server.", task.getError()); - return null; - } - - Log.i(Const.TAG, "found schemas " + task.getResult()); - List s = task.getResult(); - updateListOnUIThread(s); - for (ParseSchema ps : s) { - schemas.put(ps.getName(), ps); - } - - return null; - }); - } - - private void updateListOnUIThread(final List schemasList) { - runOnUiThread(() -> { - if (schemasList.size() == 0) { - showEmptyState(); - return; - } - statefulLayout.showContent(); - adapter.clear(); - adapter.addAll(schemasList); - adapter.notifyDataSetChanged(); - }); - } - - private void showErrorOnUIThread(final String title, final Exception e) { - runOnUiThread(() -> statefulLayout.showError(R.drawable.ic_parse_24dp, title, e.getLocalizedMessage(), "Retry", view -> fetchSchemasAsync())); - } - - private void showEmptyState() { - statefulLayout.showEmpty(R.drawable.ic_parse_24dp, getString(R.string.empty_state_schemas_screen_short), getString(R.string.empty_state_schemas_screen_long)); - } - - private void showTable(String tableName) { - Intent intent = new Intent(this, SingleClassParseActivity.class); - intent.putExtra(Const.BUNDLE_KEY_CLASS_NAME, tableName); - if (schemas.containsKey(tableName)) { - int size = schemas.get(tableName).getFields().size(); - String[] fieldsArr = new String[size]; - - Iterator it = schemas.get(tableName).getFields().keySet().iterator(); - for (int i=0; i() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_single_app) + val toolbar = findViewById(R.id.toolbar) + setSupportActionBar(toolbar) + + var extra = intent.extras + if (extra == null) { + extra = savedInstanceState + } + + val appName = if (extra != null && extra.containsKey(Const.BUNDLE_KEY_PARSE_APP_NAME)) { + extra.getString(Const.BUNDLE_KEY_PARSE_APP_NAME) + } else "" + + title = appName + + statefulLayout = findViewById(R.id.stateful_layout) + adapter = ParseClassesAdapter(this) + adapter.setListener(object: ParseClassesAdapter.OnClickListener { + override fun onSchemaCLicked(schema: ParseSchema) { + showTable(schema.name) + } + }) + val listView = findViewById(R.id.list_view) + listView.adapter = adapter + fetchSchemasAsync() + } + + private fun fetchSchemasAsync() { + statefulLayout.showLoading() + ParseSchema.getParseSchemasAsync().continueWith { task -> + if (task.isFaulted || task.isCancelled) { + showErrorOnUIThread(getString(R.string.schemas_screen_error_title), task.error) + Log.e("ParseDashboard","Error fetching from parse server.", task.error) + return@continueWith null + } + + Log.i(Const.TAG, "Found schemas: ${task.result}") + val s = task.result + updateListOnUIThread(s) + s.forEach { ps -> + schemas[ps.name] = ps + } + + return@continueWith null + } + } + + private fun updateListOnUIThread(schemasList: List) { + runOnUiThread { + if (schemasList.isEmpty()) { + showEmptyState() + return@runOnUiThread + } + statefulLayout.showContent() + adapter.clear() + adapter.addAll(schemasList) + adapter.notifyDataSetChanged() + } + } + + private fun showErrorOnUIThread(title: String, e: Exception) { + runOnUiThread { + statefulLayout.showError( + R.drawable.ic_parse_24dp, + title, + e.localizedMessage, + "Retry" + ) { + fetchSchemasAsync() + } + } + } + + private fun showEmptyState() { + statefulLayout.showEmpty( + R.drawable.ic_parse_24dp, + getString(R.string.empty_state_schemas_screen_short), + getString(R.string.empty_state_schemas_screen_long) + ) + } + + private fun showTable(tableName: String) { + val intent = Intent(this, SingleClassParseActivity::class.java) + intent.putExtra(Const.BUNDLE_KEY_CLASS_NAME, tableName) + if (schemas.containsKey(tableName)) { + val fields = schemas[tableName]?.fields?.keys?.toTypedArray() + if (fields != null) { + intent.putExtra(Const.BUNDLE_KEY_CLASS_FIELDS_NAME, fields) + this.startActivityForResult(intent, 1) + } + } + } +} diff --git a/app/src/main/java/com/galtashma/parsedashboard/screens/SingleClassParseActivity.java b/app/src/main/java/com/galtashma/parsedashboard/screens/SingleClassParseActivity.java deleted file mode 100644 index f505533..0000000 --- a/app/src/main/java/com/galtashma/parsedashboard/screens/SingleClassParseActivity.java +++ /dev/null @@ -1,210 +0,0 @@ -package com.galtashma.parsedashboard.screens; - -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.ListView; -import android.widget.RadioButton; -import android.widget.Spinner; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; - -import com.afollestad.materialdialogs.MaterialDialog; -import com.galtashma.lazyparse.LazyList; -import com.galtashma.lazyparse.ScrollInfiniteAdapter; -import com.galtashma.lazyparse.ScrollInfiniteListener; -import com.galtashma.parsedashboard.Const; -import com.galtashma.parsedashboard.ListPreferenceStore; -import com.galtashma.parsedashboard.SortPreferenceStore; -import com.galtashma.parsedashboard.adapters.ParseObjectsAdapter; -import com.galtashma.parsedashboard.R; -import com.parse.ParseObject; -import com.parse.ParseQuery; -import com.vlonjatg.progressactivity.ProgressRelativeLayout; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public class SingleClassParseActivity extends AppCompatActivity implements ScrollInfiniteAdapter.OnClickListener { - - private String className; - ProgressRelativeLayout statefulLayout; - private String[] fieldNames; - private ListPreferenceStore visibleFieldsStore; - private SortPreferenceStore sortPreferenceStore; - - private ParseObjectsAdapter adapter; - - private static final String PREF_KEY = "KEY_SingleClassParseActivity_"; - private static final String PREF_SORT = "SORT_SingleClassParseActivity"; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_single_class); - Toolbar toolbar = findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - Bundle extra = getIntent().getExtras(); - if (extra == null) { - extra = savedInstanceState; - } - - if (extra == null || !extra.containsKey(Const.BUNDLE_KEY_CLASS_NAME)) { - finish(); - return; - } - - className = extra.getString(Const.BUNDLE_KEY_CLASS_NAME); - setTitle(className); - - if (extra.containsKey(Const.BUNDLE_KEY_CLASS_FIELDS_NAME)) { - fieldNames = extra.getStringArray(Const.BUNDLE_KEY_CLASS_FIELDS_NAME); - - // Remove objectId field as it is special and is displayed anyway - List l = new ArrayList<>(Arrays.asList(fieldNames)); - if (l.contains("objectId")) { - l.remove("objectId"); - } - - fieldNames = l.toArray(new String[l.size()]); - } else { - fieldNames = new String[]{"createdAt", "updatedAt"}; - } - - visibleFieldsStore = new ListPreferenceStore(PREF_KEY + className); - if (visibleFieldsStore.isEmpty()) { - visibleFieldsStore.add("createdAt"); - visibleFieldsStore.add("updatedAt"); - } - - sortPreferenceStore = new SortPreferenceStore(PREF_SORT + className); - statefulLayout = findViewById(R.id.stateful_layout); - - initList(); - } - - private void initList() { - ListView listView = findViewById(R.id.list_view); - statefulLayout.showLoading(); - ParseQuery query = new ParseQuery<>(className); - - if (!sortPreferenceStore.isEmpty()) { - if (sortPreferenceStore.isAsc()) { - query.orderByAscending(sortPreferenceStore.getKey()); - } else { - query.orderByDescending(sortPreferenceStore.getKey()); - } - } - - LazyList list = new LazyList<>(query); - adapter = new ParseObjectsAdapter(this, list, visibleFieldsStore.getList()); - listView.setAdapter(adapter); - listView.setOnScrollListener(new ScrollInfiniteListener(adapter)); - adapter.setOnClickListener(this); - - if (list.getLimit() == 0) { - statefulLayout.showEmpty(R.drawable.ic_parse_24dp, getString(R.string.empty_state_objects_screen_short), getString(R.string.empty_state_objects_screen_long)); - return; - } - - statefulLayout.showContent(); - } - - @Override - public void onClick(ParseObject parseObject) { - Intent i = new Intent(this, SingleObjectParseActivity.class); - i.putExtra(Const.BUNDLE_KEY_CLASS_NAME, parseObject.getClassName()); - i.putExtra(Const.BUNDLE_KEY_OBJECT_ID, parseObject.getObjectId()); - this.startActivityForResult(i, 1); - } - - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu_class_view, menu); - return true; - } - - private void refresh() { - initList(); - } - - public void onRefresh(MenuItem item) { - refresh(); - } - - public void onSelectFavFields(MenuItem item) { - List selectedIndices = new ArrayList<>(); - - for (int i=0; i { - visibleFieldsStore.reset(); - for (CharSequence key : text) { - visibleFieldsStore.add(key.toString()); - } - adapter.updatePreviewFields(visibleFieldsStore.getList()); - return true; - }) - .positiveText(R.string.save) - .show(); - } - - private int findKeyIndex(String key) { - for (int i=0; i adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, fieldNames); - fieldSelector.setAdapter(adapter); - - if (!sortPreferenceStore.isEmpty()) { - int index = findKeyIndex(sortPreferenceStore.getKey()); - if (index != -1) { - fieldSelector.setSelection(index); - ascRadioButton.setChecked(sortPreferenceStore.isAsc()); - descRadioButton.setChecked(!sortPreferenceStore.isAsc()); - } - } - - Button cancelButton = dialog.getCustomView().findViewById(R.id.sort_dialog_button_cancel); - Button confirmButton = dialog.getCustomView().findViewById(R.id.sort_dialog_button_confirm); - cancelButton.setOnClickListener(view -> dialog.dismiss()); - - confirmButton.setOnClickListener(view -> { - String key = (String) fieldSelector.getSelectedItem(); - boolean asc = ascRadioButton.isChecked(); - sortPreferenceStore.update(key, asc); - dialog.dismiss(); - refresh(); - }); - - dialog.show(); - } -} diff --git a/app/src/main/java/com/galtashma/parsedashboard/screens/SingleClassParseActivity.kt b/app/src/main/java/com/galtashma/parsedashboard/screens/SingleClassParseActivity.kt new file mode 100644 index 0000000..3901e31 --- /dev/null +++ b/app/src/main/java/com/galtashma/parsedashboard/screens/SingleClassParseActivity.kt @@ -0,0 +1,211 @@ +package com.galtashma.parsedashboard.screens + +import android.content.Intent +import android.os.Bundle +import android.os.PersistableBundle +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.widget.* +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.Toolbar +import com.afollestad.materialdialogs.MaterialDialog +import com.galtashma.lazyparse.LazyList +import com.galtashma.lazyparse.ScrollInfiniteAdapter +import com.galtashma.lazyparse.ScrollInfiniteListener +import com.galtashma.parsedashboard.Const +import com.galtashma.parsedashboard.ListPreferenceStore +import com.galtashma.parsedashboard.R +import com.galtashma.parsedashboard.SortPreferenceStore +import com.galtashma.parsedashboard.adapters.ParseObjectsAdapter +import com.parse.ParseObject +import com.parse.ParseQuery +import com.vlonjatg.progressactivity.ProgressRelativeLayout + +/** + * Created by gal on 3/16/18, rewritten by Cyb3rKo on 05/12/22. + */ + +private const val PREF_KEY = "KEY_SingleClassParseActivity_" +private const val PREF_SORT = "SORT_SingleClassParseActivity" + +class SingleClassParseActivity : + AppCompatActivity(), + ScrollInfiniteAdapter.OnClickListener { + + private var className = "" + private lateinit var statefulLayout: ProgressRelativeLayout + private lateinit var fieldNames: MutableList + private lateinit var visibleFieldsStore: ListPreferenceStore + private lateinit var sortPreferenceStore: SortPreferenceStore + + private lateinit var adapter: ParseObjectsAdapter + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_single_class) + val toolbar = findViewById(R.id.toolbar) + setSupportActionBar(toolbar) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + + var extra = intent.extras + if (extra == null) { + extra = savedInstanceState + } + + if (extra == null || !extra.containsKey(Const.BUNDLE_KEY_CLASS_NAME)) { + finish() + return + } + + className = extra.getString(Const.BUNDLE_KEY_CLASS_NAME).toString() + title = className + + if (extra.containsKey(Const.BUNDLE_KEY_CLASS_FIELDS_NAME)) { + fieldNames = extra.getStringArray(Const.BUNDLE_KEY_CLASS_FIELDS_NAME)!!.toMutableList() + + // Remove objectId field as it is special and is displayed anyway + if (fieldNames.contains("objectId")) { + fieldNames.remove("objectId") + } + } else { + fieldNames = mutableListOf("createdAt", "updatedAt") + } + + visibleFieldsStore = ListPreferenceStore(PREF_KEY + className) + if (visibleFieldsStore.isEmpty()) { + visibleFieldsStore.add("createdAt") + visibleFieldsStore.add("updatedAt") + } + + sortPreferenceStore = SortPreferenceStore(PREF_SORT + className) + statefulLayout = findViewById(R.id.stateful_layout) + + initList() + } + + private fun initList() { + val listView = findViewById(R.id.list_view) + statefulLayout.showLoading() + val query = ParseQuery(className) + + if (!sortPreferenceStore.isEmpty()) { + if (sortPreferenceStore.isAsc()) { + query.orderByAscending(sortPreferenceStore.getKey()) + } else { + query.orderByDescending(sortPreferenceStore.getKey()) + } + } + + val list = LazyList(query) + adapter = ParseObjectsAdapter(this, list, visibleFieldsStore.list) + listView.adapter = adapter + listView.setOnScrollListener(ScrollInfiniteListener(adapter)) + adapter.setOnClickListener(this) + + if (list.limit == 0) { + statefulLayout.showEmpty( + R.drawable.ic_parse_24dp, + getString(R.string.empty_state_objects_screen_short), + getString(R.string.empty_state_objects_screen_long) + ) + return + } + + statefulLayout.showContent() + } + + override fun onClick(parseObject: ParseObject?) { + val i = Intent(this, SingleObjectParseActivity::class.java) + i.putExtra(Const.BUNDLE_KEY_CLASS_NAME, parseObject?.className) + i.putExtra(Const.BUNDLE_KEY_OBJECT_ID, parseObject?.objectId) + startActivityForResult(i, 1) + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.menu_class_view, menu) + return true + } + + private fun refresh() = initList() + + @Suppress("UNUSED_PARAMETER") + fun onRefresh(item: MenuItem) = refresh() + + @Suppress("UNUSED_PARAMETER") + fun onSelectFavFields(item: MenuItem) { + val selectedIndices = mutableListOf() + + fieldNames.forEachIndexed { index, fieldName -> + if (visibleFieldsStore.exists(fieldName)) { + selectedIndices.add(index) + } + } + + Log.d("ParseDashboard", "Selected indices: $selectedIndices") + + MaterialDialog.Builder(this) + .title(R.string.action_select_fav_fields) + .items(fieldNames) + .itemsCallbackMultiChoice(selectedIndices.toTypedArray()) { _, _, text -> + visibleFieldsStore.reset() + text.forEach { key -> + visibleFieldsStore.add(key.toString()) + } + adapter.updatePreviewFields(visibleFieldsStore.list) + true + } + .positiveText(R.string.save) + .show() + } + + private fun findKeyIndex(key: String): Int { + fieldNames.forEachIndexed { index, k -> + if (k == key) return index + } + return -1 + } + + @Suppress("UNUSED_PARAMETER") + fun onSelectOrderFields(item: MenuItem) { + val dialog = MaterialDialog.Builder(this) + .title("Sort Fields") + .customView(R.layout.dialog_field_sort, false).build() + + if (dialog.customView == null) return + + val dialogView = dialog.customView!! + val fieldSelector = dialogView.findViewById(R.id.sort_dialog_selected_field) + val ascRadioButton = dialogView.findViewById(R.id.sort_dialog_order_asc) + val descRadioButton = dialogView.findViewById(R.id.sort_dialog_order_desc) + val adapter = ArrayAdapter( + this, + android.R.layout.simple_spinner_dropdown_item, + fieldNames + ) + fieldSelector.adapter = adapter + + if (!sortPreferenceStore.isEmpty()) { + val index = findKeyIndex(sortPreferenceStore.getKey()) + if (index != -1) { + fieldSelector.setSelection(index) + ascRadioButton.isChecked = sortPreferenceStore.isAsc() + descRadioButton.isChecked = !sortPreferenceStore.isAsc() + } + } + + val cancelButton = dialogView.findViewById