diff --git a/.github/workflows/publish-release.yaml b/.github/workflows/publish-release.yaml new file mode 100644 index 0000000..89fc861 --- /dev/null +++ b/.github/workflows/publish-release.yaml @@ -0,0 +1,31 @@ +name: Publish Release +on: workflow_dispatch + +jobs: + publish: + if: ${{ github.repository == 'requestly/requestly-android-sdk'}} + runs-on: [ubuntu-latest] + + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + + - name: Publish to Maven Local + run: ./gradlew publishToMavenLocal + env: + ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }} + ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }} + + - name: Upload Build Artifacts + uses: actions/upload-artifact@v3 + with: + name: 'requestly-android-sdk-release-artifacts' + path: '~/.m2/repository/' + + - name: Publish to the Staging Repository + run: ./gradlew publishReleasePublicationToStagingRepository --no-parallel + env: + ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }} + ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }} + ORG_GRADLE_PROJECT_NEXUS_USERNAME: ${{ secrets.ORG_GRADLE_PROJECT_NEXUS_USERNAME }} + ORG_GRADLE_PROJECT_NEXUS_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_NEXUS_PASSWORD }} \ No newline at end of file diff --git a/.github/workflows/publish-snapshot.yaml b/.github/workflows/publish-snapshot.yaml new file mode 100644 index 0000000..8603732 --- /dev/null +++ b/.github/workflows/publish-snapshot.yaml @@ -0,0 +1,31 @@ +name: Publish Snapshot +on: workflow_dispatch + +jobs: + publish: + if: ${{ github.repository == 'requestly/requestly-android-sdk'}} + runs-on: [ubuntu-latest] + + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + + - name: Publish to Maven Local + run: ./gradlew publishToMavenLocal + env: + ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }} + ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }} + + - name: Upload Build Artifacts + uses: actions/upload-artifact@v3 + with: + name: 'requestly-android-sdk-snapshot-artifacts' + path: '~/.m2/repository/' + + - name: Publish to the Staging Repository + run: ./gradlew publishReleasePublicationToSnapshotRepository --no-parallel + env: + ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }} + ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }} + ORG_GRADLE_PROJECT_NEXUS_USERNAME: ${{ secrets.ORG_GRADLE_PROJECT_NEXUS_USERNAME }} + ORG_GRADLE_PROJECT_NEXUS_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_NEXUS_PASSWORD }} \ No newline at end of file diff --git a/README.md b/README.md index 1ed50e9..9ab3136 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@

GitHub release - Maven + Maven + Maven

