1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

DROID-1803 App | Tech | Kotlin + Library updates (#704)

This commit is contained in:
Evgenii Kozlov 2023-12-15 13:22:30 +01:00 committed by uburoiubu
parent 232b634e48
commit 53fd8e9061
No known key found for this signature in database
GPG key ID: C8FB80E0A595FBB6
31 changed files with 172 additions and 949 deletions

View file

@ -1,14 +1,13 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "kotlin-android-extensions"
id "kotlin-kapt"
id 'com.google.gms.google-services'
}
android {
compileSdkVersion 34
buildToolsVersion "32.0.0"
buildToolsVersion "34.0.0"
defaultConfig {
applicationId "com.anytypeio.anytype.sample"
@ -31,12 +30,12 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11
kotlin {
jvmToolchain(17)
}
buildFeatures {

View file

@ -27,11 +27,6 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".search.SearchOnPageActivity"
android:windowSoftInputMode="stateHidden|adjustResize"/>
<activity android:name=".DisabledAnimationActivity" />
<activity android:name=".ScrollAndMove" />
<activity android:name=".StyleActivity" />
<activity android:name=".MainActivity" />
<activity android:name=".StyleToolbarActivity"/>

View file

@ -1,266 +0,0 @@
package com.anytypeio.anytype.sample
import android.content.Context
import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_utils.ext.imm
import com.anytypeio.anytype.core_utils.ui.ViewType
import com.anytypeio.anytype.sample.adapter.AbstractAdapter
import com.anytypeio.anytype.sample.adapter.AbstractHolder
import kotlinx.android.synthetic.main.activity_disabled_animation.*
import kotlinx.android.synthetic.main.item_editable.view.*
import timber.log.Timber
class DisabledAnimationActivity : AppCompatActivity(R.layout.activity_disabled_animation) {
private val start: List<Mock>
get() = mutableListOf(
Mock(
id = 0,
text = "TITLE",
type = 0,
isFocused = false
),
Mock(
id = 0,
text = "BULLETED",
type = 0,
isFocused = true
),
Mock(
id = 1,
text = "PARAGRAPH 2",
type = 0,
isFocused = false
)
)
private val end: List<Mock>
get() = listOf(
Mock(
id = 0,
text = "TITLE",
type = 0,
isFocused = false
),
Mock(
id = 0,
text = "PARAGRAPH",
type = 1,
isFocused = true
),
Mock(
id = 1,
text = "PARAGRAPH 2",
type = 0,
isFocused = false
)
)
private val mockAdapter = MockAdapter(
items = start.toMutableList()
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
recycler.apply {
layoutManager = CustomLinearLayoutManager(context)
adapter = mockAdapter
itemAnimator = null
}
startButton.setOnClickListener {
Timber.d("Start button clicked")
mockAdapter.update(
update = end
)
}
endButton.setOnClickListener {
Timber.d("End button clicked")
mockAdapter.update(
update = start
)
}
}
class MockAdapter(val items: MutableList<Mock>) : AbstractAdapter<Mock>(items) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AbstractHolder<Mock> {
return when (viewType) {
0 -> {
val inflater = LayoutInflater.from(parent.context)
val view = inflater.inflate(R.layout.item_editable, parent, false)
MockHolder(view)
}
1 -> {
val inflater = LayoutInflater.from(parent.context)
val view = inflater.inflate(R.layout.item_editable, parent, false)
MockHolder2(view)
}
else -> throw IllegalStateException()
}
}
override fun getItemViewType(position: Int): Int {
return items[position].getViewType()
}
override fun update(update: List<Mock>) {
val old = ArrayList(items)
val cb = Differ(old = old, new = update)
val result = DiffUtil.calculateDiff(cb)
items.clear()
items.addAll(update)
result.dispatchUpdatesTo(this)
}
}
class MockHolder(view: View) : AbstractHolder<Mock>(view) {
override fun bind(item: Mock) {
Timber.d("Binding item: $item")
itemView.input.setText(item.text)
if (item.isFocused)
focus()
else
itemView.input.clearFocus()
}
private fun focus() {
itemView.input.apply {
post {
if (!hasFocus()) {
if (requestFocus()) {
context.imm().showSoftInput(this, InputMethodManager.SHOW_FORCED)
} else {
Timber.d("Couldn't gain focus")
}
} else
Timber.d("Already had focus")
}
}
}
}
class MockHolder2(view: View) : AbstractHolder<Mock>(view) {
override fun bind(item: Mock) {
Timber.d("Binding item: $item")
itemView.input.setText(item.text)
itemView.input.setTextColor(Color.GREEN)
if (item.isFocused) focus()
}
private fun focus() {
itemView.input.apply {
post {
Timber.d("Focusing!")
if (!hasFocus()) {
if (requestFocus()) {
context.imm().showSoftInput(this, InputMethodManager.SHOW_FORCED)
} else {
Timber.d("Couldn't gain focus")
}
} else
Timber.d("Already had focus")
}
}
}
}
data class Mock(
val id: Int,
val text: String,
val isFocused: Boolean,
val type: Int
) : ViewType {
override fun getViewType(): Int = type
}
class Differ(
private val old: List<Mock>,
private val new: List<Mock>
) : DiffUtil.Callback() {
override fun getOldListSize(): Int = old.size
override fun getNewListSize(): Int = new.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = old[oldItemPosition]
val newItem = new[newItemPosition]
Timber.d("areItemsTheSame for: $oldItem \n and \n $newItem")
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = old[oldItemPosition]
val newItem = new[newItemPosition]
Timber.d("areContentsTheSame for: $oldItem \n and \n $newItem")
return oldItem == newItem
}
}
class CustomLinearLayoutManager(
context: Context,
//focus: View
) : LinearLayoutManager(context) {
override fun onInterceptFocusSearch(focused: View, direction: Int): View? {
Timber.d("OnInterceptFocusSearch view with text: ${(focused as EditText).text}")
when (direction) {
View.FOCUS_UP -> {
Timber.d("OnInterceptFocusSearch direction: FOCUS_UP")
}
View.FOCUS_DOWN -> {
Timber.d("OnInterceptFocusSearch direction: FOCUS_DOWN")
}
}
//return super.onInterceptFocusSearch(focused, direction)
//val v = getChildAt(2)?.findViewById<EditText>(R.id.input)
//v?.requestFocus()
//Timber.d("At position 2 there is a view with text: ${(v as EditText).text}")
return null
}
override fun removeView(child: View?) {
Timber.d("On remove view")
super.removeView(child)
}
override fun detachView(child: View) {
Timber.d("On detach view")
super.detachView(child)
}
override fun onFocusSearchFailed(
focused: View,
focusDirection: Int,
recycler: RecyclerView.Recycler,
state: RecyclerView.State
): View? {
Timber.d("onFocusSearchFailed for view with text: ${(focused as EditText).text}")
when (focusDirection) {
View.FOCUS_UP -> {
Timber.d("onFocusSearchFailed direction: FOCUS_UP")
}
View.FOCUS_DOWN -> {
Timber.d("onFocusSearchFailed direction: FOCUS_DOWN")
}
}
return super.onFocusSearchFailed(focused, focusDirection, recycler, state)
}
}
}

View file

@ -1,51 +0,0 @@
package com.anytypeio.anytype.sample
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.android.synthetic.main.activity_long_clicked.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import timber.log.Timber
/**
* Emulate long click on text block, with following
* Selection and Focus events
*
*/
class LongClickActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_long_clicked)
textInputWidget.enableEditMode()
textInputWidget.setText("Hi, Konstantin")
textInputWidget.setOnClickListener {
Timber.d("Single clicked!")
}
textInputWidget.setOnLongClickListener {
Timber.d("Long clicked!")
lifecycleScope.launch {
delay(500)
Timber.d("Enabling read mode")
textInputWidget.enableReadMode()
}
true
}
textInputWidget.selectionWatcher = {intRange: IntRange ->
Timber.d("Selection changed : $intRange")
}
textInputWidget.setOnFocusChangeListener { v, hasFocus ->
Timber.d("Focus changed:$hasFocus")
}
button.setOnClickListener {
textInputWidget.enableEditMode()
}
}
}

