Migrate some more to Jetpack Compose + Material3

This commit is contained in:
William Brawner 2023-09-11 21:23:10 -06:00
parent 643345493e
commit c7e54f21d9
26 changed files with 223 additions and 1005 deletions

View file

@ -166,9 +166,9 @@ android.productFlavors.forEach { flavor ->
apply(plugin = "com.google.firebase.crashlytics") apply(plugin = "com.google.firebase.crashlytics")
dependencies { dependencies {
implementation("com.android.billingclient:billing:5.1.0") implementation("com.android.billingclient:billing:6.0.1")
implementation("com.google.android.play:core-ktx:1.8.1") implementation("com.google.android.play:core-ktx:1.8.1")
implementation("com.google.firebase:firebase-crashlytics:18.3.5") implementation("com.google.firebase:firebase-crashlytics:18.4.1")
} }
} }
} }

View file

@ -1,9 +1,6 @@
package com.wbrawner.simplemarkdown.utility package com.wbrawner.simplemarkdown.utility
import android.app.Activity import androidx.compose.runtime.Composable
import androidx.lifecycle.MutableLiveData
import com.google.android.material.button.MaterialButton
class SupportLinkProvider(@Suppress("unused") private val activity: Activity) { @Composable
val supportLinks = MutableLiveData<List<MaterialButton>>() fun SupportLinks() {}
}

View file

