forked from upstream-mirrors/quik
Launcher widget
This commit is contained in:
@@ -19,6 +19,8 @@
|
||||
package manager
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.klinker.android.send_message.BroadcastUtils
|
||||
import me.leolin.shortcutbadger.ShortcutBadger
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -26,6 +28,8 @@ class WidgetManagerImpl @Inject constructor(private val context: Context) : Widg
|
||||
|
||||
override fun updateUnreadCount(count: Int) {
|
||||
ShortcutBadger.applyCount(context, count)
|
||||
|
||||
BroadcastUtils.sendExplicitBroadcast(context, Intent(), WidgetManager.ACTION_NOTIFY_DATASET_CHANGED)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -93,6 +93,27 @@ class MessageRepositoryImpl @Inject constructor(
|
||||
})
|
||||
}
|
||||
|
||||
override fun getConversationsForWidget(): List<Pair<Conversation, Message>> {
|
||||
val realm = Realm.getDefaultInstance()
|
||||
|
||||
val conversations = realm.copyFromRealm(realm.where(Conversation::class.java)
|
||||
.equalTo("archived", false)
|
||||
.equalTo("blocked", false)
|
||||
.findAll())
|
||||
|
||||
val messages = realm.copyFromRealm(realm.where(Message::class.java)
|
||||
.sort("date", Sort.DESCENDING)
|
||||
.distinctValues("threadId")
|
||||
.findAll())
|
||||
|
||||
val conversationMap = conversations.associateBy { conversation -> conversation.id }
|
||||
|
||||
return messages.mapNotNull { message ->
|
||||
val conversation = conversationMap[message.threadId]
|
||||
if (conversation == null) null else Pair(conversation, message)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBlockedConversations(): Flowable<List<Conversation>> {
|
||||
return Realm.getDefaultInstance()
|
||||
.where(Conversation::class.java)
|
||||
|
||||
@@ -20,6 +20,10 @@ package manager
|
||||
|
||||
interface WidgetManager {
|
||||
|
||||
companion object {
|
||||
const val ACTION_NOTIFY_DATASET_CHANGED = "com.moez.QKSMS.intent.action.ACTION_NOTIFY_DATASET_CHANGED"
|
||||
}
|
||||
|
||||
fun updateUnreadCount(count: Int)
|
||||
|
||||
}
|
||||
@@ -30,6 +30,8 @@ interface MessageRepository {
|
||||
|
||||
fun getConversations(archived: Boolean = false): Flowable<List<Pair<Conversation, Message>>>
|
||||
|
||||
fun getConversationsForWidget(): List<Pair<Conversation, Message>>
|
||||
|
||||
fun getBlockedConversations(): Flowable<List<Conversation>>
|
||||
|
||||
fun getConversationAsync(threadId: Long): Conversation
|
||||
|
||||
@@ -112,6 +112,18 @@
|
||||
<action android:name="android.provider.action.EXTERNAL_PROVIDER_CHANGE" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver android:name="feature.widget.WidgetProvider">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.moez.QKSMS.intent.action.ACTION_NOTIFY_DATASET_CHANGED" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_info" />
|
||||
</receiver>
|
||||
|
||||
<service android:name="com.android.mms.transaction.TransactionService" />
|
||||
<service
|
||||
@@ -128,6 +140,9 @@
|
||||
<data android:scheme="mmsto" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service
|
||||
android:name="feature.widget.WidgetService"
|
||||
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
||||
|
||||
<meta-data
|
||||
android:name="preloaded_fonts"
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Moez Bhatti <moez.bhatti@gmail.com>
|
||||
*
|
||||
* This file is part of QKSMS.
|
||||
*
|
||||
* QKSMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QKSMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QKSMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package feature.widget
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Typeface
|
||||
import android.telephony.PhoneNumberUtils
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.style.StyleSpan
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import android.widget.RemoteViewsService
|
||||
import com.moez.QKSMS.R
|
||||
import common.util.Colors
|
||||
import common.util.DateFormatter
|
||||
import common.util.GlideApp
|
||||
import common.util.extensions.dpToPx
|
||||
import feature.main.MainActivity
|
||||
import injection.appComponent
|
||||
import model.Contact
|
||||
import model.Conversation
|
||||
import model.Message
|
||||
import model.PhoneNumber
|
||||
import repository.MessageRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class WidgetAdapter(intent: Intent) : RemoteViewsService.RemoteViewsFactory {
|
||||
|
||||
companion object {
|
||||
private const val MAX_CONVERSATIONS_COUNT = 25
|
||||
}
|
||||
|
||||
@Inject lateinit var context: Context
|
||||
@Inject lateinit var colors: Colors
|
||||
@Inject lateinit var dateFormatter: DateFormatter
|
||||
@Inject lateinit var messageRepo: MessageRepository
|
||||
|
||||
private val appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)
|
||||
private val smallWidget = intent.getBooleanExtra("small_widget", false)
|
||||
private var conversations: List<Pair<Conversation, Message>> = listOf()
|
||||
private val appWidgetManager by lazy { AppWidgetManager.getInstance(context) }
|
||||
|
||||
// Cache colors, load lazily. This entire class is recreated when the widget is recreated,
|
||||
// so if the theme changes then these values will be correct in the new instance
|
||||
private val theme by lazy { colors.theme.blockingFirst() }
|
||||
private val background by lazy { colors.background.blockingFirst() }
|
||||
private val textPrimary by lazy { colors.textPrimary.blockingFirst() }
|
||||
private val textSecondary by lazy { colors.textSecondary.blockingFirst() }
|
||||
private val textTertiary by lazy { colors.textTertiary.blockingFirst() }
|
||||
private val textPrimaryOnTheme by lazy { colors.textPrimaryOnTheme.blockingFirst() }
|
||||
|
||||
override fun onCreate() {
|
||||
appComponent.inject(this)
|
||||
}
|
||||
|
||||
override fun onDataSetChanged() {
|
||||
conversations = messageRepo.getConversationsForWidget()
|
||||
|
||||
val remoteViews = RemoteViews(context.packageName, R.layout.widget)
|
||||
appWidgetManager.partiallyUpdateAppWidget(appWidgetId, remoteViews)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of rows to show. If there are more conversations than the max count,
|
||||
* we'll return the max count + 1, where the last row just shows "View more conversations"
|
||||
*/
|
||||
override fun getCount(): Int {
|
||||
val count = Math.min(conversations.size, MAX_CONVERSATIONS_COUNT)
|
||||
val shouldShowViewMore = count < conversations.size
|
||||
return count + if (shouldShowViewMore) 1 else 0
|
||||
}
|
||||
|
||||
override fun getViewAt(position: Int): RemoteViews {
|
||||
return when {
|
||||
position >= MAX_CONVERSATIONS_COUNT -> getOverflowView()
|
||||
else -> getConversationView(position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getConversationView(position: Int): RemoteViews {
|
||||
val conversation = conversations[position].first
|
||||
val message = conversations[position].second
|
||||
|
||||
val remoteViews = RemoteViews(context.packageName, R.layout.widget_list_item)
|
||||
|
||||
// Avatar
|
||||
remoteViews.setViewVisibility(R.id.avatar, if (smallWidget) View.GONE else View.VISIBLE)
|
||||
remoteViews.setInt(R.id.avatar, "setBackgroundColor", theme)
|
||||
remoteViews.setTextColor(R.id.initial, textPrimaryOnTheme)
|
||||
remoteViews.setInt(R.id.icon, "setColorFilter", textPrimaryOnTheme)
|
||||
remoteViews.setInt(R.id.avatarMask, "setColorFilter", background)
|
||||
|
||||
val contact = conversation.recipients.map { recipient ->
|
||||
recipient.contact ?: Contact().apply { numbers.add(PhoneNumber().apply { address = recipient.address }) }
|
||||
}.firstOrNull()
|
||||
|
||||
|
||||
// Use the icon if there's no name, otherwise show an initial
|
||||
if (contact?.name.orEmpty().isNotEmpty()) {
|
||||
remoteViews.setTextViewText(R.id.initial, contact?.name?.substring(0, 1))
|
||||
remoteViews.setViewVisibility(R.id.icon, View.GONE)
|
||||
} else {
|
||||
remoteViews.setTextViewText(R.id.initial, null)
|
||||
remoteViews.setViewVisibility(R.id.icon, View.VISIBLE)
|
||||
}
|
||||
|
||||
remoteViews.setImageViewBitmap(R.id.photo, null)
|
||||
contact?.numbers?.firstOrNull()?.address?.let { address ->
|
||||
val futureGet = GlideApp.with(context)
|
||||
.asBitmap()
|
||||
.load(PhoneNumberUtils.stripSeparators(address))
|
||||
.submit(48.dpToPx(context), 48.dpToPx(context))
|
||||
|
||||
try {
|
||||
remoteViews.setImageViewBitmap(R.id.photo, futureGet.get())
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
// Name
|
||||
remoteViews.setTextColor(R.id.name, textPrimary)
|
||||
remoteViews.setTextViewText(R.id.name, boldText(conversation.getTitle(), !message.read))
|
||||
|
||||
// Date
|
||||
remoteViews.setTextColor(R.id.date, if (message.read) textTertiary else textPrimary)
|
||||
remoteViews.setTextViewText(R.id.date, boldText(dateFormatter.getConversationTimestamp(message.date), !message.read))
|
||||
|
||||
// Snippet
|
||||
remoteViews.setTextColor(R.id.snippet, if (message.read) textTertiary else textPrimary)
|
||||
remoteViews.setTextViewText(R.id.snippet, boldText(message.getSummary(), !message.read))
|
||||
|
||||
// Launch conversation on click
|
||||
val clickIntent = Intent().putExtra("threadId", conversation.id)
|
||||
remoteViews.setOnClickFillInIntent(R.id.conversation, clickIntent)
|
||||
|
||||
return remoteViews
|
||||
}
|
||||
|
||||
private fun getOverflowView(): RemoteViews {
|
||||
val view = RemoteViews(context.packageName, R.layout.widget_loading)
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
val pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
view.setTextColor(R.id.loadingText, textSecondary)
|
||||
view.setTextViewText(R.id.loadingText, context.getString(R.string.widget_more))
|
||||
view.setOnClickPendingIntent(R.id.loading, pendingIntent)
|
||||
return view
|
||||
}
|
||||
|
||||
private fun boldText(text: String, shouldBold: Boolean): CharSequence {
|
||||
return if (shouldBold) {
|
||||
SpannableString(text).apply {
|
||||
setSpan(StyleSpan(Typeface.BOLD), 0, length, Spannable.SPAN_INCLUSIVE_INCLUSIVE)
|
||||
}
|
||||
} else {
|
||||
text
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLoadingView(): RemoteViews {
|
||||
val view = RemoteViews(context.packageName, R.layout.widget_loading)
|
||||
view.setTextViewText(R.id.loadingText, context.getText(R.string.widget_loading))
|
||||
return view
|
||||
}
|
||||
|
||||
override fun getViewTypeCount(): Int {
|
||||
return 2
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return position.toLong()
|
||||
}
|
||||
|
||||
override fun hasStableIds(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Moez Bhatti <moez.bhatti@gmail.com>
|
||||
*
|
||||
* This file is part of QKSMS.
|
||||
*
|
||||
* QKSMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QKSMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QKSMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package feature.widget
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProvider
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.widget.RemoteViews
|
||||
import com.moez.QKSMS.R
|
||||
import common.util.Colors
|
||||
import feature.compose.ComposeActivity
|
||||
import feature.main.MainActivity
|
||||
import injection.appComponent
|
||||
import manager.WidgetManager
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class WidgetProvider : AppWidgetProvider() {
|
||||
|
||||
@Inject lateinit var colors: Colors
|
||||
|
||||
init {
|
||||
appComponent.inject(this)
|
||||
}
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
when (intent.action) {
|
||||
WidgetManager.ACTION_NOTIFY_DATASET_CHANGED -> updateData(context)
|
||||
else -> super.onReceive(context, intent)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all widgets in the list
|
||||
*/
|
||||
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
|
||||
super.onUpdate(context, appWidgetManager, appWidgetIds)
|
||||
|
||||
for (appWidgetId in appWidgetIds) {
|
||||
updateWidget(context, appWidgetId, isSmallWidget(appWidgetManager, appWidgetId))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all the widgets that they should update their adapter data
|
||||
*/
|
||||
private fun updateData(context: Context) {
|
||||
val appWidgetManager = AppWidgetManager.getInstance(context)
|
||||
val appWidgetIds = appWidgetManager.getAppWidgetIds(ComponentName(context, WidgetProvider::class.java))
|
||||
|
||||
// We need to update all Mms appwidgets on the home screen.
|
||||
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.conversations)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update widget when widget size changes
|
||||
*/
|
||||
override fun onAppWidgetOptionsChanged(context: Context, appWidgetManager: AppWidgetManager, appWidgetId: Int, newOptions: Bundle) {
|
||||
updateWidget(context, appWidgetId, isSmallWidget(appWidgetManager, appWidgetId))
|
||||
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true when widget has less than 4 columns, else false
|
||||
*/
|
||||
private fun isSmallWidget(appWidgetManager: AppWidgetManager, appWidgetId: Int): Boolean {
|
||||
val options = appWidgetManager.getAppWidgetOptions(appWidgetId)
|
||||
val size = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)
|
||||
|
||||
var n = 2
|
||||
while (70 * n - 30 < size) {
|
||||
++n
|
||||
}
|
||||
|
||||
val columns = n - 1
|
||||
|
||||
return columns < 4
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the widget appWidgetId
|
||||
*/
|
||||
private fun updateWidget(context: Context, appWidgetId: Int, smallWidget: Boolean) {
|
||||
Timber.v("updateWidget appWidgetId: $appWidgetId")
|
||||
val remoteViews = RemoteViews(context.packageName, R.layout.widget)
|
||||
|
||||
// Apply colors from theme
|
||||
remoteViews.setInt(R.id.background, "setColorFilter", colors.background.blockingFirst())
|
||||
remoteViews.setInt(R.id.toolbar, "setColorFilter", colors.toolbarColor.blockingFirst())
|
||||
remoteViews.setTextColor(R.id.title, colors.textPrimary.blockingFirst())
|
||||
remoteViews.setInt(R.id.compose, "setColorFilter", colors.theme.blockingFirst())
|
||||
|
||||
// Set adapter for conversations
|
||||
val intent = Intent(context, WidgetService::class.java)
|
||||
.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
|
||||
.putExtra("small_widget", smallWidget)
|
||||
intent.data = Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))
|
||||
remoteViews.setRemoteAdapter(R.id.conversations, intent)
|
||||
|
||||
// Main intent
|
||||
val mainIntent = Intent(context, MainActivity::class.java)
|
||||
val mainPI = PendingIntent.getActivity(context, 0, mainIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
remoteViews.setOnClickPendingIntent(R.id.title, mainPI)
|
||||
|
||||
// Compose intent
|
||||
val composeIntent = Intent(context, ComposeActivity::class.java)
|
||||
val composePI = PendingIntent.getActivity(context, 0, composeIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
remoteViews.setOnClickPendingIntent(R.id.compose, composePI)
|
||||
|
||||
// Conversation intent
|
||||
val startActivityIntent = Intent(context, ComposeActivity::class.java)
|
||||
val startActivityPendingIntent = PendingIntent.getActivity(context, 0, startActivityIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
remoteViews.setPendingIntentTemplate(R.id.conversations, startActivityPendingIntent)
|
||||
|
||||
AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, remoteViews)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Moez Bhatti <moez.bhatti@gmail.com>
|
||||
*
|
||||
* This file is part of QKSMS.
|
||||
*
|
||||
* QKSMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* QKSMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with QKSMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package feature.widget
|
||||
|
||||
import android.content.Intent
|
||||
import android.widget.RemoteViewsService
|
||||
|
||||
class WidgetService : RemoteViewsService() {
|
||||
|
||||
override fun onGetViewFactory(intent: Intent): RemoteViewsService.RemoteViewsFactory {
|
||||
return WidgetAdapter(intent)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,7 +20,6 @@ package injection
|
||||
|
||||
import common.QKApplication
|
||||
import common.util.ContactImageLoader
|
||||
import dagger.Component
|
||||
import common.widget.AvatarView
|
||||
import common.widget.MmsPreviewView
|
||||
import common.widget.PagerTitleView
|
||||
@@ -29,6 +28,7 @@ import common.widget.QkEditText
|
||||
import common.widget.QkSwitch
|
||||
import common.widget.QkTextView
|
||||
import common.widget.Separator
|
||||
import dagger.Component
|
||||
import feature.blocked.BlockedActivity
|
||||
import feature.blocked.BlockedViewModel
|
||||
import feature.compose.ComposeActivity
|
||||
@@ -50,6 +50,8 @@ import feature.setup.SetupActivity
|
||||
import feature.setup.SetupViewModel
|
||||
import feature.themepicker.ThemePickerActivity
|
||||
import feature.themepicker.ThemePickerViewModel
|
||||
import feature.widget.WidgetAdapter
|
||||
import feature.widget.WidgetProvider
|
||||
import receiver.DefaultSmsChangedReceiver
|
||||
import receiver.MarkReadReceiver
|
||||
import receiver.MarkSeenReceiver
|
||||
@@ -93,6 +95,9 @@ interface AppComponent {
|
||||
fun inject(receiver: RemoteMessagingReceiver)
|
||||
fun inject(receiver: SmsProviderChangedReceiver)
|
||||
fun inject(receiver: SmsReceiver)
|
||||
fun inject(receiver: WidgetProvider)
|
||||
|
||||
fun inject(service: WidgetAdapter)
|
||||
|
||||
fun inject(view: AvatarView)
|
||||
fun inject(view: DetailedChipView)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2017 Moez Bhatti <moez.bhatti@gmail.com>
|
||||
~
|
||||
~ This file is part of QKSMS.
|
||||
~
|
||||
~ QKSMS is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ QKSMS is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with QKSMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/white" />
|
||||
<corners
|
||||
android:topLeftRadius="4dp"
|
||||
android:topRightRadius="4dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 428 KiB |
@@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2017 Moez Bhatti <moez.bhatti@gmail.com>
|
||||
~
|
||||
~ This file is part of QKSMS.
|
||||
~
|
||||
~ QKSMS is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ QKSMS is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with QKSMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<RelativeLayout
|
||||
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">
|
||||
|
||||
<!-- Background card. Needs to be an ImageView because nothing else supports tinting -->
|
||||
<ImageView
|
||||
android:id="@+id/background"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:src="@drawable/rounded_rectangle_4dp" />
|
||||
|
||||
<!-- Apply the background card -->
|
||||
<ImageView
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/rounded_rectangle_top_4dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_alignBottom="@id/toolbar"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignTop="@id/toolbar"
|
||||
android:layout_toStartOf="@id/compose"
|
||||
android:background="@drawable/ripple"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingStart="16dp"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="18sp"
|
||||
tools:textColor="@color/textPrimary" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/compose"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_alignBottom="@id/toolbar"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignTop="@id/toolbar"
|
||||
android:background="@drawable/ripple"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/ic_add_black_24dp"
|
||||
tools:tint="@color/tools_theme" />
|
||||
|
||||
<ListView
|
||||
android:id="@+id/conversations"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_below="@id/toolbar"
|
||||
android:cacheColorHint="#00000000"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:divider="@null"
|
||||
android:dividerHeight="0dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingTop="8dp"
|
||||
tools:listitem="@layout/widget_list_item" />
|
||||
|
||||
<!-- This needs to be a FrameLayout, since Views are not supported-->
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="8dp"
|
||||
android:layout_below="@id/toolbar"
|
||||
android:background="@drawable/ab_shadow" />
|
||||
|
||||
</RelativeLayout>
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!--
|
||||
~ Copyright (C) 2017 Moez Bhatti <moez.bhatti@gmail.com>
|
||||
~
|
||||
~ This file is part of QKSMS.
|
||||
~
|
||||
~ QKSMS is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ QKSMS is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with QKSMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<LinearLayout
|
||||
android:id="@+id/conversation"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/ripple"
|
||||
android:gravity="center_vertical|start"
|
||||
android:orientation="horizontal"
|
||||
android:paddingBottom="12dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingTop="12dp">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/avatar"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
tools:background="@color/tools_theme">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/initial"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:textAllCaps="true"
|
||||
android:textSize="20sp"
|
||||
tools:text="A"
|
||||
tools:textColor="@color/textPrimaryDark" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/ic_person_black_24dp"
|
||||
tools:tint="@color/white" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/photo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/avatarMask"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:src="@drawable/circle_mask" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_toStartOf="@+id/date"
|
||||
android:singleLine="true"
|
||||
android:textSize="16sp"
|
||||
tools:text="@tools:sample/full_names" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginStart="16dp"
|
||||
android:textSize="12sp"
|
||||
tools:text="@tools:sample/date/day_of_week" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/snippet"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@id/name"
|
||||
android:lines="1"
|
||||
android:singleLine="true"
|
||||
android:textSize="14sp"
|
||||
tools:text="@tools:sample/lorem" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2017 Moez Bhatti <moez.bhatti@gmail.com>
|
||||
~
|
||||
~ This file is part of QKSMS.
|
||||
~
|
||||
~ QKSMS is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ QKSMS is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with QKSMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<FrameLayout
|
||||
android:id="@+id/loading"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64sp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/loadingText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:textSize="14sp"
|
||||
tools:text="Loading..." />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -137,6 +137,9 @@
|
||||
|
||||
<string name="settings_dialog_theme_title">Theme</string>
|
||||
|
||||
<string name="widget_loading">Loading…</string>
|
||||
<string name="widget_more">View more conversations</string>
|
||||
|
||||
<string name="theme_material">Material</string>
|
||||
<string name="theme_rgb">RGB</string>
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (C) 2017 Moez Bhatti <moez.bhatti@gmail.com>
|
||||
~
|
||||
~ This file is part of QKSMS.
|
||||
~
|
||||
~ QKSMS is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ QKSMS is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with QKSMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:initialLayout="@layout/widget"
|
||||
android:minHeight="110dp"
|
||||
android:minWidth="180dp"
|
||||
android:previewImage="@drawable/widget_preview"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:updatePeriodMillis="0"
|
||||
android:widgetCategory="home_screen|keyguard" />
|
||||
Reference in New Issue
Block a user