diff --git a/app/build.gradle b/app/build.gradle index a459efe..bb5b985 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,6 +27,7 @@ dependencies { implementation 'androidx.appcompat:appcompat:' + versions.androidxCore implementation 'androidx.recyclerview:recyclerview:' + versions.androidxRecyclerView implementation 'com.google.android.material:material:' + versions.googleMaterial + implementation 'androidx.browser:browser:' + versions.androidxBrowser kapt 'androidx.lifecycle:lifecycle-compiler:' + versions.lifecycle diff --git a/app/src/main/java/com/afollestad/nocknock/AppExt.kt b/app/src/main/java/com/afollestad/nocknock/AppExt.kt index b1c22cf..4f185e8 100644 --- a/app/src/main/java/com/afollestad/nocknock/AppExt.kt +++ b/app/src/main/java/com/afollestad/nocknock/AppExt.kt @@ -18,9 +18,18 @@ package com.afollestad.nocknock import android.app.Activity import android.app.Application import android.app.Application.ActivityLifecycleCallbacks +import android.content.ActivityNotFoundException +import android.content.Intent +import android.net.Uri import android.os.Bundle +import androidx.annotation.AttrRes +import androidx.annotation.ColorRes +import androidx.browser.customtabs.CustomTabsIntent +import androidx.core.content.ContextCompat import androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY import androidx.core.text.HtmlCompat.fromHtml +import com.afollestad.materialdialogs.utils.MDUtil.resolveColor +import com.afollestad.nocknock.utilities.ui.toast typealias ActivityLifeChange = (activity: Activity, resumed: Boolean) -> Unit @@ -50,3 +59,53 @@ fun Application.onActivityLifeChange(cb: ActivityLifeChange) { } fun String.toHtml() = fromHtml(this, FROM_HTML_MODE_LEGACY) + +fun String.toUri() = Uri.parse(this)!! + +fun Activity.setStatusBarColor( + @ColorRes res: Int? = null, + @AttrRes attr: Int? = null +) { + require(res != null || attr != null) { "Must specify at least one arg." } + if (res != null) { + val color = ContextCompat.getColor(this, res) + window.statusBarColor = color + } else if (attr != null) { + val color = resolveColor(this, attr = attr) + window.statusBarColor = color + } +} + +fun Activity.viewUrl(url: String) { + val customTabsIntent = CustomTabsIntent.Builder() + .apply { + setToolbarColor(resolveColor(this@viewUrl, attr = R.attr.colorPrimary)) + } + .build() + try { + customTabsIntent.launchUrl(this, url.toUri()) + } catch (_: ActivityNotFoundException) { + toast(R.string.install_web_browser) + } +} + +fun Activity.viewUrlWithApp( + url: String, + pkg: String +) { + val intent = Intent(Intent.ACTION_VIEW).apply { + data = url.toUri() + } + val resInfo = packageManager.queryIntentActivities(intent, 0) + for (info in resInfo) { + if (info.activityInfo.packageName.toLowerCase().contains(pkg) || + info.activityInfo.name.toLowerCase().contains(pkg) + ) { + startActivity(intent.apply { + setPackage(info.activityInfo.packageName) + }) + return + } + } + viewUrl(url) +} diff --git a/app/src/main/java/com/afollestad/nocknock/NockNockApp.kt b/app/src/main/java/com/afollestad/nocknock/NockNockApp.kt index 9cc5a8e..5df44c2 100644 --- a/app/src/main/java/com/afollestad/nocknock/NockNockApp.kt +++ b/app/src/main/java/com/afollestad/nocknock/NockNockApp.kt @@ -20,6 +20,7 @@ package com.afollestad.nocknock import android.app.Application import com.afollestad.nocknock.engine.engineModule import com.afollestad.nocknock.koin.mainModule +import com.afollestad.nocknock.koin.prefModule import com.afollestad.nocknock.koin.viewModelModule import com.afollestad.nocknock.notifications.NockNotificationManager import com.afollestad.nocknock.notifications.notificationsModule @@ -42,6 +43,7 @@ class NockNockApp : Application() { } val modules = listOf( + prefModule, mainModule, engineModule, commonModule, diff --git a/app/src/main/java/com/afollestad/nocknock/adapter/SiteAdapter.kt b/app/src/main/java/com/afollestad/nocknock/adapter/SiteAdapter.kt index 4e061c9..e1b034c 100644 --- a/app/src/main/java/com/afollestad/nocknock/adapter/SiteAdapter.kt +++ b/app/src/main/java/com/afollestad/nocknock/adapter/SiteAdapter.kt @@ -37,7 +37,7 @@ typealias Listener = (model: Site, longClick: Boolean) -> Unit /** @author Aidan Follestad (@afollestad) */ class SiteViewHolder constructor( itemView: View, - private val adapter: ServerAdapter + private val adapter: SiteAdapter ) : RecyclerView.ViewHolder(itemView), View.OnLongClickListener { init { @@ -94,7 +94,7 @@ class SiteViewHolder constructor( } /** @author Aidan Follestad (@afollestad) */ -class ServerAdapter(private val listener: Listener) : RecyclerView.Adapter() { +class SiteAdapter(private val listener: Listener) : RecyclerView.Adapter() { private var models = mutableListOf() diff --git a/app/src/main/java/com/afollestad/nocknock/broadcasts/StatusUpdateIntentReceiver.kt b/app/src/main/java/com/afollestad/nocknock/broadcasts/StatusUpdateIntentReceiver.kt index c5567c5..ef8a30a 100644 --- a/app/src/main/java/com/afollestad/nocknock/broadcasts/StatusUpdateIntentReceiver.kt +++ b/app/src/main/java/com/afollestad/nocknock/broadcasts/StatusUpdateIntentReceiver.kt @@ -37,7 +37,7 @@ class StatusUpdateIntentReceiver( private var callback: SiteCallback? ) : LifecycleObserver { - internal val intentReceiver = object : BroadcastReceiver() { + private val intentReceiver = object : BroadcastReceiver() { override fun onReceive( context: Context, intent: Intent diff --git a/app/src/main/java/com/afollestad/nocknock/koin/MainPrefModule.kt b/app/src/main/java/com/afollestad/nocknock/koin/MainPrefModule.kt new file mode 100644 index 0000000..654b76d --- /dev/null +++ b/app/src/main/java/com/afollestad/nocknock/koin/MainPrefModule.kt @@ -0,0 +1,32 @@ +/** + * Designed and developed by Aidan Follestad (@afollestad) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.afollestad.nocknock.koin + +import com.afollestad.rxkprefs.RxkPrefs +import com.afollestad.rxkprefs.rxkPrefs +import org.koin.dsl.module.module + +const val PREF_DARK_MODE = "dark_mode" + +/** @author Aidan Follestad (@afollestad) */ +val prefModule = module { + + single { rxkPrefs(get(), "settings") } + + factory(name = PREF_DARK_MODE) { + get().boolean(PREF_DARK_MODE, false) + } +} diff --git a/app/src/main/java/com/afollestad/nocknock/ui/DarkModeSwitchActivity.kt b/app/src/main/java/com/afollestad/nocknock/ui/DarkModeSwitchActivity.kt new file mode 100644 index 0000000..dbdedad --- /dev/null +++ b/app/src/main/java/com/afollestad/nocknock/ui/DarkModeSwitchActivity.kt @@ -0,0 +1,56 @@ +/** + * Designed and developed by Aidan Follestad (@afollestad) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.afollestad.nocknock.ui + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.afollestad.nocknock.R +import com.afollestad.nocknock.koin.PREF_DARK_MODE +import com.afollestad.nocknock.utilities.rx.attachLifecycle +import com.afollestad.rxkprefs.Pref +import org.koin.android.ext.android.inject +import timber.log.Timber.d as log + +/** @author Aidan Follestad (afollestad) */ +abstract class DarkModeSwitchActivity : AppCompatActivity() { + + private var isDarkModeEnabled: Boolean = false + private val darkModePref by inject>(name = PREF_DARK_MODE) + + override fun onCreate(savedInstanceState: Bundle?) { + isDarkModeEnabled = darkModePref.get() + setTheme(themeRes()) + super.onCreate(savedInstanceState) + + darkModePref.observe() + .filter { it != isDarkModeEnabled } + .subscribe { + log("Theme changed, recreating Activity.") + recreate() + } + .attachLifecycle(this) + } + + override fun onResume() { + super.onResume() + } + + private fun themeRes() = if (darkModePref.get()) { + R.style.AppTheme_Dark + } else { + R.style.AppTheme + } +} diff --git a/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSiteActivity.kt b/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSiteActivity.kt index 9e91397..78ff9fa 100644 --- a/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSiteActivity.kt +++ b/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSiteActivity.kt @@ -21,6 +21,7 @@ import android.widget.ArrayAdapter import androidx.appcompat.app.AppCompatActivity import com.afollestad.nocknock.R import com.afollestad.nocknock.data.model.ValidationMode +import com.afollestad.nocknock.setStatusBarColor import com.afollestad.nocknock.viewcomponents.ext.conceal import com.afollestad.nocknock.viewcomponents.ext.onLayout import com.afollestad.nocknock.viewcomponents.livedata.attachLiveData @@ -38,11 +39,12 @@ import kotlinx.android.synthetic.main.activity_addsite.responseValidationSearchT import kotlinx.android.synthetic.main.activity_addsite.rootView import kotlinx.android.synthetic.main.activity_addsite.scriptInputLayout import kotlinx.android.synthetic.main.activity_addsite.textUrlWarning -import kotlinx.android.synthetic.main.activity_addsite.toolbar import kotlinx.android.synthetic.main.activity_addsite.validationModeDescription +import kotlinx.android.synthetic.main.include_app_bar.toolbar import org.koin.androidx.viewmodel.ext.android.viewModel import kotlin.math.max import kotlin.properties.Delegates.notNull +import kotlinx.android.synthetic.main.include_app_bar.toolbar_title as toolbarTitle const val KEY_FAB_X = "fab_x" const val KEY_FAB_Y = "fab_y" @@ -62,6 +64,7 @@ class AddSiteActivity : AppCompatActivity() { @SuppressLint("SetTextI18n") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setStatusBarColor(res = R.color.inkColorDark) setContentView(R.layout.activity_addsite) setupUi(savedInstanceState) @@ -133,7 +136,11 @@ class AddSiteActivity : AppCompatActivity() { } private fun setupUi(savedInstanceState: Bundle?) { - toolbar.setNavigationOnClickListener { closeActivityWithReveal() } + toolbarTitle.setText(R.string.add_site) + toolbar.run { + setNavigationIcon(R.drawable.ic_action_close) + setNavigationOnClickListener { closeActivityWithReveal() } + } if (savedInstanceState == null) { rootView.conceal() diff --git a/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSiteActivityExt.kt b/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSiteActivityExt.kt index 4f1f9e6..04e62c7 100644 --- a/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSiteActivityExt.kt +++ b/app/src/main/java/com/afollestad/nocknock/ui/addsite/AddSiteActivityExt.kt @@ -18,6 +18,8 @@ package com.afollestad.nocknock.ui.addsite import android.view.ViewAnimationUtils.createCircularReveal import android.view.animation.AccelerateInterpolator import android.view.animation.DecelerateInterpolator +import com.afollestad.nocknock.R +import com.afollestad.nocknock.setStatusBarColor import com.afollestad.nocknock.utilities.ext.onEnd import com.afollestad.nocknock.viewcomponents.ext.conceal import com.afollestad.nocknock.viewcomponents.ext.show @@ -40,6 +42,7 @@ internal fun AddSiteActivity.closeActivityWithReveal() { if (isClosing) { return } + setStatusBarColor(attr = R.attr.colorPrimary) isClosing = true createCircularReveal(rootView, revealCx, revealCy, revealRadius, 0f) .apply { diff --git a/app/src/main/java/com/afollestad/nocknock/ui/main/MainActivity.kt b/app/src/main/java/com/afollestad/nocknock/ui/main/MainActivity.kt index 0c9bc7d..17e9341 100644 --- a/app/src/main/java/com/afollestad/nocknock/ui/main/MainActivity.kt +++ b/app/src/main/java/com/afollestad/nocknock/ui/main/MainActivity.kt @@ -17,38 +17,45 @@ package com.afollestad.nocknock.ui.main import android.content.Intent import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.Observer import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration.VERTICAL import androidx.recyclerview.widget.LinearLayoutManager import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.list.listItems +import com.afollestad.materialdialogs.list.listItemsSingleChoice import com.afollestad.nocknock.R -import com.afollestad.nocknock.adapter.ServerAdapter +import com.afollestad.nocknock.adapter.SiteAdapter import com.afollestad.nocknock.broadcasts.StatusUpdateIntentReceiver import com.afollestad.nocknock.data.model.Site import com.afollestad.nocknock.dialogs.AboutDialog +import com.afollestad.nocknock.koin.PREF_DARK_MODE import com.afollestad.nocknock.notifications.NockNotificationManager +import com.afollestad.nocknock.ui.DarkModeSwitchActivity import com.afollestad.nocknock.utilities.providers.IntentProvider +import com.afollestad.nocknock.utilities.ui.toast +import com.afollestad.nocknock.viewUrl +import com.afollestad.nocknock.viewUrlWithApp import com.afollestad.nocknock.viewcomponents.ext.showOrHide +import com.afollestad.rxkprefs.Pref import kotlinx.android.synthetic.main.activity_main.fab import kotlinx.android.synthetic.main.activity_main.list import kotlinx.android.synthetic.main.activity_main.loadingProgress -import kotlinx.android.synthetic.main.activity_main.toolbar +import kotlinx.android.synthetic.main.include_app_bar.toolbar import kotlinx.android.synthetic.main.include_empty_view.emptyText import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel /** @author Aidan Follestad (@afollestad) */ -class MainActivity : AppCompatActivity() { +class MainActivity : DarkModeSwitchActivity() { private val notificationManager by inject() private val intentProvider by inject() + private val darkModePref by inject>(name = PREF_DARK_MODE) internal val viewModel by viewModel() - private lateinit var adapter: ServerAdapter + private lateinit var siteAdapter: SiteAdapter private val statusUpdateReceiver by lazy { StatusUpdateIntentReceiver(application, intentProvider) { @@ -70,7 +77,7 @@ class MainActivity : AppCompatActivity() { viewModel.onSites() .observe(this, Observer { - adapter.set(it) + siteAdapter.set(it) emptyText.showOrHide(it.isEmpty()) }) loadingProgress.observe(this, viewModel.onIsLoading()) @@ -79,20 +86,27 @@ class MainActivity : AppCompatActivity() { } private fun setupUi() { - toolbar.inflateMenu(R.menu.menu_main) - toolbar.setOnMenuItemClickListener { item -> - if (item.itemId == R.id.about) { - AboutDialog.show(this) + toolbar.run { + inflateMenu(R.menu.menu_main) + menu.findItem(R.id.dark_mode) + .isChecked = darkModePref.get() + setOnMenuItemClickListener { item -> + when (item.itemId) { + R.id.about -> AboutDialog.show(this@MainActivity) + R.id.dark_mode -> darkModePref.set(!darkModePref.get()) + R.id.support_me -> supportMe() + } + return@setOnMenuItemClickListener true } - return@setOnMenuItemClickListener true } - adapter = ServerAdapter(this::onSiteSelected) - - list.layoutManager = LinearLayoutManager(this) - list.adapter = adapter - list.addItemDecoration(DividerItemDecoration(this, VERTICAL)) + siteAdapter = SiteAdapter(this::onSiteSelected) + list.run { + layoutManager = LinearLayoutManager(this@MainActivity) + adapter = siteAdapter + addItemDecoration(DividerItemDecoration(this@MainActivity, VERTICAL)) + } fab.setOnClickListener { addSite() } } @@ -119,4 +133,20 @@ class MainActivity : AppCompatActivity() { viewSite(model) } } + + private fun supportMe() { + MaterialDialog(this).show { + title(R.string.support_me) + message(R.string.support_me_message, html = true, lineHeightMultiplier = 1.4f) + listItemsSingleChoice(R.array.donation_options) { _, index, _ -> + when (index) { + 0 -> viewUrl("https://paypal.me/AidanFollestad") + 1 -> viewUrlWithApp("https://cash.me/\$afollestad", pkg = "com.squareup.cash") + 2 -> viewUrlWithApp("https://venmo.com/afollestad", pkg = "com.venmo") + } + toast(R.string.thank_you) + } + positiveButton(R.string.next) + } + } } diff --git a/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteActivity.kt b/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteActivity.kt index 8f82296..6b233f0 100644 --- a/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteActivity.kt +++ b/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteActivity.kt @@ -25,10 +25,11 @@ import com.afollestad.nocknock.R import com.afollestad.nocknock.broadcasts.StatusUpdateIntentReceiver import com.afollestad.nocknock.data.model.Site import com.afollestad.nocknock.data.model.ValidationMode +import com.afollestad.nocknock.setStatusBarColor import com.afollestad.nocknock.utilities.providers.IntentProvider -import com.afollestad.nocknock.viewcomponents.livedata.attachLiveData import com.afollestad.nocknock.viewcomponents.ext.dimenFloat import com.afollestad.nocknock.viewcomponents.ext.onScroll +import com.afollestad.nocknock.viewcomponents.livedata.attachLiveData import com.afollestad.nocknock.viewcomponents.livedata.toViewError import com.afollestad.nocknock.viewcomponents.livedata.toViewText import com.afollestad.nocknock.viewcomponents.livedata.toViewVisibility @@ -47,10 +48,11 @@ import kotlinx.android.synthetic.main.activity_viewsite.scrollView import kotlinx.android.synthetic.main.activity_viewsite.textLastCheckResult import kotlinx.android.synthetic.main.activity_viewsite.textNextCheck import kotlinx.android.synthetic.main.activity_viewsite.textUrlWarning -import kotlinx.android.synthetic.main.activity_viewsite.toolbar import kotlinx.android.synthetic.main.activity_viewsite.validationModeDescription +import kotlinx.android.synthetic.main.include_app_bar.toolbar import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel +import kotlinx.android.synthetic.main.include_app_bar.toolbar_title as toolbarTitle /** @author Aidan Follestad (@afollestad) */ class ViewSiteActivity : AppCompatActivity() { @@ -67,6 +69,7 @@ class ViewSiteActivity : AppCompatActivity() { @SuppressLint("SetTextI18n") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setStatusBarColor(res = R.color.inkColorDark) setContentView(R.layout.activity_viewsite) setupUi() @@ -156,7 +159,9 @@ class ViewSiteActivity : AppCompatActivity() { } private fun setupUi() { + toolbarTitle.setText(R.string.add_site) toolbar.run { + setNavigationIcon(R.drawable.ic_action_close) setNavigationOnClickListener { finish() } inflateMenu(R.menu.menu_viewsite) menu.findItem(R.id.refresh) diff --git a/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteActivityExt.kt b/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteActivityExt.kt index 2504966..ab0a6ea 100644 --- a/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteActivityExt.kt +++ b/app/src/main/java/com/afollestad/nocknock/ui/viewsite/ViewSiteActivityExt.kt @@ -22,7 +22,7 @@ import com.afollestad.nocknock.data.model.Status import com.afollestad.nocknock.data.model.isPending import com.afollestad.nocknock.toHtml import com.afollestad.nocknock.utilities.ext.animateRotation -import kotlinx.android.synthetic.main.activity_viewsite.toolbar +import kotlinx.android.synthetic.main.include_app_bar.toolbar const val KEY_SITE = "site_model" diff --git a/app/src/main/res/drawable/divider.xml b/app/src/main/res/drawable/divider.xml index 9be83b4..7561a3c 100644 --- a/app/src/main/res/drawable/divider.xml +++ b/app/src/main/res/drawable/divider.xml @@ -1,4 +1,4 @@ - + diff --git a/app/src/main/res/layout/activity_addsite.xml b/app/src/main/res/layout/activity_addsite.xml index 8567019..c02c176 100644 --- a/app/src/main/res/layout/activity_addsite.xml +++ b/app/src/main/res/layout/activity_addsite.xml @@ -1,7 +1,6 @@ - + @@ -157,7 +148,7 @@ android:layout_height="wrap_content" android:layout_marginBottom="@dimen/content_inset" android:layout_marginTop="@dimen/content_inset_half" - android:background="@color/colorPrimaryDark" + android:background="@color/inkColorDark" /> - + - - - + + + + + + + + diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index c73e1ba..b3b1727 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -3,4 +3,11 @@ + + diff --git a/app/src/main/res/values-v21/styles_parent.xml b/app/src/main/res/values-v21/styles_parent.xml new file mode 100644 index 0000000..5015af9 --- /dev/null +++ b/app/src/main/res/values-v21/styles_parent.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/app/src/main/res/values-v23/styles.xml b/app/src/main/res/values-v23/styles.xml new file mode 100644 index 0000000..c1a8339 --- /dev/null +++ b/app/src/main/res/values-v23/styles.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/app/src/main/res/values-v27/styles.xml b/app/src/main/res/values-v27/styles.xml new file mode 100644 index 0000000..c4efc7a --- /dev/null +++ b/app/src/main/res/values-v27/styles.xml @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 1927c7b..d4e6453 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -12,4 +12,10 @@ JavaScript Evaluation + + via PayPal + via Cash App + via Venmo + + diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 0000000..0df2f69 --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 4e7d9b6..778e854 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,10 +1,14 @@ - #455A64 - #37474F + #FFFFFF + #F5F5F5 + + #212121 + #212121 + + #455A64 + #37474F #FF6E40 - #EEEEEE - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d4be0d7..be8314b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,7 +13,8 @@ LinkedIn

Nock Nock is open source! Check out the GitHub page!
Icon by Kevin Aguilar of 221 Pixels. - ]]> + ]]> + Dark Mode Dismiss Add Site @@ -74,4 +75,15 @@ exception to pass custom error messages to Nock Nock. + Donate + MNML was created and is maintained by one person. Donations are much + appreciated and encourage continued support. + ]]> + Thank you very much! + Next + + Please install a video viewer app, such as Google Photos. + Please install a web browser app, such as Google Chrome. + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index d1319a1..8c4dce4 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,27 +1,14 @@ - - - - - - - - - diff --git a/app/src/main/res/values/styles_parents.xml b/app/src/main/res/values/styles_parents.xml new file mode 100644 index 0000000..8dfaaea --- /dev/null +++ b/app/src/main/res/values/styles_parents.xml @@ -0,0 +1,48 @@ + + + + + + + + + diff --git a/common/build.gradle b/common/build.gradle index 59634ca..8472928 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -29,6 +29,8 @@ dependencies { implementation 'org.koin:koin-android:' + versions.koin implementation 'org.mozilla:rhino:' + versions.rhino + api 'com.afollestad:rxkprefs:' + versions.rxkPrefs + testImplementation 'junit:junit:' + versions.junit testImplementation 'com.google.truth:truth:' + versions.truth testImplementation 'androidx.arch.core:core-testing:' + versions.archTesting diff --git a/common/src/main/java/com/afollestad/nocknock/utilities/rx/RxLifecycleExt.kt b/common/src/main/java/com/afollestad/nocknock/utilities/rx/RxLifecycleExt.kt new file mode 100644 index 0000000..a306aad --- /dev/null +++ b/common/src/main/java/com/afollestad/nocknock/utilities/rx/RxLifecycleExt.kt @@ -0,0 +1,58 @@ +/** + * Designed and developed by Aidan Follestad (@afollestad) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@file:Suppress("unused") + +package com.afollestad.nocknock.utilities.rx + +import androidx.lifecycle.Lifecycle.Event.ON_DESTROY +import androidx.lifecycle.Lifecycle.State.DESTROYED +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.OnLifecycleEvent +import io.reactivex.disposables.Disposable + +/** @author Aidan Follestad (afollestad) */ +class LifecycleAwareDisposable( + private val disposable: Disposable +) : LifecycleObserver { + + @OnLifecycleEvent(ON_DESTROY) + fun dispose() = disposable.dispose() +} + +/** + * Wraps [disposable] so that it is disposed of when the receiving [LifecycleOwner] + * is destroyed. + * + * @author Aidan Follestad (afollestad) + */ +fun LifecycleOwner.ownRx(disposable: Disposable) { + if (this.lifecycle.currentState == DESTROYED) { + disposable.dispose() + return + } + this.lifecycle.addObserver(LifecycleAwareDisposable(disposable)) +} + +/** + * Attaches the receiving [Disposable] so that it is disposed of when [lifecycleOwner] + * is destroyed. + * + * @author Aidan Follestad (afollestad) + */ +fun Disposable.attachLifecycle(lifecycleOwner: LifecycleOwner) { + lifecycleOwner.ownRx(this) +} diff --git a/common/src/main/java/com/afollestad/nocknock/utilities/ui/ContextExt.kt b/common/src/main/java/com/afollestad/nocknock/utilities/ui/ContextExt.kt new file mode 100644 index 0000000..5b09b3d --- /dev/null +++ b/common/src/main/java/com/afollestad/nocknock/utilities/ui/ContextExt.kt @@ -0,0 +1,30 @@ +/** + * Designed and developed by Aidan Follestad (@afollestad) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.afollestad.nocknock.utilities.ui + +import android.content.Context +import android.widget.Toast + +private var toast: Toast? = null + +/** Shows a toast in the receiving context, cancelling any previous. */ +fun Context.toast(message: Int) { + toast?.cancel() + toast = Toast.makeText(this, message, Toast.LENGTH_LONG) + .apply { + show() + } +} diff --git a/dependencies.gradle b/dependencies.gradle index 28a58cf..2f05d0f 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,39 +1,51 @@ ext.versions = [ + // Project minSdk : 21, compileSdk : 28, buildTools : '28.0.3', publishVersion : '0.8.0', publishVersionCode : 28, + // Plugins gradlePlugin : '3.2.1', spotlessPlugin : '3.17.0', versionPlugin : '0.20.0', + // Misc okHttp : '3.12.1', rhino : '1.7.10', + // Kotlin kotlin : '1.3.11', coroutines : '1.1.0', koin : '1.0.2', + // Google/AndroidX androidxAnnotations : '1.0.1', androidxCore : '1.0.2', androidxRecyclerView: '1.0.0', + androidxBrowser : '1.0.0', googleMaterial : '1.0.0', room : '2.0.0', lifecycle : '2.0.0', + // Rx rxBinding : '3.0.0-alpha1', + // afollestad materialDialogs : '2.0.0-rc7', - rxkPrefs : '1.2.0', + rxkPrefs : '1.2.1', + // Debugging timber : '4.7.1', + + // Unit testing junit : '4.12', mockito : '2.23.4', mockitoKotlin : '2.0.0-RC1', truth : '0.42', + // UI testing androidxTestRunner : '1.1.1', androidxTest : '1.1.0', archTesting : '2.0.0'