@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Menu
@ -26,6 +27,7 @@ import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationDrawerItem import androidx.compose.material3.NavigationDrawerItem
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Tab import androidx.compose.material3.Tab
@ -42,9 +44,13 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat.startActivity import androidx.core.content.ContextCompat.startActivity
import androidx.navigation.NavController import androidx.navigation.NavController
@ -101,7 +107,9 @@ fun MainScreen(navController: NavController, viewModel: MarkdownViewModel) {
DropdownMenuItem(text = { DropdownMenuItem(text = {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Text("Lock Swiping") Text("Lock Swiping")
Checkbox(checked = lockSwiping, onCheckedChange = { lockSwiping = !lockSwiping }) Checkbox(
checked = lockSwiping,
onCheckedChange = { lockSwiping = !lockSwiping })
} }
}, onClick = { }, onClick = {
lockSwiping = !lockSwiping lockSwiping = !lockSwiping
@ -138,7 +146,9 @@ fun MainScreen(navController: NavController, viewModel: MarkdownViewModel) {
.padding(8.dp), .padding(8.dp),
value = markdown, value = markdown,
onValueChange = { viewModel.updateMarkdown(it) }, onValueChange = { viewModel.updateMarkdown(it) },
textStyle = TextStyle.Default.copy(fontFamily = FontFamily.Monospace) textStyle = TextStyle.Default.copy(fontFamily = FontFamily.Monospace, color = MaterialTheme.colorScheme.onSurface),
keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences),
cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurface)
) )
} else { } else {
MarkdownPreview(modifier = Modifier.fillMaxSize(), markdown) MarkdownPreview(modifier = Modifier.fillMaxSize(), markdown)
@ -154,18 +164,26 @@ fun MarkdownNavigationDrawer(
navigate: (Route) -> Unit, content: @Composable (drawerState: DrawerState) -> Unit navigate: (Route) -> Unit, content: @Composable (drawerState: DrawerState) -> Unit
) { ) {
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val coroutineScope = rememberCoroutineScope()
DismissibleNavigationDrawer(drawerState = drawerState, drawerContent = { DismissibleNavigationDrawer(drawerState = drawerState, drawerContent = {
DismissibleDrawerSheet { DismissibleDrawerSheet {
Route.entries.forEach { route -> Route.entries.forEach { route ->
if (route == Route.EDITOR) { if (route == Route.EDITOR) {
return@forEach return@forEach
} }
NavigationDrawerItem(icon = { NavigationDrawerItem(
Icon(imageVector = route.icon, contentDescription = null) icon = {
}, Icon(imageVector = route.icon, contentDescription = null)
},
label = { Text(route.title) }, label = { Text(route.title) },
selected = false, selected = false,
onClick = { navigate(route) }) onClick = {
navigate(route)
coroutineScope.launch {
drawerState.close()
}
}
)
} }
} }
}) { }) {

View file

@ -14,7 +14,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavController import androidx.navigation.NavController
import com.wbrawner.simplemarkdown.utility.readAssetToString import com.wbrawner.simplemarkdown.utility.readAssetToString
import com.wbrawner.simplemarkdown.utility.toHtml import com.wbrawner.simplemarkdown.utility.toHtml
import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel
@Composable @Composable
fun MarkdownInfoScreen( fun MarkdownInfoScreen(

View file

@ -1,2 +1,104 @@
package com.wbrawner.simplemarkdown.ui package com.wbrawner.simplemarkdown.ui
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import androidx.browser.customtabs.CustomTabsIntent
import androidx.browser.customtabs.CustomTabsIntent.SHARE_STATE_ON
import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat.startActivity
import androidx.navigation.NavController
import com.wbrawner.simplemarkdown.R
import com.wbrawner.simplemarkdown.utility.SupportLinks
@Composable
fun SupportScreen(navController: NavController) {
Scaffold(topBar = {
MarkdownTopAppBar(title = "Support SimpleMarkdown", navController = navController)
}) { paddingValues ->
val context = LocalContext.current
Column(
modifier = Modifier
.padding(paddingValues)
.padding(16.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
modifier = Modifier.size(100.dp),
imageVector = Icons.Default.Favorite,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary
)
Text(context.getString(R.string.support_info), textAlign = TextAlign.Center)
Spacer(modifier = Modifier.height(8.dp))
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
CustomTabsIntent.Builder()
.setShareState(SHARE_STATE_ON)
.build()
.launchUrl(context, Uri.parse("https://github.com/wbrawner/SimpleMarkdown"))
},
colors = ButtonDefaults.buttonColors(
containerColor = Color(context.getColor(R.color.colorBackgroundGitHub)),
contentColor = Color.White
)
) {
Text(context.getString(R.string.action_view_github))
}
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
val playStoreIntent = Intent(Intent.ACTION_VIEW)
.apply {
data = Uri.parse("market://details?id=${context.packageName}")
addFlags(
Intent.FLAG_ACTIVITY_NO_HISTORY or
Intent.FLAG_ACTIVITY_NEW_DOCUMENT or
Intent.FLAG_ACTIVITY_MULTIPLE_TASK
)
}
try {
startActivity(context, playStoreIntent, null)
} catch (ignored: ActivityNotFoundException) {
playStoreIntent.data =
Uri.parse("https://play.google.com/store/apps/details?id=${context.packageName}")
startActivity(context, playStoreIntent, null)
}
},
colors = ButtonDefaults.buttonColors(
containerColor = Color(context.getColor(R.color.colorBackgroundPlayStore)),
contentColor = Color.White
)
) {
Text(context.getString(R.string.action_rate))
}
SupportLinks()
}
}
}

View file

@ -1,28 +0,0 @@
package com.wbrawner.simplemarkdown.view
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import androidx.viewpager.widget.ViewPager
class DisableableViewPager : ViewPager {
private var isSwipeLocked = false
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
return !isSwipeLocked && super.onInterceptTouchEvent(ev)
}
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(ev: MotionEvent): Boolean {
return !isSwipeLocked && super.onTouchEvent(ev)
}
fun setSwipeLocked(locked: Boolean) {
this.isSwipeLocked = locked
}
}

View file

@ -1,6 +0,0 @@
package com.wbrawner.simplemarkdown.view
interface ViewPagerPage {
fun onSelected()
fun onDeselected()
}

View file

@ -38,6 +38,7 @@ import com.wbrawner.simplemarkdown.R
import com.wbrawner.simplemarkdown.ui.MainScreen import com.wbrawner.simplemarkdown.ui.MainScreen
import com.wbrawner.simplemarkdown.ui.MarkdownInfoScreen import com.wbrawner.simplemarkdown.ui.MarkdownInfoScreen
import com.wbrawner.simplemarkdown.ui.SettingsScreen import com.wbrawner.simplemarkdown.ui.SettingsScreen
import com.wbrawner.simplemarkdown.ui.SupportScreen
import com.wbrawner.simplemarkdown.ui.theme.SimpleMarkdownTheme import com.wbrawner.simplemarkdown.ui.theme.SimpleMarkdownTheme
import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -126,7 +127,7 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
SettingsScreen(navController = navController) SettingsScreen(navController = navController)
} }
composable(Route.SUPPORT.path) { composable(Route.SUPPORT.path) {
Text("To do") SupportScreen(navController = navController)
} }
composable(Route.HELP.path) { composable(Route.HELP.path) {
MarkdownInfoScreen(title = Route.HELP.title, file = "Cheatsheet.md", navController = navController) MarkdownInfoScreen(title = Route.HELP.title, file = "Cheatsheet.md", navController = navController)
@ -141,12 +142,6 @@ class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsRes
} }
} }
} }
override fun onBackPressed() {
if (!findNavController(R.id.content).navigateUp()) {
super.onBackPressed()
}
}
} }
enum class Route( enum class Route(

View file

@ -1,70 +0,0 @@
package com.wbrawner.simplemarkdown.view.adapter
import android.content.Context
import android.content.res.Configuration
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
import androidx.viewpager.widget.ViewPager
import com.wbrawner.simplemarkdown.R
import com.wbrawner.simplemarkdown.view.fragment.EditFragment
import com.wbrawner.simplemarkdown.view.fragment.PreviewFragment
class EditPagerAdapter(fm: FragmentManager, private val context: Context)
: FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT), ViewPager.OnPageChangeListener {
private val editFragment = EditFragment()
private val previewFragment = PreviewFragment()
override fun getItem(position: Int): Fragment {
return when (position) {
FRAGMENT_EDIT -> editFragment
FRAGMENT_PREVIEW -> previewFragment
else -> throw IllegalStateException("Attempting to get fragment for invalid page number")
}
}
override fun getCount(): Int {
return NUM_PAGES
}
override fun getPageTitle(position: Int): CharSequence? {
var stringId = 0
when (position) {
FRAGMENT_EDIT -> stringId = R.string.action_edit
FRAGMENT_PREVIEW -> stringId = R.string.action_preview
}
return context.getString(stringId)
}
override fun getPageWidth(position: Int): Float {
return if (context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
0.5f
} else {
super.getPageWidth(position)
}
}
override fun onPageScrollStateChanged(state: Int) {
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
}
override fun onPageSelected(position: Int) {
when (position) {
FRAGMENT_EDIT -> {
editFragment.onSelected()
}
FRAGMENT_PREVIEW -> {
editFragment.onDeselected()
}
}
}
companion object {
const val FRAGMENT_EDIT = 0
const val FRAGMENT_PREVIEW = 1
const val NUM_PAGES = 2
}
}

View file

@ -1,99 +0,0 @@
//package com.wbrawner.simplemarkdown.view.fragment
//
//import android.content.res.Configuration
//import android.os.Bundle
//import android.view.LayoutInflater
//import android.view.MenuItem
//import android.view.View
//import android.view.ViewGroup
//import android.widget.Toast
//import androidx.appcompat.app.AppCompatDelegate
//import androidx.fragment.app.Fragment
//import androidx.lifecycle.lifecycleScope
//import androidx.navigation.fragment.findNavController
//import androidx.navigation.ui.setupWithNavController
//import com.wbrawner.plausible.android.Plausible
//import com.wbrawner.simplemarkdown.R
//import com.wbrawner.simplemarkdown.databinding.FragmentMarkdownInfoBinding
//import com.wbrawner.simplemarkdown.utility.*
//import kotlinx.coroutines.launch
//
//class MarkdownInfoFragment : Fragment() {
// private val errorHandler: ErrorHandler by errorHandlerImpl()
// private var _binding: FragmentMarkdownInfoBinding? = null
// private val binding: FragmentMarkdownInfoBinding
// get() = _binding!!
//
// override fun onCreate(savedInstanceState: Bundle?) {
// super.onCreate(savedInstanceState)
// setHasOptionsMenu(true)
// }
//
// override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
// _binding = FragmentMarkdownInfoBinding.inflate(inflater, container, false)
// return binding.root
// }
//
// override fun onDestroyView() {
// super.onDestroyView()
// _binding = null
// }
//
// override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// val fileName = arguments?.getString(EXTRA_FILE)
// if (fileName.isNullOrBlank()) {
// findNavController().navigateUp()
// return
// }
// binding.toolbar.setupWithNavController(findNavController())
//
// val isNightMode = AppCompatDelegate.getDefaultNightMode() ==
// AppCompatDelegate.MODE_NIGHT_YES
// || resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
// val defaultCssId = if (isNightMode) {
// R.string.pref_custom_css_default_dark
// } else {
// R.string.pref_custom_css_default
// }
// val css: String? = getString(defaultCssId)
// lifecycleScope.launch {
// try {
// val html = view.context.assets?.readAssetToString(fileName)
// ?.toHtml()
// ?: throw RuntimeException("Unable to open stream to $fileName")
// binding.infoWebview.loadDataWithBaseURL(null,
// String.format(FORMAT_CSS, css) + html,
// "text/html",
// "UTF-8", null
// )
// } catch (e: Exception) {
// errorHandler.reportException(e)
// Toast.makeText(view.context, R.string.file_load_error, Toast.LENGTH_SHORT).show()
// findNavController().navigateUp()
// }
// }
// }
//
// override fun onOptionsItemSelected(item: MenuItem): Boolean {
// if (item.itemId == android.R.id.home) {
// findNavController().navigateUp()
// return true
// }
// return super.onOptionsItemSelected(item)
// }
//
// override fun onStart() {
// super.onStart()
// arguments?.getString(EXTRA_FILE)?.let {
// Plausible.pageView(it)
// }
// }
//
// companion object {
// const val FORMAT_CSS = "<style>" +
// "%s" +
// "</style>"
// const val EXTRA_TITLE = "title"
// const val EXTRA_FILE = "file"
// }
//}

View file

@ -1,45 +0,0 @@
//package com.wbrawner.simplemarkdown.view.fragment
//
//import android.os.Bundle
//import android.view.LayoutInflater
//import android.view.MenuItem
//import android.view.View
//import android.view.ViewGroup
//import androidx.fragment.app.Fragment
//import androidx.navigation.fragment.findNavController
//import androidx.navigation.ui.setupWithNavController
//import com.wbrawner.plausible.android.Plausible
//import com.wbrawner.simplemarkdown.R
//import com.wbrawner.simplemarkdown.databinding.FragmentSettingsBinding
//
//class SettingsContainerFragment : Fragment() {
// private var _binding: FragmentSettingsBinding? = null
// private val binding: FragmentSettingsBinding
// get() = _binding!!
//
// override fun onCreate(savedInstanceState: Bundle?) {
// super.onCreate(savedInstanceState)
// setHasOptionsMenu(true)
// }
//
// override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
// _binding = FragmentSettingsBinding.inflate(inflater, container, false)
// return binding.root
// }
//
// override fun onDestroyView() {
// super.onDestroyView()
// _binding = null
// }
//
// override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// binding.toolbar.setupWithNavController(findNavController())
// }
//
// override fun onOptionsItemSelected(item: MenuItem): Boolean = findNavController().navigateUp()
//
// override fun onStart() {
// super.onStart()
// Plausible.pageView("Settings")
// }
//}

View file

@ -1,82 +0,0 @@
package com.wbrawner.simplemarkdown.view.fragment
import android.content.SharedPreferences
import android.os.Build
import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate
import androidx.lifecycle.lifecycleScope
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import com.wbrawner.simplemarkdown.BuildConfig
import com.wbrawner.simplemarkdown.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class SettingsFragment
: PreferenceFragmentCompat(),
SharedPreferences.OnSharedPreferenceChangeListener {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_general)
if (BuildConfig.DEBUG) {
preferenceScreen.addPreference(Preference(preferenceScreen.context).apply {
title = "Force crash"
onPreferenceClickListener = Preference.OnPreferenceClickListener {
throw RuntimeException("Forced crash from settings")
}
})
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
lifecycleScope.launch(context = Dispatchers.IO) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
sharedPreferences.registerOnSharedPreferenceChangeListener(this@SettingsFragment)
(findPreference(getString(R.string.pref_key_dark_mode)) as? ListPreference)?.let {
setListPreferenceSummary(sharedPreferences, it)
}
@Suppress("ConstantConditionIf")
if (!BuildConfig.ENABLE_CUSTOM_CSS) {
findPreference<Preference>(getString(R.string.pref_custom_css))?.let {
preferenceScreen.removePreference(it)
}
}
}
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (!isAdded || sharedPreferences == null || key == null) return
val preference = findPreference(key) as? ListPreference ?: return
setListPreferenceSummary(sharedPreferences, preference)
if (preference.key != getString(R.string.pref_key_dark_mode)) {
return
}
var darkMode: Int = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY
} else {
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
}
val darkModeValue = sharedPreferences.getString(preference.key, null)
if (!darkModeValue.isNullOrEmpty()) {
if (darkModeValue.equals(getString(R.string.pref_value_light), ignoreCase = true)) {
darkMode = AppCompatDelegate.MODE_NIGHT_NO
} else if (darkModeValue.equals(getString(R.string.pref_value_dark), ignoreCase = true)) {
darkMode = AppCompatDelegate.MODE_NIGHT_YES
}
}
AppCompatDelegate.setDefaultNightMode(darkMode)
activity?.recreate()
}
private fun setListPreferenceSummary(sharedPreferences: SharedPreferences, preference: ListPreference) {
val storedValue = sharedPreferences.getString(
preference.key,
null
) ?: return
val index = preference.findIndexOfValue(storedValue)
if (index < 0) return
preference.summary = preference.entries[index].toString()
}
}