View file

@ -1,90 +0,0 @@
package com.anytypeio.anytype.sample
import android.os.Bundle
import android.text.Annotation
import android.text.Spannable
import android.text.SpannableString
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.item_test.view.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val annotation = Annotation("key", "rounded")
val list = mutableListOf<Item>()
val range = IntRange(0, 50)
range.forEach {
var t = "I am block number $it"
val spannable = SpannableString(t)
if (it == 10) {
spannable.setSpan(annotation, 0, t.length, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
list.add(Item(text = spannable))
} else {
list.add(Item(text = spannable))
}
}
with(recyclerView) {
layoutManager = LinearLayoutManager(this@MainActivity)
addItemDecoration(
DividerItemDecoration(
this@MainActivity,
DividerItemDecoration.VERTICAL
)
)
adapter = MarkupAdapter(list) { pos: Int ->
(this.adapter as MarkupAdapter).setData(
Item(SpannableString("I am block number $pos")),
pos
)
}
}
}
class MarkupAdapter(private val data: MutableList<Item>, private val listener: (Int) -> Unit) :
RecyclerView.Adapter<MarkupAdapter.MarkupViewHolder>() {
fun setData(item: Item, position: Int) {
data[position] = item
notifyItemChanged(position)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MarkupViewHolder {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.item_test, parent, false)
return MarkupViewHolder(view)
}
override fun getItemCount(): Int = data.size
override fun onBindViewHolder(holder: MarkupViewHolder, position: Int) {
holder.bind(data[position].text, listener)
}
class MarkupViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(text: SpannableString, listener: (Int) -> Unit) {
itemView.item.text = text
itemView.setOnClickListener {
listener.invoke(adapterPosition)
}
}
}
}
}
data class Item(val text: SpannableString)

