Unmesh/compose diary (#98)
* Diary sample baseline added to compose-samples * Updated main README table * README for Diary app updated * all Android Studio warnings cleared * Fixed all warnings within Android studio * Ran ktlint script * Deleted unused component * Placeholder for Editor and forced light mode on app due to calendarView * updated suggested changes in PR #98 * required changes for PR #98 * Row removed, weight adjusted * ktlint updated * Hardcoded string updated to use stringResource * string hardcoded again as there's an issue with pulling data from stringResource * Theme and other fixes for PR #98 * changes to theme and comment removed * stringResource issue fixed * screenshots added * updated style for text * project settings gradle file updated
|
@ -0,0 +1 @@
|
||||||
|
/build
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Diary
|
||||||
|
|
||||||
|
This sample is built with Jetpack Compose, the new UI framework in Android.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
To learn how to load apps on the Surface Duo emulator, see the [documentation](https://docs.microsoft.com/dual-screen/android), and follow [the blog](https://devblogs.microsoft.com/surface-duo).
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
When the app is in dual mode, both the Calendar View and the Editor will be spread out on two separate screens. If the user is in portrait mode, the app will give you the ability to toggle between calendar view and the editor
|
||||||
|
|
||||||
|
The sample uses the [Companion Pane](https://docs.microsoft.com/dual-screen/introduction#companion-pane) app pattern to show a Calendar view + Diary on One Screen , and the diary editor box for the selected day on the other pane.
|
||||||
|
|
||||||
|
|
||||||
|
## Single Screen Mode
|
||||||
|
|
||||||
|
In single screen mode, users are able to view one of two screens at a time: Diary Writer or Calendar with Content for the day. A button appears in the Calendar window and the editor that can be used to toggle between the two screens
|
||||||
|
![Screenshot_20220713-090404](https://user-images.githubusercontent.com/20245964/178855424-a3cdb8dc-b33f-432b-a328-c95de79af11c.png)
|
||||||
|
![Screenshot_20220713-090516](https://user-images.githubusercontent.com/20245964/178855427-6c498346-3ccb-4fad-8749-ac4993f03728.png)
|
||||||
|
|
||||||
|
|
||||||
|
## Dual Screen Mode
|
||||||
|
|
||||||
|
In dual screen mode, both windows are visible, so no buttons are visible to enable toggle.
|
||||||
|
![Screenshot_20220713-090345](https://user-images.githubusercontent.com/20245964/178855463-b6df48ae-f57d-4529-b0ce-23e32c19edff.png)
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||||
|
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
||||||
|
the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.
|
||||||
|
|
||||||
|
When you submit a pull request, a CLA bot will automatically determine whether you need to provide
|
||||||
|
a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions
|
||||||
|
provided by the bot. You will only need to do this once across all repos using our CLA.
|
||||||
|
|
||||||
|
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||||
|
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||||
|
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright (c) Microsoft Corporation.
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,61 @@
|
||||||
|
plugins {
|
||||||
|
id 'com.android.application'
|
||||||
|
id 'org.jetbrains.kotlin.android'
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdk 32
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "com.microsoft.device.display.samples.diary"
|
||||||
|
minSdk 29
|
||||||
|
targetSdk 32
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
vectorDrawables {
|
||||||
|
useSupportLibrary true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = '1.8'
|
||||||
|
}
|
||||||
|
buildFeatures {
|
||||||
|
compose true
|
||||||
|
}
|
||||||
|
composeOptions {
|
||||||
|
kotlinCompilerExtensionVersion composeVersion
|
||||||
|
}
|
||||||
|
packagingOptions {
|
||||||
|
resources {
|
||||||
|
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
implementation androidxDependencies.ktxCore
|
||||||
|
|
||||||
|
implementation composeDependencies.composeUI
|
||||||
|
implementation composeDependencies.composeRuntime
|
||||||
|
implementation composeDependencies.composeMaterial
|
||||||
|
implementation composeDependencies.composeUITooling
|
||||||
|
implementation composeDependencies.activityCompose
|
||||||
|
|
||||||
|
implementation googleDependencies.systemUiController
|
||||||
|
|
||||||
|
implementation microsoftDependencies.twoPaneLayout
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
package="com.microsoft.device.display.samples.diary">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/Theme.TwoPaneExample"
|
||||||
|
tools:targetApi="31">
|
||||||
|
<activity
|
||||||
|
android:name="com.microsoft.device.display.samples.diary.MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:windowSoftInputMode="adjustResize"
|
||||||
|
android:theme="@style/Theme.TwoPaneExample">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
|
@ -0,0 +1,9 @@
|
||||||
|
** Sample text for the diary App **
|
||||||
|
|
||||||
|
Today was wonderful
|
||||||
|
|
||||||
|
- I went for a run in the morning at 7:30 AM
|
||||||
|
- Had some herbal tea at 8:30 AM
|
||||||
|
- Picked up some breakfast at local bakery in Seattle
|
||||||
|
- Started working at 9:15 AM
|
||||||
|
- Had Pizza for Lunch at Cafe 86
|
|
@ -0,0 +1,39 @@
|
||||||
|
package com.microsoft.device.display.samples.diary
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import java.io.BufferedReader
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileReader
|
||||||
|
import java.io.FileWriter
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
fun readDayFile(fileName: String, rootDir: File, context: Context): String {
|
||||||
|
val file = File(rootDir, "/$fileName")
|
||||||
|
if (!file.exists()) {
|
||||||
|
return context.getString(R.string.no_diary_content)
|
||||||
|
}
|
||||||
|
val fileReader = FileReader(file)
|
||||||
|
|
||||||
|
val dayContent = BufferedReader(fileReader).useLines { lines ->
|
||||||
|
val content = StringBuilder()
|
||||||
|
lines.forEach { content.append(it + System.getProperty("line.separator")) }
|
||||||
|
content.toString()
|
||||||
|
}
|
||||||
|
fileReader.close()
|
||||||
|
return dayContent
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To save a txt file to a specified file path
|
||||||
|
*/
|
||||||
|
fun saveFile(fileName: String, content: String, rootDir: File) {
|
||||||
|
val file = File(rootDir, "/$fileName")
|
||||||
|
val out = FileWriter(file)
|
||||||
|
try {
|
||||||
|
out.write(content)
|
||||||
|
out.close()
|
||||||
|
} catch (ioError: IOException) {
|
||||||
|
Log.d("TAG", "saveFile: IO ERROR -> $ioError")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.microsoft.device.display.samples.diary
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import com.microsoft.device.display.samples.diary.ui.theme.DiaryTheme
|
||||||
|
|
||||||
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContent {
|
||||||
|
DiaryTheme {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
color = MaterialTheme.colors.background
|
||||||
|
) {
|
||||||
|
MainApp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package com.microsoft.device.display.samples.diary
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import com.microsoft.device.display.samples.diary.ui.pages.CalendarPage
|
||||||
|
import com.microsoft.device.display.samples.diary.ui.pages.DiaryPage
|
||||||
|
import com.microsoft.device.dualscreen.twopanelayout.TwoPaneLayout
|
||||||
|
import java.io.File
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For handling window navigation for Surface Duo, this acts as a main Controller for both the Composable
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun MainApp() {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val rootDataDir: File = context.applicationContext.dataDir
|
||||||
|
|
||||||
|
var text by rememberSaveable { mutableStateOf("") }
|
||||||
|
var currentSelectedDate by rememberSaveable {
|
||||||
|
mutableStateOf(LocalDate.now())
|
||||||
|
}
|
||||||
|
var content by rememberSaveable { mutableStateOf("") }
|
||||||
|
|
||||||
|
val updateDate: (LocalDate) -> Unit = { date ->
|
||||||
|
currentSelectedDate = date
|
||||||
|
}
|
||||||
|
val updateContent: () -> Unit = {
|
||||||
|
content = readDayFile(currentSelectedDate.toString(), rootDataDir, context)
|
||||||
|
}
|
||||||
|
val updateText: (String) -> Unit = { newText ->
|
||||||
|
text = newText
|
||||||
|
}
|
||||||
|
|
||||||
|
TwoPaneLayout(
|
||||||
|
pane1 = {
|
||||||
|
CalendarPage(
|
||||||
|
content = content,
|
||||||
|
selectedDate = currentSelectedDate,
|
||||||
|
updateDate = updateDate,
|
||||||
|
updateContent = updateContent
|
||||||
|
)
|
||||||
|
},
|
||||||
|
pane2 = {
|
||||||
|
DiaryPage(
|
||||||
|
text = text,
|
||||||
|
updateText = updateText,
|
||||||
|
selectedDate = currentSelectedDate,
|
||||||
|
updateContent = updateContent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
package com.microsoft.device.display.samples.diary.ui.pages
|
||||||
|
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.CalendarView
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
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.wrapContentWidth
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Scaffold
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.TopAppBar
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Edit
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.Shadow
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
import com.microsoft.device.display.samples.diary.R
|
||||||
|
import com.microsoft.device.dualscreen.twopanelayout.TwoPaneScope
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The CalendarView Composable which also contains the Diary Content for the selected day
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun TwoPaneScope.CalendarPage(
|
||||||
|
content: String,
|
||||||
|
selectedDate: LocalDate,
|
||||||
|
updateDate: (LocalDate) -> Unit,
|
||||||
|
updateContent: () -> Unit
|
||||||
|
) {
|
||||||
|
val twoPaneScope = this
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(text = stringResource(id = R.string.app_name))
|
||||||
|
},
|
||||||
|
backgroundColor = MaterialTheme.colors.primaryVariant,
|
||||||
|
actions = {
|
||||||
|
if (twoPaneScope.isSinglePane) {
|
||||||
|
IconButton(
|
||||||
|
onClick = { twoPaneScope.navigateToPane2() }
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Edit,
|
||||||
|
contentDescription = stringResource(R.string.edit_diary)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
|
||||||
|
Column {
|
||||||
|
AndroidView(
|
||||||
|
{
|
||||||
|
CalendarView(it)
|
||||||
|
.apply {
|
||||||
|
layoutParams = ViewGroup.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.wrapContentWidth(),
|
||||||
|
update = {
|
||||||
|
it.setOnDateChangeListener { _, year, month, dayOfMonth ->
|
||||||
|
val currentSelectedDate = LocalDate.of(year, month + 1, dayOfMonth)
|
||||||
|
updateDate(currentSelectedDate)
|
||||||
|
updateContent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(color = Color.Gray)
|
||||||
|
.height(1.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(all = 10.dp),
|
||||||
|
text = selectedDate.toString(),
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 24.sp,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
shadow = Shadow(
|
||||||
|
color = Color.Gray,
|
||||||
|
offset = Offset(4.0f, 5.0f),
|
||||||
|
blurRadius = 2f
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = content,
|
||||||
|
Modifier.padding(10.dp),
|
||||||
|
style = TextStyle(
|
||||||
|
fontSize = 18.sp,
|
||||||
|
textAlign = TextAlign.Left,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
package com.microsoft.device.display.samples.diary.ui.pages
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material.Button
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Scaffold
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.TextField
|
||||||
|
import androidx.compose.material.TopAppBar
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.DateRange
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.microsoft.device.display.samples.diary.R
|
||||||
|
import com.microsoft.device.display.samples.diary.saveFile
|
||||||
|
import com.microsoft.device.dualscreen.twopanelayout.TwoPaneScope
|
||||||
|
import java.io.File
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EditorPage Composable contains the Editor for the Diary for selected date on the CalendarView
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun TwoPaneScope.DiaryPage(
|
||||||
|
text: String,
|
||||||
|
updateText: (String) -> Unit,
|
||||||
|
selectedDate: LocalDate,
|
||||||
|
updateContent: () -> Unit
|
||||||
|
) {
|
||||||
|
val twoPaneScope = this
|
||||||
|
val context = LocalContext.current
|
||||||
|
val rootDataDir: File = context.applicationContext.dataDir
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
if (isSinglePane) {
|
||||||
|
Text(text = stringResource(R.string.app_name))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backgroundColor = MaterialTheme.colors.primaryVariant,
|
||||||
|
actions = {
|
||||||
|
if (twoPaneScope.isSinglePane) {
|
||||||
|
IconButton(
|
||||||
|
onClick = { twoPaneScope.navigateToPane1() }
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.DateRange,
|
||||||
|
contentDescription = stringResource(R.string.date_picker)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
|
||||||
|
TextField(
|
||||||
|
value = text,
|
||||||
|
placeholder = { Text(stringResource(R.string.diary_placeholder)) },
|
||||||
|
textStyle = MaterialTheme.typography.h5,
|
||||||
|
onValueChange = { newText -> updateText(newText) },
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.weight(0.9f)
|
||||||
|
)
|
||||||
|
|
||||||
|
Button(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(150.dp)
|
||||||
|
.padding(all = 10.dp)
|
||||||
|
.weight(0.1f),
|
||||||
|
onClick = {
|
||||||
|
saveFile(selectedDate.toString(), text, rootDataDir)
|
||||||
|
updateContent()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.save_button))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.microsoft.device.display.samples.diary.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
val Purple200 = Color(0xFFBB86FC)
|
||||||
|
val Purple500 = Color(0xFF6200EE)
|
||||||
|
val Purple700 = Color(0xFF3700B3)
|
||||||
|
val Teal200 = Color(0xFF03DAC5)
|
||||||
|
val Teal500 = Color(0xFF03CAC5)
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.microsoft.device.display.samples.diary.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.Shapes
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
val Shapes = Shapes(
|
||||||
|
small = RoundedCornerShape(4.dp),
|
||||||
|
medium = RoundedCornerShape(4.dp),
|
||||||
|
large = RoundedCornerShape(0.dp)
|
||||||
|
)
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.microsoft.device.display.samples.diary.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.lightColors
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
private val LightColorPalette = lightColors(
|
||||||
|
primary = Purple500,
|
||||||
|
primaryVariant = Purple700,
|
||||||
|
secondary = Teal200
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DiaryTheme(
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val colors = LightColorPalette
|
||||||
|
|
||||||
|
MaterialTheme(
|
||||||
|
colors = colors,
|
||||||
|
typography = Typography,
|
||||||
|
shapes = Shapes,
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.microsoft.device.display.samples.diary.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.material.Typography
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.Shadow
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
// Set of Material typography styles to start with
|
||||||
|
val Typography = Typography(
|
||||||
|
body1 = TextStyle(
|
||||||
|
fontFamily = FontFamily.Default,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 24.sp,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
shadow = Shadow(
|
||||||
|
color = Color.Gray,
|
||||||
|
offset = Offset(4.0f, 5.0f),
|
||||||
|
blurRadius = 2f
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
|
body2 = TextStyle(
|
||||||
|
fontFamily = FontFamily.Default,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 18.sp,
|
||||||
|
textAlign = TextAlign.Left
|
||||||
|
)
|
||||||
|
)
|
|
@ -0,0 +1,30 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="85.84757"
|
||||||
|
android:endY="92.4963"
|
||||||
|
android:startX="42.9492"
|
||||||
|
android:startY="49.59793"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#44000000"
|
||||||
|
android:offset="0.0" />
|
||||||
|
<item
|
||||||
|
android:color="#00000000"
|
||||||
|
android:offset="1.0" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="#00000000" />
|
||||||
|
</vector>
|
|
@ -0,0 +1,170 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#3DDC84"
|
||||||
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M9,0L9,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,0L19,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,0L29,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,0L39,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,0L49,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,0L59,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,0L69,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,0L79,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M89,0L89,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M99,0L99,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,9L108,9"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,19L108,19"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,29L108,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,39L108,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,49L108,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,59L108,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,69L108,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,79L108,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,89L108,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,99L108,99"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,29L89,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,39L89,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,49L89,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,59L89,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,69L89,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,79L89,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,19L29,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,19L39,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,19L49,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,19L59,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,19L69,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,19L79,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
</vector>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
После Ширина: | Высота: | Размер: 1.4 KiB |
После Ширина: | Высота: | Размер: 2.8 KiB |
После Ширина: | Высота: | Размер: 982 B |
После Ширина: | Высота: | Размер: 1.7 KiB |
После Ширина: | Высота: | Размер: 1.9 KiB |
После Ширина: | Высота: | Размер: 3.8 KiB |
После Ширина: | Высота: | Размер: 2.8 KiB |
После Ширина: | Высота: | Размер: 5.8 KiB |
После Ширина: | Высота: | Размер: 3.8 KiB |
После Ширина: | Высота: | Размер: 7.6 KiB |
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="purple_200">#FFBB86FC</color>
|
||||||
|
<color name="purple_500">#FF6200EE</color>
|
||||||
|
<color name="purple_700">#FF3700B3</color>
|
||||||
|
<color name="teal_200">#FF03DAC5</color>
|
||||||
|
<color name="teal_700">#FF018786</color>
|
||||||
|
<color name="black">#FF000000</color>
|
||||||
|
<color name="white">#FFFFFFFF</color>
|
||||||
|
</resources>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Compose Diary</string>
|
||||||
|
<string name="calendar_button">Calendar</string>
|
||||||
|
<string name="diary_editor">Diary Editor</string>
|
||||||
|
<string name="save_button">Save Diary</string>
|
||||||
|
<string name="diary_placeholder">Start writing...</string>
|
||||||
|
<string name="no_diary_content">No data available for the day</string>
|
||||||
|
<string name="edit_diary">Edit Diary</string>
|
||||||
|
<string name="date_picker">Date Selector</string>
|
||||||
|
</resources>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<style name="Theme.TwoPaneExample" parent="android:Theme.Material.Light.NoActionBar">
|
||||||
|
<item name="android:statusBarColor">@color/purple_700</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
Sample backup rules file; uncomment and customize as necessary.
|
||||||
|
See https://developer.android.com/guide/topics/data/autobackup
|
||||||
|
for details.
|
||||||
|
Note: This file is ignored for devices older that API 31
|
||||||
|
See https://developer.android.com/about/versions/12/backup-restore
|
||||||
|
-->
|
||||||
|
<full-backup-content>
|
||||||
|
<!--
|
||||||
|
<include domain="sharedpref" path="."/>
|
||||||
|
<exclude domain="sharedpref" path="device.xml"/>
|
||||||
|
-->
|
||||||
|
</full-backup-content>
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
|
Sample data extraction rules file; uncomment and customize as necessary.
|
||||||
|
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||||
|
for details.
|
||||||
|
-->
|
||||||
|
<data-extraction-rules>
|
||||||
|
<cloud-backup>
|
||||||
|
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
||||||
|
<include .../>
|
||||||
|
<exclude .../>
|
||||||
|
-->
|
||||||
|
</cloud-backup>
|
||||||
|
<!--
|
||||||
|
<device-transfer>
|
||||||
|
<include .../>
|
||||||
|
<exclude .../>
|
||||||
|
</device-transfer>
|
||||||
|
-->
|
||||||
|
</data-extraction-rules>
|
|
@ -63,6 +63,8 @@ The samples are built with Microsoft Compose libraries, [TwoPaneLayout](https://
|
||||||
| | |
|
| | |
|
||||||
| [DragAndDrop](https://github.com/microsoft/surface-duo-compose-samples/tree/main/DragAndDrop) | ![DragAndDrop app icon](screenshots/drag_and_drop.svg) | Drag and Drop sample that shows how to implement drag and drop in your Android app, following the Android [drag and drop guidance](https://developer.android.com/guide/topics/ui/drag-drop) in Jetpack Compose. |
|
| [DragAndDrop](https://github.com/microsoft/surface-duo-compose-samples/tree/main/DragAndDrop) | ![DragAndDrop app icon](screenshots/drag_and_drop.svg) | Drag and Drop sample that shows how to implement drag and drop in your Android app, following the Android [drag and drop guidance](https://developer.android.com/guide/topics/ui/drag-drop) in Jetpack Compose. |
|
||||||
| | |
|
| | |
|
||||||
|
| [Diary](https://github.com/microsoft/surface-duo-compose-samples/tree/main/Diary) | |Diary app sample that encorporates Calendar view on one pane and diary editor for the selected date on the other pane, following the Android [Companion Pane](https://docs.microsoft.com/dual-screen/introduction#companion-pane) app pattern. |
|
||||||
|
| | |
|
||||||
|
|
||||||
## Social links
|
## Social links
|
||||||
|
|
||||||
|
|
|
@ -5,4 +5,4 @@
|
||||||
|
|
||||||
rootProject.name = "ComposeSamples"
|
rootProject.name = "ComposeSamples"
|
||||||
include ':ListDetail', ':CompanionPane', ':DualView', ':ExtendedCanvas', ':ComposeGallery',
|
include ':ListDetail', ':CompanionPane', ':DualView', ':ExtendedCanvas', ':ComposeGallery',
|
||||||
':TwoPage', ':NavigationRail', ':DyAdd', ':DragAndDrop', ':VideoPlusChat', ':SourceEditorCompose'
|
':TwoPage', ':NavigationRail', ':DyAdd', ':DragAndDrop', ':VideoPlusChat', ':SourceEditorCompose', ':Diary'
|