View file

@ -1,69 +0,0 @@
//package com.wbrawner.simplemarkdown.view.fragment
//
//import android.content.ActivityNotFoundException
//import android.content.Intent
//import android.net.Uri
//import android.os.Bundle
//import android.view.LayoutInflater
//import android.view.View
//import android.view.ViewGroup
//import androidx.browser.customtabs.CustomTabsIntent
//import androidx.fragment.app.Fragment
//import androidx.lifecycle.Observer
//import androidx.navigation.fragment.findNavController
//import androidx.navigation.ui.setupWithNavController
//import com.wbrawner.plausible.android.Plausible
//import com.wbrawner.simplemarkdown.databinding.FragmentSupportBinding
//import com.wbrawner.simplemarkdown.utility.SupportLinkProvider
//
//class SupportFragment : Fragment() {
// private var _binding: FragmentSupportBinding? = null
// private val binding: FragmentSupportBinding
// get() = _binding!!
//
// override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
// _binding = FragmentSupportBinding.inflate(inflater, container, false)
// return binding.root
// }
//
// override fun onDestroyView() {
// super.onDestroyView()
// _binding = null
// }
//
// override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// binding.toolbar.setupWithNavController(findNavController())
// binding.githubButton.setOnClickListener {
// CustomTabsIntent.Builder()
// .addDefaultShareMenuItem()
// .build()
// .launchUrl(view.context, Uri.parse("https://github" +
// ".com/wbrawner/SimpleMarkdown"))
// }
// binding.rateButton.setOnClickListener {
// val playStoreIntent = Intent(Intent.ACTION_VIEW)
// .apply {
// data = Uri.parse("market://details?id=${view.context.packageName}")
// addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY or
// Intent.FLAG_ACTIVITY_NEW_DOCUMENT or
// Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
// }
// try {
// startActivity(playStoreIntent)
// } catch (ignored: ActivityNotFoundException) {
// playStoreIntent.data = Uri.parse("https://play.google.com/store/apps/details?id=${view.context.packageName}")
// startActivity(playStoreIntent)
// }
// }
// SupportLinkProvider(requireActivity()).supportLinks.observe(viewLifecycleOwner, Observer { links ->
// links.forEach {
// binding.supportButtons.addView(it)
// }
// })
// }
//
// override fun onStart() {
// super.onStart()
// Plausible.pageView("Support")
// }
//}