View file

@ -1,406 +0,0 @@
package com.anytypeio.anytype.sample
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Point
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_utils.ext.toast
import kotlinx.android.synthetic.main.activity_scroll_and_move.*
import kotlinx.android.synthetic.main.item_scroll_and_move.view.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import timber.log.Timber
class ScrollAndMove : AppCompatActivity() {
private val decoration = object : RecyclerView.ItemDecoration() {
var divider: Drawable? = null
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
parent.adapter?.itemCount?.let { count ->
if (count == 1) {
outRect.bottom = screenHeight
outRect.top = screenHeight
} else {
if (parent.getChildAdapterPosition(view) == count - 1) {
outRect.bottom = screenHeight
} else if (parent.getChildAdapterPosition(view) == 0) {
outRect.top = screenHeight
}
}
}
}
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
//c.save()
Timber.d("child count: ${parent.childCount}")
for (i in 0 until parent.childCount) {
Timber.d("idx: $i")
val child = parent.getChildAt(i)
val pos = parent.getChildAdapterPosition(child)
Timber.d("onDrawOver position: $pos")
var targeted = false
var ratio: Float = 0.0f
(parent.adapter as? Adapter)?.let { adapter ->
val item = adapter.models[pos]
targeted = item.isTargeted
ratio = item.ratio
}
if (!targeted) continue
Timber.d("onDrawOver ratio: $ratio")
if (ratio in END_RANGE) {
Timber.d("Drawing bottom decoration for: $pos")
val left = 0
val right = parent.width
val top = child.bottom
val bottom = child.bottom + divider!!.intrinsicHeight
divider?.setBounds(left, top, right, bottom)
divider?.draw(c)
} else if (ratio in START_RANGE) {
Timber.d("Drawing bottom decoration for: $pos")
val left = 0
val right = parent.width
val top = child.top
val bottom = child.top + divider!!.intrinsicHeight
divider?.setBounds(left, top, right, bottom)
divider?.draw(c)
}
}
//c.restore()
}
}
private val channel = Channel<Unit>()
init {
channel
.consumeAsFlow()
.onEach { Timber.d("event!") }
.mapLatest {
calculate()
}
.onEach { Timber.d("done") }
.launchIn(lifecycleScope)
}
val screenHeight: Int
get() {
val wm = (getSystemService(Context.WINDOW_SERVICE) as WindowManager)
val display = wm.defaultDisplay
val p = Point()
display.getSize(p)
return p.y / 2
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_scroll_and_move)
setup()
}
fun toggleDecoration() {
if (recycler.itemDecorationCount > 0) {
recycler.removeItemDecoration(decoration)
} else {
recycler.addItemDecoration(decoration)
}
}
fun setup() {
toogleDecoration.setOnClickListener {
toggleDecoration()
}
apply.setOnClickListener {
move()
}
val text = getString(R.string.placeholder_text)
val models = mutableListOf<Model>().apply {
repeat(MODEL_COUNT) { count ->
add(
Model(
text = "$count. $text",
isSelected = count in 0..1
)
)
}
}
decoration.divider = getDrawable(R.drawable.scroll_and_move_divider)
recycler.apply {
addItemDecoration(decoration)
layoutManager = LinearLayoutManager(context)
adapter = Adapter(
models = models
)
addOnScrollListener(
object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
//Timber.d("On scrolled dy: $dy")
//Timber.d("Current scroll y: ${recycler.scrollY}")
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
lifecycleScope.launch {
channel.send(Unit)
}
}
//return
// Timber.d("Vertical scroll: ${recycler.computeVerticalScrollOffset()}")
}
}
)
}
}
fun move() {
val adapter = (recycler.adapter as Adapter)
val models = adapter.models
val target = models.firstOrNull { it.isTargeted }
check(models.count { it.isTargeted } == 1) { "merde!" }
if (target != null) {
when (target.ratio) {
in START_RANGE -> {
// top
val result = mutableListOf<Model>()
val selected = models.filter { it.isSelected }
models.forEach { model ->
if (!model.isSelected) {
if (!model.isTargeted)
result.add(model)
if (model.isTargeted) {
result.addAll(selected)
result.add(
model.copy(
isTargeted = false,
ratio = 0.0f
)
)
}
}
}
adapter.update(result)
}
in MIDDLE_RANDE -> {
// inside
val update =
models.filter { !it.isSelected }.map { it.copy(isTargeted = false) }
adapter.update(update)
}
in END_RANGE -> {
// bottom
val result = mutableListOf<Model>()
val selected = models.filter { it.isSelected }
models.forEach { model ->
if (!model.isSelected) {
if (!model.isTargeted)
result.add(model)
if (model.isTargeted) {
result.add(
model.copy(
isTargeted = false,
ratio = 0.0f
)
)
result.addAll(selected)
}
}
}
adapter.update(result)
}
}
}
toast("well done!")
}
fun calculate() {
val lm = recycler.layoutManager as LinearLayoutManager
recycler.findChildViewUnder(0f, screenHeight.toFloat())?.let { view ->
Timber.d("Found view: $view")
val first = lm.findFirstVisibleItemPosition()
val last = lm.findLastVisibleItemPosition()
val position = recycler.getChildAdapterPosition(view)
val center = screenHeight.toFloat()
val top = view.top
val height = view.height
val ratio: Float
if (center < top) {
Timber.d("top of the view is below center")
val delta = top - center
ratio = delta / height
Timber.d("Ration = $ratio")
} else {
Timber.d("top ($top) of the view is above center ($center)")
val delta = center - top
ratio = delta / height
Timber.d("Ration = $ratio")
}
Timber.d("Target position: $position")
Timber.d("First visible item position: $first")
Timber.d("Last visible item position: $last")
(recycler.adapter as Adapter).let {
val update = it.models.mapIndexed { index, model ->
model.copy(
isTargeted = index == position,
ratio = ratio
)
}
it.update(update)
}
}
}
companion object {
const val MODEL_COUNT = 20
val START_RANGE = 0.0..0.2
val MIDDLE_RANDE = 0.2..0.8
val END_RANGE = 0.8..1.0
}
}
data class Model(
val text: String,
val isTargeted: Boolean = false,
val isSelected: Boolean = false,
val ratio: Float = 0.0f
)
class Adapter(var models: List<Model>) : RecyclerView.Adapter<Adapter.ViewHolder>() {
override fun onCreateViewHolder(
parent: ViewGroup, viewType: Int
): ViewHolder = ViewHolder(
view = LayoutInflater.from(parent.context).inflate(
R.layout.item_scroll_and_move,
parent,
false
)
)
override fun getItemCount(): Int = models.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(models[position])
}
fun update(update: List<Model>) {
this.models = update
notifyDataSetChanged()
Timber.d("Updated data set: $update")
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val top = itemView.topDivider
private val bottom = itemView.bottomDivider
private val paragraph = itemView.paragraph
fun bind(model: Model) {
if (model.isTargeted) {
when (model.ratio) {
in START_RANGE -> {
itemView.setBackgroundColor(Color.WHITE)
}
in MIDDLE_RANDE -> {
itemView.setBackgroundColor(Color.parseColor("#FFB522"))
}
in END_RANGE -> {
itemView.setBackgroundColor(Color.WHITE)
}
}
Timber.d("Binding selected model: $model")
} else {
itemView.setBackgroundColor(Color.WHITE)
//bottom.invisible()
//top.invisible()
Timber.d("Binding simple model")
}
if (model.isSelected) {
paragraph.setTextColor(
Color.parseColor("#3E58EB")
)
} else {
paragraph.setTextColor(
Color.parseColor("#2C2B27")
)
}
paragraph.text = model.text
itemView.isSelected = false
}
}
companion object {
val START_RANGE = 0.0..0.2
val MIDDLE_RANDE = 0.2..0.8
val END_RANGE = 0.8..1.0
}
}