@@ -13,6 +14,7 @@ Requestly Android SDK lets you debug your android apps without needing you to setup any proxies or install any certificates everytime. It makes easy to identify & debug your Android Apps faster and save your time. +- [Try Now](#try-now) - [Installation](#installation) - [SDK Initialization](#sdk-initialization) - [API Debugger Initialization](#api-debugger-initialization) @@ -21,8 +23,26 @@ Requestly Android SDK lets you debug your android apps without needing you to se - [Features](#features) - [API Debugger](#api-debugger) - [Analytics Event Debugger](#analytics-event-debugger) - - [Logs Debugger](#logs-debugger-coming-soon) + - [Logs Debugger](#logs-debugger) + - [Host Switcher](#host-switcher) - [Acknowledgments](#acknowledgments) + +## Try Now +### ↓ Click on Screenshots to try Apps ↓ + + Try Now + + + Try Now + + + Try Now + + +### OR +- [Dowload CryptoDemo Apk](https://github.com/requestly/android-debugger-examples/releases/latest/download/cryptodemo-debug.apk) +- [Download Infinity Reddit Apk](https://github.com/requestly/android-debugger-examples/releases/latest/download/infinity-reddit-debug.apk) +- [Download Pokedex Apk](https://github.com/requestly/android-debugger-examples/releases/latest/download/pokedex-debug.apk) ## Installation The best way to install the Requestly Android SDK is with a build system like Gradle. This ensures you can easily upgrade to the latest versions. @@ -31,10 +51,10 @@ RQInterceptor is distributed through [Maven Central](https://search.maven.org/se ``` dependencies { - debugImplementation "io.requestly:requestly-android:2.4.0" - releaseImplementation "io.requestly:requestly-android-noop:2.4.0" - debugImplementation "io.requestly:requestly-android-okhttp:2.4.0" - releaseImplementation "io.requestly:requestly-android-okhttp-noop:2.4.0" + debugImplementation "io.requestly:requestly-android:2.4.3" + releaseImplementation "io.requestly:requestly-android-noop:2.4.3" + debugImplementation "io.requestly:requestly-android-okhttp:2.4.3" + releaseImplementation "io.requestly:requestly-android-okhttp-noop:2.4.3" } ``` @@ -47,12 +67,12 @@ class App : Application(){ super.onCreate() // Initialize Requestly SDK like this - Requestly.Builder(this, "") + Requestly.Builder(this, [optional ""]) .build() } } ``` - +> `sdk-key` is optional. You can use local devtool features without sdk-key.
To get the sdk key, you need to create an app. Follow the steps [here](https://docs.requestly.io/android/tutorial/create-app) to create an app. ## API Debugger Initialization @@ -97,11 +117,15 @@ RequestlyEvent.send(, >) Events Debugger -### Logs Debugger (Coming Soon) +### Logs Debugger Debug your Logs directly from your App. No need to connect your device to your computer to know what's happening inside your app. Logs Debugger +### Host Switcher +Switch between production and staging APIs easily in your Android debug builds. Eg. api.requestly.io → staging.requestly.io + +Logs Debugger + ## Acknowledgments -Special Thanks to chuckerteam for maintaining such an awesome project because of which rq-interceptor was possible -https://github.com/chuckerteam/chucker +Special Thanks to [chuckerteam](https://github.com/chuckerteam/chucker) for maintaining such an awesome project [Chucker](https://github.com/chuckerteam/chucker) because of which requestly-android-sdk was possible. diff --git a/assets/host-switcher.jpeg b/assets/host-switcher.jpeg new file mode 100644 index 0000000..d312a74 Binary files /dev/null and b/assets/host-switcher.jpeg differ diff --git a/gradle.properties b/gradle.properties index 954d323..655dfb7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,9 +19,9 @@ org.gradle.parallel=true android.useAndroidX=true -VERSION_NAME=2.4.1 +VERSION_NAME=2.4.5-SNAPSHOT # 1*100*100 + 0*100 + 0 => 10000 -VERSION_CODE=20401 +VERSION_CODE=20405 GROUP=io.requestly POM_REPO_NAME=Requestly Android SDK diff --git a/requestly-android-core-noop/build.gradle b/requestly-android-core-noop/build.gradle index 39b9071..206e40d 100644 --- a/requestly-android-core-noop/build.gradle +++ b/requestly-android-core-noop/build.gradle @@ -11,6 +11,12 @@ android { '-module-name', "io.requestly.android.core", "-Xexplicit-api=strict" ] + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' } buildFeatures { diff --git a/requestly-android-core/src/main/java/io/requestly/android/core/KeyValueStorageManager.kt b/requestly-android-core/src/main/java/io/requestly/android/core/KeyValueStorageManager.kt index 354029b..d147a80 100644 --- a/requestly-android-core/src/main/java/io/requestly/android/core/KeyValueStorageManager.kt +++ b/requestly-android-core/src/main/java/io/requestly/android/core/KeyValueStorageManager.kt @@ -4,16 +4,56 @@ import android.content.Context import android.content.SharedPreferences import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import java.io.File +import java.lang.ref.WeakReference + +data class SharedPrefFileData( + val key: String, + val value: Any?, + val dataType: SharedPrefType?, + val fileName: String, +) + +enum class SharedPrefType { + STRING { + override fun toString() = "String" + }, + INTEGER { + override fun toString() = "Integer" + }, + DOUBLE { + override fun toString() = "Double" + }, + LONG { + override fun toString() = "Long" + }, + BOOLEAN { + override fun toString() = "Boolean" + }, + STRING_SET { + override fun toString() = "StringSet" + }; + + abstract override fun toString(): String +} object KeyValueStorageManager { - private const val FILE_NAME = "io.requestly.requestly_pref_file" + private const val REQUESTLY_SHARED_PREF_FILE_PREFIX = "io.requestly." + private const val FILE_NAME = "${REQUESTLY_SHARED_PREF_FILE_PREFIX}requestly_pref_file" + /** + * Full path to the default directory assigned to the package for its + * persistent data. + */ + private lateinit var mContext: WeakReference private lateinit var mSharedPref: SharedPreferences private lateinit var gson: Gson - private var changeListeners: MutableList = mutableListOf() + private var changeListeners: MutableList = + mutableListOf() fun initialize(context: Context) { + mContext = WeakReference(context) mSharedPref = context.getSharedPreferences(FILE_NAME, 0) gson = Gson() } @@ -50,6 +90,63 @@ object KeyValueStorageManager { return gson.fromJson(json, typeToken.type) } + fun deleteKeyFromFile(keyName: String, fileName: String) { + val pref = mContext.get()?.getSharedPreferences(fileName, 0) ?: return + + with(pref.edit()) { + this.remove(keyName) + this.commit() + } + } + + fun putStringInfile(value: String, keyName: String, fileName: String) { + val pref = mContext.get()?.getSharedPreferences(fileName, 0) ?: return + with(pref.edit()) { + this.putString(keyName, value) + this.commit() + } + } + + fun putStringSetInfile(value: Set, keyName: String, fileName: String) { + val pref = mContext.get()?.getSharedPreferences(fileName, 0) ?: return + with(pref.edit()) { + this.putStringSet(keyName, value) + this.commit() + } + } + + fun putIntegerInfile(value: Int, keyName: String, fileName: String) { + val pref = mContext.get()?.getSharedPreferences(fileName, 0) ?: return + with(pref.edit()) { + this.putInt(keyName, value) + this.commit() + } + } + + fun putDoubleInfile(value: Double, keyName: String, fileName: String) { + val pref = mContext.get()?.getSharedPreferences(fileName, 0) ?: return + with(pref.edit()) { + this.putFloat(keyName, value.toFloat()) + this.commit() + } + } + + fun putLongInfile(value: Long, keyName: String, fileName: String) { + val pref = mContext.get()?.getSharedPreferences(fileName, 0) ?: return + with(pref.edit()) { + this.putLong(keyName, value) + this.commit() + } + } + + fun putBooleanInfile(value: Boolean, keyName: String, fileName: String) { + val pref = mContext.get()?.getSharedPreferences(fileName, 0) ?: return + with(pref.edit()) { + this.putBoolean(keyName, value) + this.commit() + } + } + fun registerChangeListener(forKey: String, changeListener: () -> Unit) { val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key -> if (key === forKey) { @@ -59,4 +156,44 @@ object KeyValueStorageManager { changeListeners.add(listener) mSharedPref.registerOnSharedPreferenceChangeListener(listener) } + + @Suppress("CANDIDATE_CHOSEN_USING_OVERLOAD_RESOLUTION_BY_LAMBDA_ANNOTATION") + fun fetchDataFromAllSharedPrefFiles(): List { + val context = mContext.get() ?: return emptyList() + + val prefsDir = File(context.applicationInfo.dataDir, "shared_prefs") + + if (!prefsDir.exists() || !prefsDir.isDirectory) return emptyList() + + return prefsDir + .list() + ?.filter { !it.startsWith(REQUESTLY_SHARED_PREF_FILE_PREFIX) } + ?.flatMap { filename -> + val fileNameWithOutExtension = filename.removeSuffix(".xml") + val thisPref = context.getSharedPreferences(fileNameWithOutExtension, 0) + return@flatMap thisPref.all.map { entry -> + SharedPrefFileData( + key = entry.key, + value = entry.value, + dataType = detectType(entry.value), + fileName = fileNameWithOutExtension, + ) + } + } ?: emptyList() + } + + private fun detectType(value: Any?): SharedPrefType? { + if (value == null) return null + + return when (value::class.simpleName) { + "Int" -> SharedPrefType.INTEGER + "Float" -> SharedPrefType.DOUBLE + "Long" -> SharedPrefType.LONG + "HashSet" -> SharedPrefType.STRING_SET + "Boolean" -> SharedPrefType.BOOLEAN + "String" -> SharedPrefType.STRING + else -> null + } + } } + diff --git a/requestly-android-core/src/main/java/io/requestly/android/core/Requestly.kt b/requestly-android-core/src/main/java/io/requestly/android/core/Requestly.kt index 0a4e58b..979a1be 100644 --- a/requestly-android-core/src/main/java/io/requestly/android/core/Requestly.kt +++ b/requestly-android-core/src/main/java/io/requestly/android/core/Requestly.kt @@ -51,17 +51,17 @@ class Requestly { // End Configuration fun build() { - applicationScope.launch { - Log.d(Constants.LOG_TAG, "Start: Building Core") + Log.d(Constants.LOG_TAG, "Start: Building Core") - // Create Requestly Instance - INSTANCE = Requestly() - getInstance().applicationContext = applicationContext - getInstance().listNotificationHelper = ListNotificationHelper(applicationContext) + // Create Requestly Instance + INSTANCE = Requestly() + getInstance().applicationContext = applicationContext + getInstance().listNotificationHelper = ListNotificationHelper(applicationContext) - // Init KeyValueStorageManager - KeyValueStorageManager.initialize(applicationContext) + // Init KeyValueStorageManager + KeyValueStorageManager.initialize(applicationContext) + applicationScope.launch { // Init SettingsManager Singleton // Overrides the token set previously appToken?.let { diff --git a/requestly-android-core/src/main/java/io/requestly/android/core/modules/hostSwitcher/HostSwitcherFragment.kt b/requestly-android-core/src/main/java/io/requestly/android/core/modules/hostSwitcher/HostSwitcherFragment.kt index 4c7aa28..e94d063 100644 --- a/requestly-android-core/src/main/java/io/requestly/android/core/modules/hostSwitcher/HostSwitcherFragment.kt +++ b/requestly-android-core/src/main/java/io/requestly/android/core/modules/hostSwitcher/HostSwitcherFragment.kt @@ -87,6 +87,8 @@ class HostSwitcherFragment : Fragment() { val adaptor = HostSwitchItemAdaptor(items) viewModel.rulesListLive.observe(viewLifecycleOwner) { adaptor.items = it.map(mapper) + // Its fine to use notifyDataSetChanged here. + // Data size is small enough to not cause Frame skips or lags. adaptor.notifyDataSetChanged() } mainBinding.hostSwitcherRulesRecyclerView.adapter = adaptor diff --git a/requestly-android-core/src/main/java/io/requestly/android/core/modules/sharedPrefViewer/SharedPrefLineItemAdaptor.kt b/requestly-android-core/src/main/java/io/requestly/android/core/modules/sharedPrefViewer/SharedPrefLineItemAdaptor.kt new file mode 100644 index 0000000..c06261c --- /dev/null +++ b/requestly-android-core/src/main/java/io/requestly/android/core/modules/sharedPrefViewer/SharedPrefLineItemAdaptor.kt @@ -0,0 +1,63 @@ +package io.requestly.android.core.modules.sharedPrefViewer + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import io.requestly.android.core.databinding.SharedPrefLineItemBinding + +data class SharedPrefLineItemModel( + val dataTypeText: String, + val fileName: String, + val prefKeyText: String, + val prefValueText: String, + val onEditClickListener: (() -> Unit)?, + val onDeleteClickListener: (() -> Unit)? +) + +class SharedPrefLineItemAdaptor(var items: List) : + RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder { + val viewBinding = SharedPrefLineItemBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + return ItemViewHolder(viewBinding) + } + + override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { + holder.bindTo(items[position]) + } + + override fun getItemCount(): Int { + return items.size + } + + class ItemViewHolder(itemView: SharedPrefLineItemBinding) : + RecyclerView.ViewHolder(itemView.root) { + + private val dataTypeTextView = itemView.dataTypeTextView + private val fileNameTextView = itemView.fileNameTextView + private val prefKeyTextView = itemView.prefKeyTextView + private val prefValueTextView = itemView.prefValueTextView + private val editPrefButton = itemView.editPrefButton + private val deletePrefButton = itemView.deletePrefButton + private val prefValueExtraInfoTextView = itemView.prefValueExtraInfoTextView + + fun bindTo(model: SharedPrefLineItemModel) { + dataTypeTextView.text = model.dataTypeText + fileNameTextView.text = model.fileName + prefKeyTextView.text = model.prefKeyText + prefValueTextView.text = model.prefValueText + prefValueExtraInfoTextView.visibility = View.GONE + editPrefButton.setOnClickListener { + model.onEditClickListener?.let { it1 -> it1() } + } + deletePrefButton.setOnClickListener { + model.onDeleteClickListener?.let { it1 -> it1() } + } + } + } +} diff --git a/requestly-android-core/src/main/java/io/requestly/android/core/modules/sharedPrefViewer/SharedPrefViewerFragment.kt b/requestly-android-core/src/main/java/io/requestly/android/core/modules/sharedPrefViewer/SharedPrefViewerFragment.kt new file mode 100644 index 0000000..1dc0448 --- /dev/null +++ b/requestly-android-core/src/main/java/io/requestly/android/core/modules/sharedPrefViewer/SharedPrefViewerFragment.kt @@ -0,0 +1,183 @@ +package io.requestly.android.core.modules.sharedPrefViewer + +import android.annotation.SuppressLint +import android.app.AlertDialog +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.EditText +import android.widget.TextView +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.recyclerview.widget.LinearLayoutManager +import io.requestly.android.core.R +import io.requestly.android.core.SharedPrefFileData +import io.requestly.android.core.databinding.FragmentSharedPrefViewerBinding + +class SharedPrefViewerFragment : Fragment() { + + + private lateinit var mainBinding: FragmentSharedPrefViewerBinding + private val viewModel: SharedPrefViewerViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + mainBinding = FragmentSharedPrefViewerBinding.inflate(layoutInflater) + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + mainBinding = FragmentSharedPrefViewerBinding.inflate(layoutInflater) + + // Inflate the layout for this fragment + initRecyclerView() + initFileSelectorSpinner() + + return mainBinding.root + } + + private fun initFileSelectorSpinner() { + // Keeping a copy of the list, because `adaptor.clear()` removes all entries from the + // live data also. Ideally this should be handled in viewModel itself. + val files = viewModel.prefFilesNamesLive.value?.toMutableList() ?: emptyList() + val adaptor = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, files) + adaptor.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + + with(mainBinding.fileSelectSpinner) { + this.adapter = adaptor + this.setSelection(files.indexOf(viewModel.mSelectedFileName)) + this.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onNothingSelected(parent: AdapterView<*>?) { + + } + + override fun onItemSelected( + parent: AdapterView<*>?, + view: View?, + position: Int, + id: Long + ) { + viewModel.fileNameSelected(files[position]) + } + } + } + + viewModel.prefFilesNamesLive.observe(viewLifecycleOwner) { + adaptor.clear() + adaptor.addAll(it) + adaptor.notifyDataSetChanged() + mainBinding.fileSelectSpinner.setSelection(it.indexOf(viewModel.mSelectedFileName)) + } + } + + // Its fine to use notifyDataSetChanged here. + // Data size is small enough to not cause Frame skips or lags. + @SuppressLint("NotifyDataSetChanged") + private fun initRecyclerView() { + mainBinding.sharedPrefsEntryRecyclerView.layoutManager = + LinearLayoutManager(requireContext()) + + val mapper: (SharedPrefFileData) -> SharedPrefLineItemModel = { + SharedPrefLineItemModel( + dataTypeText = it.dataType.toString(), + fileName = it.fileName, + prefKeyText = it.key, + prefValueText = it.value.toString(), + onEditClickListener = { + showEditSharedPrefEntryDialog(it) { newValue -> + return@showEditSharedPrefEntryDialog viewModel.verifyAndSave( + newValue, + it + ) + } + }, + onDeleteClickListener = { + loadDeleteConfirmationDialog { + viewModel.deleteEntry(fileName = it.fileName, keyName = it.key) + } + } + ) + } + val adaptor = SharedPrefLineItemAdaptor(viewModel.prefEntriesLive.value?.map(mapper) ?: emptyList()) + mainBinding.sharedPrefsEntryRecyclerView.adapter = adaptor + + viewModel.prefEntriesLive.observe(viewLifecycleOwner) { list -> + adaptor.items = list.map(mapper) + adaptor.notifyDataSetChanged() + } + } + + private fun showEditSharedPrefEntryDialog( + model: SharedPrefFileData, + onSaveClick: (String) -> Boolean + ) { + // Create Dialog & Set its params + val dialog = Dialog(requireContext()) + dialog.setContentView(R.layout.shared_pref_edit_value_view) + dialog.window?.setLayout( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + dialog.setCancelable(false) + + // Find all Views and configure them + val saveButton = dialog.findViewById(R.id.saveTextButton) + val cancelButton = dialog.findViewById(R.id.cancelTextButton) + val fileNameTextView = dialog.findViewById(R.id.fileNameTextView2) + val dataTypeTextView = dialog.findViewById(R.id.dataTypeTextView2) + val prefKeyTextView = dialog.findViewById(R.id.prefKeyTextView2) + val alertTextView = dialog.findViewById(R.id.alertTextView) + alertTextView.visibility = View.GONE + val prefValueEditTextBox = dialog.findViewById(R.id.prefValueEditTextBox) + + prefKeyTextView.text = model.key + fileNameTextView.text = model.fileName + dataTypeTextView.text = model.dataType.toString() + prefValueEditTextBox.setText(model.value.toString(), TextView.BufferType.EDITABLE) + prefValueEditTextBox.setSelection(prefValueEditTextBox.length()) + + prefValueEditTextBox.requestFocus() + saveButton.setOnClickListener { + val success = onSaveClick(prefValueEditTextBox.text.toString()) + if (success) { + prefValueEditTextBox.setBackgroundResource(R.drawable.edit_text_box) + alertTextView.visibility = View.GONE + dialog.hide() + } else { + prefValueEditTextBox.setBackgroundResource(R.drawable.edit_text_box_alert) + alertTextView.text = "Invalid Value for type ${model.dataType.toString()}" + alertTextView.visibility = View.VISIBLE + } + } + cancelButton.setOnClickListener { + dialog.hide() + } + + // Show Dialog + dialog.show() + } + + private fun loadDeleteConfirmationDialog(onPositiveButtonClick: () -> Unit) { + AlertDialog.Builder(requireActivity()) + .setCancelable(false) + .setTitle("Are you sure you want to delete this?") + .setPositiveButton("Yes") { dialog, _ -> + onPositiveButtonClick() + dialog.cancel() + } + .setNegativeButton("No") { dialog, _ -> + dialog.cancel() + } + .create() + .show() + } +} diff --git a/requestly-android-core/src/main/java/io/requestly/android/core/modules/sharedPrefViewer/SharedPrefViewerViewModel.kt b/requestly-android-core/src/main/java/io/requestly/android/core/modules/sharedPrefViewer/SharedPrefViewerViewModel.kt new file mode 100644 index 0000000..c23ed61 --- /dev/null +++ b/requestly-android-core/src/main/java/io/requestly/android/core/modules/sharedPrefViewer/SharedPrefViewerViewModel.kt @@ -0,0 +1,116 @@ +package io.requestly.android.core.modules.sharedPrefViewer + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import io.requestly.android.core.KeyValueStorageManager +import io.requestly.android.core.SharedPrefFileData +import io.requestly.android.core.SharedPrefType + +class SharedPrefViewerViewModel : ViewModel() { + private val CONST_ALL_FILES = "ALL" + + private var _prefEntriesLive = MutableLiveData>() + val prefEntriesLive: LiveData> = _prefEntriesLive + + private var _prefFilesNamesLive = MutableLiveData>() + var prefFilesNamesLive: LiveData> = _prefFilesNamesLive + + var mSelectedFileName = CONST_ALL_FILES + private set + + init { + loadUIData() + } + + fun deleteEntry(fileName: String, keyName: String) { + KeyValueStorageManager.deleteKeyFromFile(keyName, fileName) + loadUIData() + } + + fun fileNameSelected(fileName: String) { + mSelectedFileName = fileName + loadUIData() + } + + private fun loadUIData() { + val entireData = KeyValueStorageManager.fetchDataFromAllSharedPrefFiles() + _prefEntriesLive.value = entireData.filter { + it.fileName == mSelectedFileName || (mSelectedFileName == CONST_ALL_FILES) + } + _prefFilesNamesLive.value = + (entireData.map { it.fileName }.distinct() + CONST_ALL_FILES).reversed() + } + + fun verifyAndSave(newValue: String, prefFileData: SharedPrefFileData): Boolean { + prefFileData.dataType ?: return false + var success = false + when (prefFileData.dataType) { + SharedPrefType.STRING -> { + KeyValueStorageManager.putStringInfile( + newValue, + prefFileData.key, + prefFileData.fileName + ) + success = true + } + SharedPrefType.INTEGER -> { + newValue.toIntOrNull()?.let { + KeyValueStorageManager.putIntegerInfile( + it, + prefFileData.key, + prefFileData.fileName + ) + success = true + } + } + SharedPrefType.DOUBLE -> { + newValue.toDoubleOrNull()?.let { + KeyValueStorageManager.putDoubleInfile( + it, + prefFileData.key, + prefFileData.fileName + ) + success = true + } + } + SharedPrefType.LONG -> { + newValue.toLongOrNull()?.let { + KeyValueStorageManager.putLongInfile( + it, + prefFileData.key, + prefFileData.fileName + ) + success = true + } + } + SharedPrefType.BOOLEAN -> { + newValue.toBooleanStrictOrNull()?.let { + KeyValueStorageManager.putBooleanInfile( + it, + prefFileData.key, + prefFileData.fileName + ) + success = true + } + } + SharedPrefType.STRING_SET -> { + if (newValue[0] == '[' && newValue[newValue.length - 1] == ']') { + val stringSet = newValue + .subSequence(1, newValue.length - 1) + .split(",") + .map { it.trim() } + .toSet() + KeyValueStorageManager.putStringSetInfile( + stringSet, + prefFileData.key, + prefFileData.fileName + ) + success = true + } + } + } + loadUIData() + return success + } +} diff --git a/requestly-android-core/src/main/java/io/requestly/android/core/navigation/Navigator.kt b/requestly-android-core/src/main/java/io/requestly/android/core/navigation/Navigator.kt index 96f44b0..58a1d32 100644 --- a/requestly-android-core/src/main/java/io/requestly/android/core/navigation/Navigator.kt +++ b/requestly-android-core/src/main/java/io/requestly/android/core/navigation/Navigator.kt @@ -1,7 +1,7 @@ package io.requestly.android.core.navigation import androidx.navigation.NavController -import io.requestly.android.core.MainNavGraphDirections +import io.requestly.android.core.RqMainNavGraphDirections class Navigator { lateinit var navController: NavController @@ -9,16 +9,16 @@ class Navigator { // navigate on main thread or nav component crashes sometimes fun navigateToFlow(navigationFlow: NavigationFlow) = when (navigationFlow) { is NavigationFlow.NetworkFlow -> navController.navigate( - MainNavGraphDirections.actionGlobalNetworkFlow() + RqMainNavGraphDirections.actionGlobalNetworkFlow() ) is NavigationFlow.AnalyticsFlow -> navController.navigate( - MainNavGraphDirections.actionGlobalAnalyticsFlow() + RqMainNavGraphDirections.actionGlobalAnalyticsFlow() ) is NavigationFlow.HostSwitcherFlow -> navController.navigate( - MainNavGraphDirections.actionGlobalHostSwitcher() + RqMainNavGraphDirections.actionGlobalHostSwitcher() ) // is NavigationFlow.DashboardFlow -> navController.navigate( -// MainNavGraphDirections.actionGlobalDashboardFlow(navigationFlow.msg) +// RqMainNavGraphDirections.actionGlobalDashboardFlow(navigationFlow.msg) // ) } } diff --git a/requestly-android-core/src/main/res/drawable/edit_text_box.xml b/requestly-android-core/src/main/res/drawable/edit_text_box.xml new file mode 100644 index 0000000..bd354d2 --- /dev/null +++ b/requestly-android-core/src/main/res/drawable/edit_text_box.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/requestly-android-core/src/main/res/drawable/edit_text_box_alert.xml b/requestly-android-core/src/main/res/drawable/edit_text_box_alert.xml new file mode 100644 index 0000000..c2d70de --- /dev/null +++ b/requestly-android-core/src/main/res/drawable/edit_text_box_alert.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/requestly-android-core/src/main/res/drawable/ic_baseline_add_24.xml b/requestly-android-core/src/main/res/drawable/ic_baseline_add_24.xml new file mode 100644 index 0000000..70046c4 --- /dev/null +++ b/requestly-android-core/src/main/res/drawable/ic_baseline_add_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/requestly-android-core/src/main/res/drawable/ic_baseline_storage_24.xml b/requestly-android-core/src/main/res/drawable/ic_baseline_storage_24.xml new file mode 100644 index 0000000..c8faeb2 --- /dev/null +++ b/requestly-android-core/src/main/res/drawable/ic_baseline_storage_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/requestly-android-core/src/main/res/layout/activity_main_requestly.xml b/requestly-android-core/src/main/res/layout/activity_main_requestly.xml index ea453bd..fc589ca 100644 --- a/requestly-android-core/src/main/res/layout/activity_main_requestly.xml +++ b/requestly-android-core/src/main/res/layout/activity_main_requestly.xml @@ -14,7 +14,7 @@ app:defaultNavHost="true" app:layout_constraintBottom_toTopOf="@id/updateNotifyStripView" app:layout_constraintTop_toTopOf="parent" - app:navGraph="@navigation/main_nav_graph" /> + app:navGraph="@navigation/rq_main_nav_graph" /> + + + + + + + + + diff --git a/requestly-android-core/src/main/res/layout/host_switcher_item.xml b/requestly-android-core/src/main/res/layout/host_switcher_item.xml index 20b455b..bb514ee 100644 --- a/requestly-android-core/src/main/res/layout/host_switcher_item.xml +++ b/requestly-android-core/src/main/res/layout/host_switcher_item.xml @@ -9,9 +9,11 @@ android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="16dp" + android:layout_marginStart="8dp" android:text="Replaces" - android:textSize="12sp" + android:textAllCaps="true" + android:textColor="#73777B" + android:textSize="14sp" app:layout_constraintBottom_toBottomOf="@+id/startingTextView" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/startingTextView" /> @@ -20,9 +22,12 @@ android:id="@+id/deleteButton" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginEnd="16dp" - android:textSize="16dp" + android:layout_marginEnd="8dp" android:text="Delete" + android:textAllCaps="true" + android:textColor="#0000EE" + android:textSize="16sp" + android:textStyle="bold" app:layout_constraintBottom_toBottomOf="@+id/editButton" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@+id/editButton" /> @@ -32,8 +37,11 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="24dp" - android:textSize="16dp" android:text="Edit" + android:textAllCaps="true" + android:textColor="#0000EE" + android:textSize="16sp" + android:textStyle="bold" app:layout_constraintBottom_toBottomOf="@+id/activeSwitch" app:layout_constraintEnd_toStartOf="@+id/deleteButton" app:layout_constraintTop_toTopOf="@+id/activeSwitch" /> @@ -42,11 +50,14 @@ android:id="@+id/activeSwitch" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="16dp" + android:layout_marginStart="8dp" android:layout_marginTop="24dp" android:layout_marginBottom="16dp" android:minHeight="48dp" android:text="Active" + android:textAllCaps="true" + android:textColor="#73777B" + android:textStyle="bold" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/provisionalTextView" /> @@ -55,9 +66,11 @@ android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="16dp" + android:layout_marginStart="8dp" android:text="With" - android:textSize="12sp" + android:textAllCaps="true" + android:textColor="#73777B" + android:textSize="14sp" app:layout_constraintBottom_toBottomOf="@+id/provisionalTextView" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/provisionalTextView" /> @@ -68,10 +81,12 @@ android:layout_height="wrap_content" android:layout_marginStart="24dp" android:layout_marginTop="16dp" - android:layout_marginEnd="16dp" + android:layout_marginEnd="8dp" android:singleLine="false" android:text="TextView" + android:textColor="@color/black" android:textSize="18sp" + android:textStyle="italic" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/textView" app:layout_constraintTop_toTopOf="parent" /> @@ -81,11 +96,22 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="16dp" + android:layout_marginEnd="8dp" android:singleLine="false" android:text="TextView" + android:textColor="@color/black" android:textSize="18sp" + android:textStyle="italic" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="@+id/startingTextView" app:layout_constraintTop_toBottomOf="@+id/startingTextView" /> + + + + diff --git a/requestly-android-core/src/main/res/layout/shared_pref_edit_value_view.xml b/requestly-android-core/src/main/res/layout/shared_pref_edit_value_view.xml new file mode 100644 index 0000000..ed110a1 --- /dev/null +++ b/requestly-android-core/src/main/res/layout/shared_pref_edit_value_view.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + diff --git a/requestly-android-core/src/main/res/layout/shared_pref_line_item.xml b/requestly-android-core/src/main/res/layout/shared_pref_line_item.xml new file mode 100644 index 0000000..2546673 --- /dev/null +++ b/requestly-android-core/src/main/res/layout/shared_pref_line_item.xml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/requestly-android-core/src/main/res/menu/bottom_nav_menu.xml b/requestly-android-core/src/main/res/menu/bottom_nav_menu.xml index 03d63f5..ce49fb7 100644 --- a/requestly-android-core/src/main/res/menu/bottom_nav_menu.xml +++ b/requestly-android-core/src/main/res/menu/bottom_nav_menu.xml @@ -20,4 +20,9 @@ android:id="@id/logs_flow" android:icon="@drawable/ic_console_24" android:title="Logs" /> + + diff --git a/requestly-android-core/src/main/res/menu/host_switcher_fragment_menu.xml b/requestly-android-core/src/main/res/menu/host_switcher_fragment_menu.xml index dea07fb..1ffc95d 100644 --- a/requestly-android-core/src/main/res/menu/host_switcher_fragment_menu.xml +++ b/requestly-android-core/src/main/res/menu/host_switcher_fragment_menu.xml @@ -3,7 +3,7 @@ xmlns:android="http://schemas.android.com/apk/res/android"> diff --git a/requestly-android-core/src/main/res/navigation/main_nav_graph.xml b/requestly-android-core/src/main/res/navigation/rq_main_nav_graph.xml similarity index 76% rename from requestly-android-core/src/main/res/navigation/main_nav_graph.xml rename to requestly-android-core/src/main/res/navigation/rq_main_nav_graph.xml index 0f14cb7..1c14e73 100644 --- a/requestly-android-core/src/main/res/navigation/main_nav_graph.xml +++ b/requestly-android-core/src/main/res/navigation/rq_main_nav_graph.xml @@ -1,27 +1,28 @@ + + app:popUpTo="@id/rq_main_nav_graph" /> + app:popUpTo="@id/rq_main_nav_graph" /> + app:popUpTo="@id/rq_main_nav_graph" /> diff --git a/requestly-android-core/src/main/res/navigation/shared_pref_viewer_graph.xml b/requestly-android-core/src/main/res/navigation/shared_pref_viewer_graph.xml new file mode 100644 index 0000000..a580f34 --- /dev/null +++ b/requestly-android-core/src/main/res/navigation/shared_pref_viewer_graph.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/requestly-android-core/src/main/res/values/nav_ids.xml b/requestly-android-core/src/main/res/values/nav_ids.xml index bd6e4bd..c3e0691 100644 --- a/requestly-android-core/src/main/res/values/nav_ids.xml +++ b/requestly-android-core/src/main/res/values/nav_ids.xml @@ -4,9 +4,11 @@ + + diff --git a/requestly-android-core/src/main/res/values/strings.xml b/requestly-android-core/src/main/res/values/strings.xml index 1680082..b1879b8 100644 --- a/requestly-android-core/src/main/res/values/strings.xml +++ b/requestly-android-core/src/main/res/values/strings.xml @@ -9,4 +9,6 @@ Clear Add New Logcat filter + + Hello blank fragment diff --git a/requestly-android-noop/build.gradle b/requestly-android-noop/build.gradle index 45aec10..22e7dce 100644 --- a/requestly-android-noop/build.gradle +++ b/requestly-android-noop/build.gradle @@ -10,6 +10,14 @@ android { minSdk rootProject.minSdkVersion targetSdk rootProject.targetSdkVersion } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } } dependencies { diff --git a/requestly-android-okhttp-noop/build.gradle b/requestly-android-okhttp-noop/build.gradle index b0daebb..7dcb224 100644 --- a/requestly-android-okhttp-noop/build.gradle +++ b/requestly-android-okhttp-noop/build.gradle @@ -9,6 +9,12 @@ android { '-module-name', "io.requestly.android.okhttp", "-Xexplicit-api=strict" ] + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = "1.8" } buildFeatures { diff --git a/requestly-android-okhttp/build.gradle b/requestly-android-okhttp/build.gradle index 5309cea..9129f6a 100644 --- a/requestly-android-okhttp/build.gradle +++ b/requestly-android-okhttp/build.gradle @@ -11,6 +11,8 @@ android { '-module-name', "io.requestly.android.okhttp", "-Xexplicit-api=strict" ] + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } defaultConfig { diff --git a/requestly-android-okhttp/src/main/kotlin/io/requestly/android/okhttp/internal/support/RequestProcessor.kt b/requestly-android-okhttp/src/main/kotlin/io/requestly/android/okhttp/internal/support/RequestProcessor.kt index 633c13b..5dd8f2e 100644 --- a/requestly-android-okhttp/src/main/kotlin/io/requestly/android/okhttp/internal/support/RequestProcessor.kt +++ b/requestly-android-okhttp/src/main/kotlin/io/requestly/android/okhttp/internal/support/RequestProcessor.kt @@ -31,8 +31,28 @@ internal class RequestProcessor( ) { private val switchingRulesMap = HashMap() - - init { + private var isStorageListenerInitDone = false + + // NOT WORKING with Hilt +// init { +// val typeToken = object : TypeToken>() {} +// val storageChangeListener: () -> Unit = { +// switchingRulesMap.clear() +// KeyValueStorageManager.getList(HostSwitcherFragmentViewModel.KEY_NAME, typeToken) +// ?.forEach { +// if (it.isActive) { +// switchingRulesMap[it.startingText] = it.provisionalText +// } +// } +// } +// storageChangeListener() +// KeyValueStorageManager.registerChangeListener( +// HostSwitcherFragmentViewModel.KEY_NAME, +// storageChangeListener +// ) +// } + + private fun initStorageListener() { val typeToken = object : TypeToken>() {} val storageChangeListener: () -> Unit = { switchingRulesMap.clear() @@ -48,9 +68,14 @@ internal class RequestProcessor( HostSwitcherFragmentViewModel.KEY_NAME, storageChangeListener ) + isStorageListenerInitDone = true } fun process(req: Request, transaction: HttpTransaction): Request { + if(!isStorageListenerInitDone) { + initStorageListener() + } + var urlString = req.url.toString() switchingRulesMap.forEach { urlString = urlString.replace(it.key, it.value, ignoreCase = true) diff --git a/requestly-android/build.gradle b/requestly-android/build.gradle index eb4d686..33faaa5 100644 --- a/requestly-android/build.gradle +++ b/requestly-android/build.gradle @@ -10,11 +10,20 @@ android { minSdk rootProject.minSdkVersion targetSdk rootProject.targetSdkVersion } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } } dependencies { api project(':requestly-android-core') api project(':requestly-android-event') + api project(':requestly-android-okhttp') } apply from: rootProject.file('gradle/gradle-mvn-push.gradle') diff --git a/sample/build.gradle b/sample/build.gradle index a36647e..e994e06 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -63,9 +63,6 @@ android { } dependencies { - debugImplementation project(':requestly-android-okhttp') - releaseImplementation project(':requestly-android-okhttp-noop') - debugImplementation project(':requestly-android') releaseImplementation project(':requestly-android-noop') diff --git a/sample/src/main/kotlin/io/requestly/android/sample/MainActivity.kt b/sample/src/main/kotlin/io/requestly/android/sample/MainActivity.kt index 7a7e5dc..e27cb6b 100644 --- a/sample/src/main/kotlin/io/requestly/android/sample/MainActivity.kt +++ b/sample/src/main/kotlin/io/requestly/android/sample/MainActivity.kt @@ -7,9 +7,9 @@ import android.text.method.LinkMovementMethod import android.view.View import androidx.appcompat.app.AppCompatActivity import io.requestly.android.event.api.RequestlyEvent +import io.requestly.android.okhttp.api.RQ import io.requestly.android.okhttp.api.RQ.getLaunchIntent import io.requestly.android.sample.databinding.ActivityMainSampleBinding -import io.requestly.android.okhttp.api.RQ private val interceptorTypeSelector = InterceptorTypeSelector() @@ -100,6 +100,22 @@ class MainActivity : AppCompatActivity() { .penaltyDeath() .build() ) + + val spFile1 = getSharedPreferences("com.sample.shared_pref_file_1", 0) + with(spFile1.edit()) { + this.putBoolean("bool_value", true) + this.putString("string_value", "Hello World") + this.putFloat("float_value", 3.14F) + this.putInt("integer_value", 100) + this.putLong("long_value", 1000000000000L) + this.commit() + } + + val spFile2 = getSharedPreferences("com.sample.shared_pref_file_2", 0) + with(spFile2.edit()) { + this.putStringSet("string_set_value", setOf("One", "Two", "Three", "Four")) + this.commit() + } }