View file

@ -1,61 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/drawerLayout">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.wbrawner.simplemarkdown.view.DisableableViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/bottomSheet"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorBackground"
app:layout_scrollFlags="scroll|enterAlways" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorBackground"
android:visibility="gone">
<com.google.android.material.tabs.TabItem
android:id="@+id/editTab"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/action_edit" />
<com.google.android.material.tabs.TabItem
android:id="@+id/previewTab"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/action_preview" />
</com.google.android.material.tabs.TabLayout>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigationView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:menu="@menu/menu_main" />
</androidx.drawerlayout.widget.DrawerLayout>

View file

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/content"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />

View file

@ -1,23 +0,0 @@
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/markdown_edit_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.wbrawner.simplemarkdown.view.fragment.EditFragment">
<EditText
android:id="@+id/markdown_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:gravity="top"
android:fontFamily="monospace"
android:hint="@string/markdown_here"
android:imeOptions="flagNoExtractUi"
android:inputType="textMultiLine|textCapSentences"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingBottom="16dp"
android:scrollHorizontally="false"
android:importantForAutofill="no" />
</androidx.core.widget.NestedScrollView>

View file

@ -1,63 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorBackground"
android:id="@+id/drawerLayout">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.wbrawner.simplemarkdown.view.DisableableViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/bottomSheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:liftOnScroll="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorBackground"
app:layout_scrollFlags="scroll|enterAlways|snap" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@color/colorBackground">
<com.google.android.material.tabs.TabItem
android:id="@+id/editTab"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/action_edit" />
<com.google.android.material.tabs.TabItem
android:id="@+id/previewTab"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/action_preview" />
</com.google.android.material.tabs.TabLayout>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/navigationView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:menu="@menu/menu_main" />
</androidx.drawerlayout.widget.DrawerLayout>