View file

@ -1,36 +0,0 @@
package com.anytypeio.anytype.sample.search
import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.doOnTextChanged
import androidx.recyclerview.widget.LinearLayoutManager
import com.anytypeio.anytype.sample.R
import kotlinx.android.synthetic.main.activity_search_on_page.*
class SearchOnPageActivity : AppCompatActivity(R.layout.activity_search_on_page) {
private val items = mutableListOf<SearchOnPageAdapter.Item>().apply {
repeat(10) {
add(
SearchOnPageAdapter.Item(
id = it,
txt = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
)
)
}
}
private val mockAdapter = SearchOnPageAdapter(
items = items
)
override fun onStart() {
super.onStart()
recycler.apply {
layoutManager = LinearLayoutManager(context)
adapter = mockAdapter
}
search.doOnTextChanged { text, start, before, count ->
}
}
}

View file

@ -1,36 +0,0 @@
package com.anytypeio.anytype.sample.search
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.anytypeio.anytype.sample.R
import com.anytypeio.anytype.sample.adapter.AbstractAdapter
import com.anytypeio.anytype.sample.adapter.AbstractHolder
import kotlinx.android.synthetic.main.item_editable.view.*
class SearchOnPageAdapter(
private var items: List<Item>
) : AbstractAdapter<SearchOnPageAdapter.Item>(items) {
override fun update(update: List<Item>) {
this.items = update
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AbstractHolder<Item> {
val inflater = LayoutInflater.from(parent.context)
val view = inflater.inflate(R.layout.item_editable, parent, false)
return ItemViewHolder(view)
}
data class Item(
val id: Int,
val txt: String
)
class ItemViewHolder(view: View) : AbstractHolder<Item>(view) {
override fun bind(item: Item) {
itemView.input.setText(item.txt)
}
}
}