View file

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.wbrawner.simplemarkdown.view.fragment.MarkdownInfoFragment">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="128dp"
android:background="@color/colorBackground">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:elevation="0dp"
app:layout_collapseMode="pin" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<WebView
android:id="@+id/infoWebview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:nestedScrollingEnabled="false" />
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -1,13 +0,0 @@
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.wbrawner.simplemarkdown.view.fragment.PreviewFragment">
<WebView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:nestedScrollingEnabled="false"
android:id="@+id/markdown_view" />
</androidx.core.widget.NestedScrollView>

View file

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorBackground" />
</com.google.android.material.appbar.AppBarLayout>
<fragment
android:id="@+id/fragment_settings"
android:name="com.wbrawner.simplemarkdown.view.fragment.SettingsFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>

View file

@ -1,78 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorBackground"
app:layout_constraintTop_toTopOf="parent" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:clipChildren="false"
android:clipToPadding="false"
android:padding="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/heartIcon"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/ic_favorite_black_24dp"
android:tint="@color/colorAccent"
android:contentDescription="@string/description_heart"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/supportInfoText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="@string/support_info"
android:textAlignment="center"
android:textColor="@color/colorOnBackground"
app:layout_constraintTop_toBottomOf="@+id/heartIcon" />
<com.google.android.material.button.MaterialButton
android:id="@+id/githubButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/colorBackgroundGitHub"
android:textColor="@color/colorWhite"
android:text="@string/action_view_github"
app:layout_constraintTop_toBottomOf="@+id/supportInfoText" />
<com.google.android.material.button.MaterialButton
android:id="@+id/rateButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:backgroundTint="@color/colorBackgroundPlayStore"
android:textColor="@color/colorWhite"
android:text="@string/action_rate"
app:layout_constraintTop_toBottomOf="@+id/githubButton" />
<LinearLayout
android:id="@+id/supportButtons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="@+id/rateButton" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_share"
android:icon="@drawable/ic_share"
android:title="@string/action_share"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_new"
android:title="@string/action_new"
android:alphabeticShortcut="N"
app:showAsAction="never" />
<item
android:id="@+id/action_load"
android:title="@string/action_open"
android:alphabeticShortcut="O"
app:showAsAction="never" />
<item
android:id="@+id/action_save"
android:alphabeticShortcut="S"
android:title="@string/action_save" />
<item
android:id="@+id/action_save_as"
android:title="@string/action_save_as"
tools:ignore="UnusedAttribute" />
<item
android:id="@+id/action_lock_swipe"
android:checkable="true"
android:title="@string/action_lock_swipe"
app:showAsAction="never" />
</menu>

View file

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<group android:id="@+id/mainGroup">
<item
android:id="@+id/action_mainFragment_to_settingsContainerFragment"
android:title="@string/action_settings"
android:icon="@drawable/ic_settings_black_24dp"
app:showAsAction="never" />
<item
android:id="@+id/action_mainFragment_to_supportFragment"
android:title="@string/support_title"
android:icon="@drawable/ic_favorite_black_24dp"
app:showAsAction="never" />
</group>
<group android:id="@+id/addtionalInfoGroup">
<item
android:id="@+id/action_mainFragment_to_helpFragment"
android:title="@string/action_help"
android:icon="@drawable/ic_help_black_24dp"
app:showAsAction="never" />
<item
android:id="@+id/action_mainFragment_to_librariesFragment"
android:title="@string/action_libraries"
android:icon="@drawable/ic_info_black_24dp"
app:showAsAction="never" />
<item
android:id="@+id/action_mainFragment_to_privacyFragment"
android:title="@string/action_privacy"
android:icon="@drawable/ic_eye_black_24dp"
app:showAsAction="never" />
</group>
</menu>

View file

@ -1,87 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@+id/mainFragment">
<fragment
android:id="@+id/settingsContainerFragment"
android:name="com.wbrawner.simplemarkdown.view.fragment.SettingsContainerFragment"
android:label="@string/title_activity_settings" />
<fragment
android:id="@+id/mainFragment"
android:name="com.wbrawner.simplemarkdown.view.fragment.MainFragment"
android:label="">
<action
android:id="@+id/action_mainFragment_to_helpFragment"
app:destination="@id/helpFragment"
app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right"
app:popExitAnim="@android:anim/slide_out_right"
app:popUpTo="@id/mainFragment" />
<action
android:id="@+id/action_mainFragment_to_privacyFragment"
app:destination="@id/privacyFragment"
app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right"
app:popExitAnim="@android:anim/slide_out_right"
app:popUpTo="@id/mainFragment" />
<action
android:id="@+id/action_mainFragment_to_librariesFragment"
app:destination="@id/librariesFragment"
app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right"
app:popExitAnim="@android:anim/slide_out_right"
app:popUpTo="@id/mainFragment" />
<action
android:id="@+id/action_mainFragment_to_settingsContainerFragment"
app:destination="@id/settingsContainerFragment"
app:enterAnim="@android:anim/slide_in_left"
app:exitAnim="@android:anim/slide_out_right"
app:popExitAnim="@android:anim/slide_out_right"
app:popUpTo="@id/mainFragment" />
<action
android:id="@+id/action_mainFragment_to_supportFragment"
app:destination="@id/supportFragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim"
app:popUpTo="@id/mainFragment" />
</fragment>
<fragment
android:id="@+id/supportFragment"
android:name="com.wbrawner.simplemarkdown.view.fragment.SupportFragment"
android:label="@string/support_title" />
<fragment
android:id="@+id/helpFragment"
android:name="com.wbrawner.simplemarkdown.view.fragment.MarkdownInfoFragment"
android:label="@string/action_help"
tools:layout="@layout/fragment_markdown_info">
<argument
android:name="file"
app:argType="string"
android:defaultValue="Cheatsheet.md" />
</fragment>
<fragment
android:id="@+id/privacyFragment"
android:name="com.wbrawner.simplemarkdown.view.fragment.MarkdownInfoFragment"
android:label="@string/action_privacy"
tools:layout="@layout/fragment_markdown_info">
<argument
android:name="file"
android:defaultValue="Privacy Policy.md"
app:argType="string" />
</fragment>
<fragment
android:id="@+id/librariesFragment"
android:name="com.wbrawner.simplemarkdown.view.fragment.MarkdownInfoFragment"
android:label="@string/action_libraries"
tools:layout="@layout/fragment_markdown_info">
<argument
android:name="file"
android:defaultValue="Libraries.md"
app:argType="string" />
</fragment>
</navigation>

View file

@ -1,27 +0,0 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<SwitchPreference
android:defaultValue="true"
android:key="autosave"
android:summaryOff="@string/pref_autosave_off"
android:summaryOn="@string/pref_autosave_on"
android:title="@string/pref_title_autosave" />
<EditTextPreference
android:defaultValue="@string/pref_custom_css_default"
android:key="@string/pref_custom_css"
android:summary="@string/pref_description_custom_css"
android:title="@string/pref_title_custom_css" />
<ListPreference
android:entries="@array/pref_entries_dark_mode"
android:entryValues="@array/pref_values_dark_mode"
android:defaultValue="@string/pref_key_dark_mode_auto"
android:key="@string/pref_key_dark_mode"
android:title="@string/title_dark_mode" />
<SwitchPreference
android:defaultValue="false"
android:key="@string/readability_enabled"
android:summaryOff="@string/pref_readability_off"
android:summaryOn="@string/pref_readability_on"
android:title="@string/pref_title_readability" />
</PreferenceScreen>

View file

@ -1,105 +1,107 @@
package com.wbrawner.simplemarkdown.utility package com.wbrawner.simplemarkdown.utility
import android.app.Activity import android.app.Activity
import android.app.Application
import android.os.Bundle
import android.widget.Toast import android.widget.Toast
import androidx.lifecycle.MutableLiveData import androidx.compose.foundation.layout.fillMaxWidth
import com.android.billingclient.api.* import androidx.compose.material3.Button
import com.google.android.material.button.MaterialButton import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.BillingClientStateListener
import com.android.billingclient.api.BillingFlowParams
import com.android.billingclient.api.BillingFlowParams.ProductDetailsParams
import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.ConsumeParams
import com.android.billingclient.api.ProductDetails
import com.android.billingclient.api.QueryProductDetailsParams
import com.wbrawner.simplemarkdown.R import com.wbrawner.simplemarkdown.R
class SupportLinkProvider(private val activity: Activity) : BillingClientStateListener, @Composable
PurchasesUpdatedListener { fun SupportLinks() {
val supportLinks = MutableLiveData<List<MaterialButton>>() val context = LocalContext.current
var products by remember { mutableStateOf(emptyList<ProductDetails>()) }
private val billingClient: BillingClient = BillingClient.newBuilder(activity.applicationContext) var billingClient by remember { mutableStateOf<BillingClient?>(null) }
.setListener(this) DisposableEffect(context) {
billingClient = BillingClient.newBuilder(context.applicationContext)
.setListener { _, purchases ->
purchases?.forEach { purchase ->
val consumeParams = ConsumeParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
billingClient?.consumeAsync(consumeParams) { _, _ ->
Toast.makeText(
context,
context.getString(R.string.support_thank_you),
Toast.LENGTH_SHORT
).show()
}
}
}
.enablePendingPurchases() .enablePendingPurchases()
.build() .build()
init { billingClient?.startConnection(object : BillingClientStateListener {
billingClient.startConnection(this) override fun onBillingServiceDisconnected() {
activity.application.registerActivityLifecycleCallbacks( billingClient?.startConnection(this)
object : Application.ActivityLifecycleCallbacks { }
override fun onActivityPaused(activity: Activity) {
}
override fun onActivityStarted(activity: Activity) { override fun onBillingSetupFinished(result: BillingResult) {
} if (result.responseCode != BillingClient.BillingResponseCode.OK) {
return
override fun onActivityDestroyed(activity: Activity) {
billingClient.endConnection()
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
}
override fun onActivityStopped(activity: Activity) {
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
}
override fun onActivityResumed(activity: Activity) {
}
} }
) val productsQuery = listOf("support_the_developer", "tip_coffee", "tip_beer")
} .map {
QueryProductDetailsParams.Product.newBuilder()
override fun onBillingSetupFinished(result: BillingResult) { .setProductId(it)
if (result.responseCode != BillingClient.BillingResponseCode.OK) { .setProductType(BillingClient.ProductType.INAPP)
return .build()
}
val skuDetails = SkuDetailsParams.newBuilder()
.setSkusList(listOf("support_the_developer", "tip_coffee", "tip_beer"))
.setType(BillingClient.SkuType.INAPP)
.build()
billingClient.querySkuDetailsAsync(skuDetails) { skuDetailsResponse, skuDetailsList ->
// Process the result.
if (skuDetailsResponse.responseCode != BillingClient.BillingResponseCode.OK || skuDetailsList.isNullOrEmpty()) {
return@querySkuDetailsAsync
}
skuDetailsList.sortedBy { it.priceAmountMicros }
.map { skuDetails ->
val supportButton = MaterialButton(activity)
supportButton.text = activity.getString(
R.string.support_button_purchase,
skuDetails.title,
skuDetails.price
)
supportButton.setOnClickListener {
val flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build()
billingClient.launchBillingFlow(activity, flowParams)
}
supportButton
} }
.let { val productDetailsQuery = QueryProductDetailsParams.newBuilder()
supportLinks.postValue(it) .setProductList(productsQuery)
}
}
}
override fun onBillingServiceDisconnected() {
billingClient.startConnection(this)
}
override fun onPurchasesUpdated(result: BillingResult, purchases: MutableList<Purchase>?) {
purchases?.forEach { purchase ->
val consumeParams = ConsumeParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build() .build()
billingClient.consumeAsync(consumeParams) { _, _ -> billingClient?.queryProductDetailsAsync(productDetailsQuery) { result, productDetails ->
Toast.makeText( if (result.responseCode != BillingClient.BillingResponseCode.OK || productDetails.isEmpty()) {
activity, return@queryProductDetailsAsync
activity.getString(R.string.support_thank_you), }
Toast.LENGTH_SHORT products =
).show() productDetails.sortedBy { it.oneTimePurchaseOfferDetails?.priceAmountMicros }
.toList()
}
} }
})
onDispose {
billingClient?.endConnection()
}
}
products.forEach { product ->
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
val productDetails = ProductDetailsParams.newBuilder()
.setProductDetails(product)
.build()
val flowParams = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(listOf(productDetails))
.build()
billingClient?.launchBillingFlow(context as Activity, flowParams)
}
) {
Text(
context.getString(
R.string.support_button_purchase,
product.name,
product.oneTimePurchaseOfferDetails?.formattedPrice
)
)
} }
} }
} }